Adding Vuex

This commit is contained in:
An Phan 2017-04-16 09:37:54 +08:00
parent 024caa40e1
commit 0ad2916d1d
No known key found for this signature in database
GPG key ID: 05536BB4BCDC02A2
24 changed files with 1466 additions and 159 deletions

View file

@ -14,7 +14,10 @@
"url": "https://github.com/phanan/koel"
},
"babel": {
"presets": ["es2015"]
"presets": [
"es2015",
"stage-0"
]
},
"eslintConfig": {
"extends": "vue",
@ -44,6 +47,7 @@
"autoprefixer": "^6.7.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-stage-0": "^6.22.0",
"babel-register": "^6.23.0",
"babel-runtime": "^6.22.0",
"chai": "^3.4.1",
@ -57,7 +61,8 @@
"laravel-mix": "^0.8.0",
"mocha": "^2.3.4",
"node-sass": "^3.4.2",
"sinon": "^1.17.2"
"sinon": "^1.17.2",
"vuex": "^2.2.1"
},
"scripts": {
"postinstall": "yarn build",

View file

@ -2,6 +2,8 @@ import './static-loader'
import Vue from 'vue'
import { event } from './utils'
import { http } from './services'
import store from './store'
import App from './app.vue'
import { VirtualScroller } from 'vue-virtual-scroller/dist/vue-virtual-scroller'
Vue.component('virtual-scroller', VirtualScroller)
@ -13,7 +15,8 @@ Vue.component('virtual-scroller', VirtualScroller)
*/
new Vue({
el: '#app',
render: h => h(require('./app.vue')),
store,
render: h => h(App),
created () {
event.init()
http.init()

View file

@ -24,6 +24,7 @@
<script>
import Vue from 'vue'
import { mapGetters, mapActions } from 'vuex'
import siteHeader from './components/site-header/index.vue'
import siteFooter from './components/site-footer/index.vue'
@ -47,13 +48,26 @@ export default {
}
},
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')
if (token) {
this.authenticated = true
computed: {
...mapGetters(['jwtToken'])
},
watch: {
jwtToken (token) {
if (!token) {
// log out
this.authenticated = false
forceReloadWindow()
return
}
this.init()
}
},
mounted () {
console.log(this.$store._modules)
// if a jwt token is found in local storage, init
this.jwtToken && this.init()
// Create the element to be the ghost drag image.
const dragGhost = document.createElement('div')
@ -73,29 +87,37 @@ export default {
methods: {
init () {
showOverlay()
this.authenticated = true
// 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()
this.$store.dispatch('initGlobal').then(data => {
this.$store.dispatch('initArtists', data)
.then(artists => this.$store.dispatch('initAlbums', artists))
.then(albums => this.$store.dispatch('initSongs', albums))
.then(() => this.$store.dispatch('initInteractions', data))
.then(() => this.$store.dispatch('initPlaylists', data))
.then(() => this.$store.dispatch('initSettings', data))
.then(() => this.$store.dispatch('initUsers', data))
.then(() => this.$store.dispatch('initPreferences', data))
.then(() => playback.init())
.then(({ player: { media } }) => {
event.emit('equalizer:init', media)
hideOverlay()
// Ask for user's notification permission.
this.requestNotifPermission()
// Ask for user's notification permission.
this.requestNotifPermission()
// To confirm or not to confirm closing, it's a question.
window.onbeforeunload = e => {
if (!preferences.confirmClosing) {
return
}
// To confirm or not to confirm closing, it's a question.
window.onbeforeunload = e => {
if (!preferences.confirmClosing) {
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.'
}
// Let all other components know we're ready.
event.emit('koel:ready')
// 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.'
}
})
}).catch(() => {
this.authenticated = false
})
@ -193,16 +215,6 @@ export default {
*/
'songs:edit': songs => this.$refs.editSongsForm.open(songs),
/**
* Log the current user out and reset the application state.
*/
logout () {
userStore.logout().then((r) => {
ls.remove('jwt-token')
forceReloadWindow()
})
},
/**
* Init our basic, custom router on ready to determine app state.
*/

View file

@ -1,7 +1,7 @@
<template>
<form @submit.prevent="login" :class="{ error: failed }">
<input v-model="email" type="email" placeholder="Email Address" autofocus required>
<input v-model="password" type="password" placeholder="Password" required>
<input v-model="credentials.email" type="email" placeholder="Email Address" autofocus required>
<input v-model="credentials.password" type="password" placeholder="Password" required>
<button type="submit">Log In</button>
</form>
</template>
@ -13,8 +13,10 @@ import { event } from '../../utils'
export default {
data () {
return {
email: '',
password: '',
credentials: {
email: '',
password: ''
},
failed: false
}
},
@ -22,14 +24,11 @@ export default {
methods: {
login () {
this.failed = false
userStore.login(this.email, this.password).then(() => {
this.$store.dispatch('login', this.credentials).then(() => {
this.failed = false
// Reset the password so that the next login will have this field empty.
this.password = ''
event.emit('user:loggedin')
this.credentials.password = ''
}).catch(() => {
this.failed = true
})

View file

@ -31,6 +31,7 @@
import { map, cloneDeep, each } from 'lodash'
import nouislider from 'nouislider'
import { mapGetters } from 'vuex'
import { isAudioContextSupported, event, $ } from '../../utils'
import { equalizerStore, preferenceStore as preferences } from '../../stores'
@ -44,6 +45,7 @@ export default {
},
computed: {
...mapGetters(['equalizerSettings', 'preferenceByKey']),
presets () {
const clonedPreset = cloneDeep(equalizerStore.presets)
// Prepend an empty option for instruction purpose.
@ -69,7 +71,13 @@ export default {
if (~~val !== -1) {
this.loadPreset(equalizerStore.getPresetById(val))
}
}
},
},
created () {
event.on('equalizer:init', mediaElement => {
isAudioContextSupported() && this.init(mediaElement)
})
},
methods: {
@ -78,8 +86,6 @@ export default {
* @param {Element} player The audio player's node.
*/
init (player) {
const settings = equalizerStore.get()
const AudioContext = window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
@ -89,7 +95,7 @@ export default {
const context = new AudioContext()
this.preampGainNode = context.createGain()
this.changePreampGain(settings.preamp)
this.changePreampGain(this.equalizerSettings.preamp)
const source = context.createMediaElementSource(player)
source.connect(this.preampGainNode)
@ -109,7 +115,7 @@ export default {
filter.type = 'peaking'
}
filter.gain.value = settings.gains[i] ? settings.gains[i] : 0
filter.gain.value = this.equalizerSettings.gains[i] ? this.equalizerSettings.gains[i] : 0
filter.Q.value = 1
filter.frequency.value = frequency
@ -131,12 +137,11 @@ export default {
* Create the UI sliders for both the preamp and the normal bands.
*/
createSliders () {
const config = equalizerStore.get()
each(Array.from(document.querySelectorAll('#equalizer .slider')), (el, i) => {
nouislider.create(el, {
connect: [false, true],
// the first element is the preamp. The rest are gains.
start: i === 0 ? config.preamp : config.gains[i - 1],
start: i === 0 ? this.equalizerSettings.preamp : this.equalizerSettings.gains[i - 1],
range: { min: -20, max: 20 },
orientation: 'vertical',
direction: 'rtl'
@ -165,7 +170,7 @@ export default {
})
// Now we set this value to trigger the audio processing.
this.selectedPresetIndex = preferences.selectedPreset
this.selectedPresetIndex = this.preferenceByKey('selectedPreset')
},
/**
@ -215,12 +220,6 @@ export default {
equalizerStore.set(this.preampGainValue, map(this.bands, 'filter.gain.value'))
}
},
mounted () {
event.on('equalizer:init', player => {
isAudioContextSupported() && this.init(player)
})
}
}
</script>

View file

@ -1,8 +1,8 @@
<template>
<span class="profile" id="userBadge">
<a class="view-profile control" href="/#!/profile">
<img class="avatar" :src="state.current.avatar" alt="Avatar"/>
<span class="name">{{ state.current.name }}</span>
<img class="avatar" :src="currentUser.avatar" alt="Avatar"/>
<span class="name">{{ currentUser.name }}</span>
</a>
<a class="logout" @click.prevent="logout"><i class="fa fa-sign-out control"></i></a>
@ -10,22 +10,17 @@
</template>
<script>
import { userStore } from '../../stores'
import { event } from '../../utils'
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'site-header--user-badge',
data () {
return {
state: userStore.state
}
computed: {
...mapGetters(['currentUser'])
},
methods: {
logout () {
event.emit('logout')
}
...mapActions(['logout'])
}
}
</script>

View file

@ -18,80 +18,81 @@ export const playback = {
* Initialize the playback service for this whole Koel app.
*/
init () {
// We don't need to init this service twice, or the media events will be duplicated.
if (this.initialized) {
return
}
this.player = plyr.setup({
controls: []
})[0]
this.audio = document.querySelector('audio')
this.volumeInput = document.getElementById('volumeRange')
/**
* Listen to 'error' event on the audio player and play the next song if any.
*/
document.querySelector('.plyr').addEventListener('error', () => 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)
return new Promise(resolve => {
// We don't need to init this service twice, or the media events will be duplicated.
if (this.initialized) {
return resolve()
}
if (preferences.repeatMode === 'REPEAT_ONE') {
this.restart()
return
this.player = plyr.setup({
controls: []
})[0]
this.audio = document.querySelector('audio')
this.volumeInput = document.getElementById('volumeRange')
/**
* Listen to 'error' event on the audio player and play the next song if any.
*/
document.querySelector('.plyr').addEventListener('error', () => 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)
}
if (preferences.repeatMode === 'REPEAT_ONE') {
this.restart()
return
}
this.playNext()
})
/**
* Attempt to preload the next song.
*/
document.querySelector('.plyr').addEventListener('canplaythrough', e => {
const nextSong = queueStore.next
if (!nextSong || nextSong.preloaded || (isMobile.any && preferences.transcodeOnMobile)) {
// Don't preload if
// - there's no next song
// - next song has already been preloaded
// - we're on mobile and "transcode" option is checked
return
}
const audio = document.createElement('audio')
audio.setAttribute('src', songStore.getSourceUrl(nextSong))
audio.setAttribute('preload', 'auto')
audio.load()
nextSong.preloaded = true
})
/**
* Listen to 'input' event on the volume range control.
* When user drags the volume control, this event will be triggered, and we
* update the volume on the plyr object.
*/
this.volumeInput.addEventListener('input', e => this.setVolume(e.target.value))
// On init, set the volume to the value found in the local storage.
this.setVolume(preferences.volume)
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => this.resume())
navigator.mediaSession.setActionHandler('pause', () => this.pause())
navigator.mediaSession.setActionHandler('previoustrack', () => this.playPrev())
navigator.mediaSession.setActionHandler('nexttrack', () => this.playNext())
}
this.playNext()
this.initialized = true
resolve(this)
})
/**
* Attempt to preload the next song.
*/
document.querySelector('.plyr').addEventListener('canplaythrough', e => {
const nextSong = queueStore.next
if (!nextSong || nextSong.preloaded || (isMobile.any && preferences.transcodeOnMobile)) {
// Don't preload if
// - there's no next song
// - next song has already been preloaded
// - we're on mobile and "transcode" option is checked
return
}
const audio = document.createElement('audio')
audio.setAttribute('src', songStore.getSourceUrl(nextSong))
audio.setAttribute('preload', 'auto')
audio.load()
nextSong.preloaded = true
})
/**
* Listen to 'input' event on the volume range control.
* When user drags the volume control, this event will be triggered, and we
* update the volume on the plyr object.
*/
this.volumeInput.addEventListener('input', e => this.setVolume(e.target.value))
// On init, set the volume to the value found in the local storage.
this.setVolume(preferences.volume)
// Init the equalizer if supported.
event.emit('equalizer:init', this.player.media)
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => this.resume())
navigator.mediaSession.setActionHandler('pause', () => this.pause())
navigator.mediaSession.setActionHandler('previoustrack', () => this.playPrev())
navigator.mediaSession.setActionHandler('nexttrack', () => this.playNext())
}
this.initialized = true
},
/**

View file

@ -0,0 +1,29 @@
import { assign } from 'lodash'
import isMobile from 'ismobilejs'
import { http } from '../services'
import * as types from './mutation-types'
const actions = {
initGlobal ({ commit }) {
return new Promise((resolve, reject) => {
http.get('data', ({ data }) => {
// Don't allow downloading on mobile devices
data.allowDownload = data.allowDownload && !isMobile.any
// Disable YouTube integration on mobile
data.useYouTube = data.useYouTube && !isMobile.phone
// If this is a new user, initialize his preferences as an empty object
if (!data.currentUser.preferences) {
data.currentUser.preferences = {}
}
// Keep a copy of the media path. We'll need this to properly warn the user later.
data.originalMediaPath = data.settings.media_path
commit(types.GLOBAL_INIT_DATA, data)
resolve(data)
}, error => reject(error))
})
}
}
export default actions

View file

@ -0,0 +1,5 @@
const getters = {
}
export default getters

View file

@ -0,0 +1,40 @@
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import albums from './modules/albums'
import artists from './modules/artists'
import equalizer from './modules/equalizer'
import favorites from './modules/favorites'
import playlists from './modules/playlists'
import preferences from './modules/preferences'
import queue from './modules/queue'
import settings from './modules/settings'
import songs from './modules/songs'
import users from './modules/users'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
state,
actions,
getters,
mutations,
modules: {
albums,
artists,
equalizer,
favorites,
playlists,
preferences,
queue,
settings,
songs,
users
},
strict: debug
})

View file

@ -0,0 +1,100 @@
/*eslint camelcase: ["error", {properties: "never"}]*/
import Vue from 'vue'
import { reduce, each, union, difference, take, filter, orderBy } from 'lodash'
import * as types from '../mutation-types'
const cache = []
const state = {
albums: []
}
const getters = {
allAlbums: state => state.albums,
albumById: state => id => cache[id],
isEmptyAlbum: state => album => !album.songs.length,
getAlbumLength: state => album => {
Vue.set(album, 'length', reduce(album.songs, (length, song) => length + song.length, 0))
Vue.set(album, 'fmtLength', secondsToHis(album.length))
return album.fmtLength
},
mostPlayedAlbums: state => (n = 6) => {
// Only non-unknown albums with actually play count are applicable.
const applicable = filter(state.albums, album => album.playCount && album.id !== 1)
return take(orderBy(applicable, 'playCount', 'desc'), n)
},
recentlyAddedAlbums: state => (n = 6) => {
const applicable = filter(state.albums, album => album.id !== 1)
return take(orderBy(applicable, 'created_at', 'desc'), n)
}
}
const actions = {
initAlbums ({ dispatch, commit, state }, artists) {
return new Promise(resolve => {
// Traverse through the artists array and add their albums into our master album list.
const albums = 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 => dispatch('setupAlbum', { album, artist }))
return albums.concat(artist.albums)
}, [])
commit(types.ALBUM_INIT_STORE, albums)
resolve(state.albums)
})
},
setupAlbum ({ commit }, { album, artist }) {
return new Promise(resolve => {
album.playCount = 0
album.artist = artist
album.info = null
cache[album.id] = album
resolve(album)
})
},
}
const mutations = {
[types.ALBUM_INIT_STORE] (state, albums) {
state.albums = albums
},
[types.ALBUM_SETUP] (state, { album, artist }) {
},
[types.ALBUM_ADD] (state, { album }) {
},
[types.ALBUM_ADD_SONGS_INTO_ALBUM] (state, { album, songs }) {
},
[types.ALBUM_REMOVE_SONGS_FROM_ALBUM] (state, { album, songs }) {
},
[types.ALBUM_REMOVE] (state, { albums }) {
},
[types.SET_INTERACTION_DATA] (state, { song: { album }, playCount }) {
album.playCount += playCount
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,123 @@
/*eslint camelcase: ["error", {properties: "never"}]*/
import Vue from 'vue'
import { reduce, each, clone, union, difference, take, filter, orderBy } from 'lodash'
import config from '../../config'
import * as types from '../mutation-types'
const UNKNOWN_ARTIST_ID = 1
const VARIOUS_ARTISTS_ID = 2
const cache = []
const state = {
artists: []
}
const getters = {
allArtists: state => state.artists,
artistById: state => id => cache[id],
isEmptyArtist: state => artist => !artist.albums.length,
isVariousArtist: state => artist => artist.id === VARIOUS_ARTISTS_ID,
isUnknownArtist: state => artist => artist.id === UNKNOWN_ARTIST_ID,
songsByArtist: state => artist => artist.songs,
artistImage: state => artist => {
},
mostPlayedArtists: state => (n = 6) => {
}
}
const actions = {
initArtists ({ dispatch, commit, state }, { artists }) {
return new Promise(resolve => {
each(artists, artist => dispatch('setupArtist', artist))
commit(types.ARTIST_INIT_STORE, artists)
resolve(state.artists)
})
},
setupArtist ({ commit }, artist) {
return new Promise(resolve => {
artist.playCount = 0
if (!artist.image) {
// Try to get an image from one of the albums.
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
// I want to break free.
return false
}
})
}
// 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.
// This also makes sure songs in compilation albums are counted as well.
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 }))
}
// Otherwise, just use all songs in the album.
return songs.concat(album.songs)
}, [])
artist.songCount = artist.songs.length
artist.info = null
cache[artist.id] = artist
resolve(artist)
})
},
addAlbumsIntoArtist ({ commit }, { artist, albums }) {
commit(types.ARTIST_ADD_ALBUMS_INTO_ARTIST, { artist, albums })
}
}
const mutations = {
[types.ARTIST_INIT_STORE] (state, artists) {
state.artists = artists
},
[types.ARTIST_ADD] (state, { artists }) {
},
[types.ARTIST_REMOVE] (state, { artists }) {
},
[types.ARTIST_ADD_ALBUMS_INTO_ARTIST] (state, { artist, albums }) {
each(albums, album => {
album.artist_id = artist.id
album.artist = artist
artist.playCount += album.playCount
})
artist.albums = union(artist.albums, [].concat(albums))
},
[types.ARTIST_REMOVE_ALBUMS_FROM_ARTIST] (state, { artist, albums }) {
},
[types.SET_INTERACTION_DATA] (state, { song: { artist }, playCount }) {
artist.playCount += playCount
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,119 @@
import { find } from 'lodash'
import * as types from '../mutation-types'
const state = {
presets: [
{
id: 0,
name: 'Default',
preamp: 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]
},
{
id: 2,
name: 'Club',
preamp: -6.7,
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]
},
{
id: 4,
name: 'Full Bass',
preamp: -7.2,
gains: [-8, 9, 9, 5, 1, -4, -8, -10, -11, -11]
},
{
id: 5,
name: 'Full Treble',
preamp: -12,
gains: [-9, -9, -9, -4, 2, 11, 16, 16, 16, 16]
},
{
id: 6,
name: 'Headphone',
preamp: -8,
gains: [4, 11, 5, -3, -2, 1, 4, 9, 12, 14]
},
{
id: 7,
name: 'Large Hall',
preamp: -7.2,
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]
},
{
id: 9,
name: 'Pop',
preamp: -6.2,
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]
},
{
id: 11,
name: 'Rock',
preamp: -10,
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]
},
{
id: 13,
name: 'Techno',
preamp: -7.7,
gains: [8, 5, -1, -5, -4, -1, 8, 9, 9, 8]
}
]
}
const getters = {
presetById: state => id => find(state.presets, { id }),
equalizerSettings: (state, getters) => {
const savedPresetId = getters.preferenceByKey('selectedPreset')
if (!savedPresetId) {
return getters.presetById(savedPresetId)
}
return getters.preferenceByKey('equalizer')
},
}
const actions = {
}
const mutations = {
[types.EQUALIZER_SAVE] (state, { preamp, gains }) {
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,61 @@
import { each, map, difference, union } from 'lodash'
import * as types from '../mutation-types'
const state = {
songs: []
}
const getters = {
allFavorites: state => state.songs
}
const actions = {
toggleLike ({ commit }) {
},
addToFavorites ({ commit }, songs) {
commit(types.FAVORITE_ADD, songs)
},
removeFromFavorites ({ commit }, songs) {
},
clearFavorites ({ commit }) {
},
like ({ commit }, songs) {
},
unlike ({ commit }, songs) {
}
}
const mutations = {
[types.FAVORITE_TOGGLE_ONE] (state, { song }) {
},
[types.FAVORITE_ADD] (state, { songs }) {
state.songs = union(state.songs, [].concat(songs))
},
[types.FAVORITE_REMOVE] (state, { songs }) {
},
[types.FAVORITE_CLEAR] (state) {
state.songs = []
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,67 @@
import { each, find, map, difference, union } from 'lodash'
import { http } from '../../services'
import * as types from '../mutation-types'
const state = {
playlists: []
}
const getters = {
allPlaylists: state => state.playlists,
playlistById: state => id => find(state.playlists, { id }),
songsInPlaylist: state => playlist => playlist.songs
}
const actions = {
initPlaylists ({ dispatch, state, commit, getters }, { playlists }) {
return new Promise(resolve => {
each(playlists, playlist => {
playlist.songs = getters.songsByIds(playlist.songs)
})
commit(types.PLAYLIST_INIT_STORE, playlists)
resolve(state.playlists)
})
},
addPlaylist ({ commit }, playlists) {
},
removePlaylist ({ commit }, playlists) {
},
storePlaylist ({ commit }, { name, songs }) {
},
deletePlaylist ({ commit }, playlist) {
},
addSongsIntoPlaylist ({ commit }, { playlist, songs }) {
},
removeSongsFromPlaylist ({ commit }, { playlist, songs }) {
},
updatePlaylist ({ commit }, playlist) {
}
}
const mutations = {
[types.PLAYLIST_INIT_STORE] (state, playlists) {
state.playlists = playlists
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,61 @@
import { extend, has, each } from 'lodash'
import { ls } from '../../services'
import * as types from '../mutation-types'
let storeKey = ''
const state = {
volume: 7,
notify: true,
repeatMode: 'NO_REPEAT',
showExtraPanel: true,
confirmClosing: false,
equalizer: {
preamp: 0,
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
artistsViewMode: null,
albumsViewMode: null,
selectedPreset: -1,
transcodeOnMobile: false
}
const getters = {
preferenceByKey: state => key => state[key]
}
const actions = {
initPreferences ({ commit, getters }, { currentUser: { preferences }}) {
return new Promise(resolve => {
commit(types.PREFERENCE_INIT_STORE, {
preferences,
user: getters.currentUser
})
resolve()
})
},
setPreferences ({ commit }, { key, val }) {
commit (types.PREFERENCE_SAVE, { key, val })
}
}
const mutations = {
[types.PREFERENCE_INIT_STORE] (state, { user, preferences }) {
storeKey = `preferences_${user.id}`
extend(state, ls.get(storeKey, state, {}))
// setupProxy()
},
[types.PREFERENCE_SAVE] (state) {
ls.set(storeKey, state)
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,61 @@
import { head, last, each, includes, union, difference, map, shuffle as _shuffle, first } from 'lodash'
import * as types from '../mutation-types'
const state = {
songs: [],
current: null
}
const getters = {
allQueuedSongs: state => state.songs,
firstQueuedSong: state => head(state.songs),
lastQueuedSong: state => last(state.songs),
containedInQueue: state => song => includes(state.songs, song),
indexOfInQueue: state => song => state.songs.indexOf(song),
nextInQueue: state => {
},
previousInQueue: state => {
}
}
const actions = {
queue ({ commit }, { songs, replace = true, toTop = true}) {
},
queueAfterCurrent ({ commit }, songs) {
},
unqueue ({ commit }, songs) {
},
moveInQueue ({ commit }, { songs, target }) {
},
shuffleQueue ({ commit }) {
},
clearQueue ({ commit }) {
}
}
const mutations = {
[types.QUEUE_QUEUE] (state) {
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,36 @@
import { http } from '../../services'
import * as types from '../mutation-types'
const state = {
settings: []
}
const getters = {
allSettings: state => state.settings
}
const actions = {
initSettings ({ commit }, { settings }) {
return new Promise(resolve => {
commit(types.SETTING_INIT_STORE, settings)
resolve()
})
},
updateSettings ({ commit }) {
}
}
const mutations = {
[types.SETTING_INIT_STORE] (state, settings) {
state.settings = settings
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,147 @@
import Vue from 'vue'
import slugify from 'slugify'
import { without, map, take, remove, orderBy, each, union, compact } from 'lodash'
import isMobile from 'ismobilejs'
import { secondsToHis, alerts, pluralize } from '../../utils'
import { http, ls } from '../../services'
import * as types from '../mutation-types'
const cache = {}
const state = {
songs: [],
recentlyPlayed: [],
}
const getters = {
allSongs: state => state.songs,
songById: state => id => cache[id],
songsByIds: (state, getters) => ids => ids.map(id => getters.songById(id)),
guessSong: state => (title, album) => {
title = slugify(title.toLowerCase())
let found = false
each(album.songs, song => {
if (slugify(song.title.toLowerCase()) === title) {
found = song
}
})
return found
},
recentlyPlayedSongs: state => state.recentlyPlayed,
songShareableUrl: state => song => `${window.location.origin}/#!/song/${song.id}`,
mostPlayedSongs: state => (n = 10) => {
const songs = take(orderBy(state.songs, 'playCount', 'desc'), n)
// Remove those with playCount=0
remove(songs, song => !song.playCount)
return songs
},
recentlyAddedSongs: state => (n = 10) => take(orderBy(state.songs, 'created_at', 'desc'), n)
}
const actions = {
initSongs ({ dispatch, commit, state }, albums) {
return new Promise(resolve => {
const songs = albums.reduce((songs, album) => {
each(album.songs, song => dispatch('setupSong', { song, album }))
return songs.concat(album.songs)
}, [])
commit(types.SONG_INIT_STORE, songs)
resolve(state.songs)
})
},
setupSong({ commit, getters, dispatch }, { song, album }) {
return new Promise(resolve => {
song.fmtLength = secondsToHis(song.length)
song.playCount = 0
song.album = album
song.liked = false
song.lyrics = null
song.playbackState = 'stopped'
if (song.contributing_artist_id) {
const artist = getters.artistById(song.contributing_artist_id)
dispatch('addAlbumsIntoArtist', { artist, album })
song.artist = artist
} else {
song.artist = getters.artistById(song.album.artist.id)
}
// Cache the song, so that byId() is faster
cache[song.id] = song
})
},
initInteractions({ commit, state, getters, dispatch }, { interactions }) {
return new Promise(resolve => {
each(interactions, interaction => {
const song = getters.songById(interaction.song_id)
if (!song) {
return
}
commit(types.SET_INTERACTION_DATA, {
song,
liked: interaction.liked,
playCount: interaction.play_count
})
if (song.liked) {
dispatch('addToFavorites', song)
}
})
resolve()
})
},
registerPlay ({ commit }, song) {
},
addRecentlyPlayed ({ commit }, song) {
},
scrobble ({ commit }, song) {
},
updateSongs ({ commit }, { song, data }) {
},
syncUpdatedSong ({ commit }, updatedSong) {
}
}
const mutations = {
[types.SONG_INIT_STORE] (state, songs) {
state.songs = songs
},
[types.SET_INTERACTION_DATA] (state, { song, liked, playCount }) {
song.liked = liked
song.playCount = playCount
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,109 @@
import { each, find, without } from 'lodash'
import md5 from 'blueimp-md5'
import Vue from 'vue'
import NProgress from 'nprogress'
import { http, ls } from '../../services'
import { alerts } from '../../utils'
import * as types from '../mutation-types'
const state = {
users: [],
current: {},
jwtToken: ls.get('jwt-token')
}
const getters = {
allUsers: state => state.users,
userById: state => id => find(state.users, { id }),
currentUser: state => state.current,
jwtToken: state => state.jwtToken
}
const actions = {
initUsers ({ commit }, { users, currentUser }) {
return new Promise(resolve => {
commit(types.USER_INIT_STORE, users)
commit(types.USER_SET_CURRENT, currentUser)
each(users, user => commit (types.USER_SET_AVATAR, user))
resolve()
})
},
setAvatar ({ commit, state }, user = null) {
if (!user) {
user = state.current
}
},
login ({ commit }, credentials) {
NProgress.start()
return new Promise((resolve, reject) => {
http.post('me', credentials, ({ data }) => {
commit(types.USER_SET_JWT_TOKEN, data.token)
resolve(data)
}, error => reject(error))
})
},
logout ({ commit }) {
return new Promise((resolve, reject) => {
http.delete('me', {}, ({ data }) => {
commit(types.USER_DELETE_JWT_TOKEN)
resolve(data)
}, error => reject(error))
})
},
updateProfile ({ commit }, { password }) {
},
storeUser ({ commit }, profile) {
},
updateUser ({ commit }, { user, profile }) {
},
destroyUser ({ commit }, user) {
}
}
const mutations = {
[types.USER_INIT_STORE] (state, users) {
state.users = users
},
[types.USER_SET_CURRENT] (state, currentUser) {
state.current = currentUser
},
[types.USER_SET_AVATAR] (state, user) {
if (!user) {
user = state.current
}
user.avatar = `https://www.gravatar.com/avatar/${md5(user.email)}?s=256`
},
[types.USER_SET_JWT_TOKEN] (state, token) {
ls.set('jwt-token', token)
state.jwtToken = token
},
[types.USER_DELETE_JWT_TOKEN] (state) {
ls.remove('jwt-token')
state.jwtToken = null
}
}
export default {
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,66 @@
export const GLOBAL_INIT_DATA = 'GLOBAL_INIT_DATA'
export const SONG_INIT_STORE = 'SONG_INIT_STORE'
export const SONG_SETUP = 'SONG_SETUP'
export const SONG_INCREASE_PLAY_COUNT = 'SONG_INCREASE_PLAY_COUNT'
export const SONG_ADD_RECENTLY_PLAYED = 'SONG_ADD_RECENTLY_PLAYED'
export const SET_INTERACTION_DATA = 'SET_INTERACTION_DATA'
export const ALBUM_INIT_STORE = 'ALBUM_INIT_STORE'
export const ALBUM_SETUP = 'ALBUM_SETUP'
export const ALBUM_ADD = 'ALBUM_ADD'
export const ALBUM_ADD_SONGS_INTO_ALBUM = 'ALBUM_ADD_SONGS_INTO_ALBUM'
export const ALBUM_REMOVE_SONGS_FROM_ALBUM = 'ALBUM_REMOVE_SONGS_FROM_ALBUM'
export const ALBUM_REMOVE = 'ALBUM_REMOVE'
export const ARTIST_INIT_STORE = 'ARTIST_INIT_STORE'
export const ARTIST_SETUP = 'ARTIST_SETUP'
export const ARTIST_ADD = 'ARTIST_ADD'
export const ARTIST_REMOVE = 'ARTIST_REMOVE'
export const ARTIST_ADD_ALBUMS_INTO_ARTIST = 'ARTIST_ADD_ALBUMS_INTO_ARTIST'
export const ARTIST_REMOVE_ALBUMS_FROM_ARTIST = 'ARTIST_REMOVE_ALBUMS_FROM_ARTIST'
export const EQUALIZER_SAVE = 'EQUALIZER_SAVE'
export const EQUALIZER_INIT = 'EQUALIZER_INIT'
export const FAVORITE_TOGGLE_ONE = 'FAVORITE_TOGGLE_ONE'
export const FAVORITE_ADD = 'FAVORITE_ADD'
export const FAVORITE_REMOVE = 'FAVORITE_REMOVE'
export const FAVORITE_CLEAR = 'FAVORITE_CLEAR'
export const FAVORITE_LIKE = 'FAVORITE_LIKE'
export const FAVORITE_UNLIKE = 'FAVORITE_UNLIKE'
export const PLAYLIST_INIT_STORE = 'PLAYLIST_INIT_STORE'
export const PLAYLIST_OBJECTIFY_SONGS = 'PLAYLIST_OBJECTIFY_SONGS'
export const PLAYLIST_ADD = 'PLAYLIST_ADD'
export const PLAYLIST_REMOVE = 'PLAYLIST_REMOVE'
export const PLAYLIST_STORE = 'PLAYLIST_STORE'
export const PLAYLIST_DELETE = 'PLAYLIST_DELETE'
export const PLAYLIST_ADD_SONGS = 'PLAYLIST_ADD_SONGS'
export const PLAYLIST_REMOVE_SONGS = 'PLAYLIST_REMOVE_SONGS'
export const PLAYLIST_UPDATE = 'PLAYLIST_UPDATE'
export const PREFERENCE_INIT_STORE = 'REFERENCE_INIT_STORE'
export const PREFERENCE_SAVE = 'PREFERENCE_SAVE'
export const QUEUE_QUEUE = 'QUEUE_QUEUE'
export const QUEUE_QUEUE_AFTER_CURRENT = 'QUEUE_QUEUE_AFTER_CURRENT'
export const QUEUE_UNQUEUE = 'QUEUE_UNQUEUE'
export const QUEUE_MOVE = 'QUEUE_MOVE'
export const QUEUE_CLEAR = 'QUEUE_CLEAR'
export const QUEUE_SET_CURRENT = 'QUEUE_SET_CURRENT'
export const QUEUE_SHUFFLE = 'QUEUE_SHUFFLE'
export const SETTING_INIT_STORE = 'SETTING_INIT_STORE'
export const SETTING_UPDATE = 'SETTING_UPDATE'
export const USER_INIT_STORE = 'USER_INIT_STORE'
export const USER_SET_CURRENT = 'USER_SET_CURRENT'
export const USER_SET_JWT_TOKEN = 'USER_SET_JWT_TOKEN'
export const USER_DELETE_JWT_TOKEN = 'USER_DELETE_JWT_TOKEN'
export const USER_SET_AVATAR = 'USER_SET_AVATAR'
export const USER_UPDATE_PROFILE = 'USER_UPDATE_PROFILE'
export const USER_ADD = 'USER_ADD'
export const USER_UPDATE = 'USER_UPDATE'
export const USER_DELETE = 'USER_DELETE'

View file

@ -0,0 +1,11 @@
import { assign } from 'lodash'
import * as types from './mutation-types'
const mutations = {
[types.GLOBAL_INIT_DATA] (state, data) {
// assign(state, data)
}
}
export default mutations

View file

@ -0,0 +1,22 @@
const state = {
songs: [],
albums: [],
artists: [],
favorites: [],
queued: [],
interactions: [],
users: [],
settings: [],
currentUser: null,
playlists: [],
useLastfm: false,
useYouTube: false,
useiTunes: false,
allowDownload: false,
currentVersion: '',
latestVersion: '',
cdnUrl: '',
originalMediaPath: ''
}
export default state

276
yarn.lock
View file

@ -361,6 +361,22 @@ babel-generator@^6.23.0:
source-map "^0.5.0"
trim-right "^1.0.1"
babel-helper-bindify-decorators@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.22.0.tgz#d7f5bc261275941ac62acfc4e20dacfb8a3fe952"
dependencies:
babel-runtime "^6.22.0"
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babel-helper-builder-binary-assignment-operator-visitor@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.22.0.tgz#29df56be144d81bdeac08262bfa41d2c5e91cdcd"
dependencies:
babel-helper-explode-assignable-expression "^6.22.0"
babel-runtime "^6.22.0"
babel-types "^6.22.0"
babel-helper-call-delegate@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.22.0.tgz#119921b56120f17e9dae3f74b4f5cc7bcc1b37ef"
@ -379,6 +395,23 @@ babel-helper-define-map@^6.22.0:
babel-types "^6.22.0"
lodash "^4.2.0"
babel-helper-explode-assignable-expression@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.22.0.tgz#c97bf76eed3e0bae4048121f2b9dae1a4e7d0478"
dependencies:
babel-runtime "^6.22.0"
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babel-helper-explode-class@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.22.0.tgz#646304924aa6388a516843ba7f1855ef8dfeb69b"
dependencies:
babel-helper-bindify-decorators "^6.22.0"
babel-runtime "^6.22.0"
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babel-helper-function-name@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.22.0.tgz#51f1bdc4bb89b15f57a9b249f33d742816dcbefc"
@ -389,6 +422,16 @@ babel-helper-function-name@^6.22.0:
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babel-helper-function-name@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.23.0.tgz#25742d67175c8903dbe4b6cb9d9e1fcb8dcf23a6"
dependencies:
babel-helper-get-function-arity "^6.22.0"
babel-runtime "^6.22.0"
babel-template "^6.23.0"
babel-traverse "^6.23.0"
babel-types "^6.23.0"
babel-helper-get-function-arity@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.22.0.tgz#0beb464ad69dc7347410ac6ade9f03a50634f5ce"
@ -418,6 +461,16 @@ babel-helper-regex@^6.22.0:
babel-types "^6.22.0"
lodash "^4.2.0"
babel-helper-remap-async-to-generator@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.22.0.tgz#2186ae73278ed03b8b15ced089609da981053383"
dependencies:
babel-helper-function-name "^6.22.0"
babel-runtime "^6.22.0"
babel-template "^6.22.0"
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babel-helper-replace-supers@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.22.0.tgz#1fcee2270657548908c34db16bcc345f9850cf42"
@ -457,6 +510,104 @@ babel-plugin-check-es2015-constants@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
babel-plugin-syntax-async-generators@^6.5.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
babel-plugin-syntax-class-constructor-call@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
babel-plugin-syntax-class-properties@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
babel-plugin-syntax-decorators@^6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
babel-plugin-syntax-do-expressions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d"
babel-plugin-syntax-dynamic-import@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
babel-plugin-syntax-exponentiation-operator@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
babel-plugin-syntax-export-extensions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
babel-plugin-syntax-function-bind@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
babel-plugin-syntax-object-rest-spread@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
babel-plugin-syntax-trailing-function-commas@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
babel-plugin-transform-async-generator-functions@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.22.0.tgz#a720a98153a7596f204099cd5409f4b3c05bab46"
dependencies:
babel-helper-remap-async-to-generator "^6.22.0"
babel-plugin-syntax-async-generators "^6.5.0"
babel-runtime "^6.22.0"
babel-plugin-transform-async-to-generator@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.22.0.tgz#194b6938ec195ad36efc4c33a971acf00d8cd35e"
dependencies:
babel-helper-remap-async-to-generator "^6.22.0"
babel-plugin-syntax-async-functions "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-class-constructor-call@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.22.0.tgz#11a4d2216abb5b0eef298b493748f4f2f4869120"
dependencies:
babel-plugin-syntax-class-constructor-call "^6.18.0"
babel-runtime "^6.22.0"
babel-template "^6.22.0"
babel-plugin-transform-class-properties@^6.22.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.23.0.tgz#187b747ee404399013563c993db038f34754ac3b"
dependencies:
babel-helper-function-name "^6.23.0"
babel-plugin-syntax-class-properties "^6.8.0"
babel-runtime "^6.22.0"
babel-template "^6.23.0"
babel-plugin-transform-decorators@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.22.0.tgz#c03635b27a23b23b7224f49232c237a73988d27c"
dependencies:
babel-helper-explode-class "^6.22.0"
babel-plugin-syntax-decorators "^6.13.0"
babel-runtime "^6.22.0"
babel-template "^6.22.0"
babel-types "^6.22.0"
babel-plugin-transform-do-expressions@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb"
dependencies:
babel-plugin-syntax-do-expressions "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-es2015-arrow-functions@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
@ -625,6 +776,35 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0:
babel-runtime "^6.22.0"
regexpu-core "^2.0.0"
babel-plugin-transform-exponentiation-operator@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.22.0.tgz#d57c8335281918e54ef053118ce6eb108468084d"
dependencies:
babel-helper-builder-binary-assignment-operator-visitor "^6.22.0"
babel-plugin-syntax-exponentiation-operator "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-export-extensions@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
dependencies:
babel-plugin-syntax-export-extensions "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-function-bind@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97"
dependencies:
babel-plugin-syntax-function-bind "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-object-rest-spread@^6.22.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
dependencies:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-regenerator@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.22.0.tgz#65740593a319c44522157538d690b84094617ea6"
@ -681,6 +861,41 @@ babel-preset-es2015@^6.18.0:
babel-plugin-transform-es2015-unicode-regex "^6.22.0"
babel-plugin-transform-regenerator "^6.22.0"
babel-preset-stage-0@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.22.0.tgz#707eeb5b415da769eff9c42f4547f644f9296ef9"
dependencies:
babel-plugin-transform-do-expressions "^6.22.0"
babel-plugin-transform-function-bind "^6.22.0"
babel-preset-stage-1 "^6.22.0"
babel-preset-stage-1@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.22.0.tgz#7da05bffea6ad5a10aef93e320cfc6dd465dbc1a"
dependencies:
babel-plugin-transform-class-constructor-call "^6.22.0"
babel-plugin-transform-export-extensions "^6.22.0"
babel-preset-stage-2 "^6.22.0"
babel-preset-stage-2@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.22.0.tgz#ccd565f19c245cade394b21216df704a73b27c07"
dependencies:
babel-plugin-syntax-dynamic-import "^6.18.0"
babel-plugin-transform-class-properties "^6.22.0"
babel-plugin-transform-decorators "^6.22.0"
babel-preset-stage-3 "^6.22.0"
babel-preset-stage-3@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.22.0.tgz#a4e92bbace7456fafdf651d7a7657ee0bbca9c2e"
dependencies:
babel-plugin-syntax-trailing-function-commas "^6.22.0"
babel-plugin-transform-async-generator-functions "^6.22.0"
babel-plugin-transform-async-to-generator "^6.22.0"
babel-plugin-transform-exponentiation-operator "^6.22.0"
babel-plugin-transform-object-rest-spread "^6.22.0"
babel-register@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.23.0.tgz#c9aa3d4cca94b51da34826c4a0f9e08145d74ff3"
@ -1124,9 +1339,9 @@ circular-json@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
clean-css@4.0.x:
version "4.0.6"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.6.tgz#992cf37c1064dafbca7f42b522837b411b87cab5"
clean-css@4.0.x, clean-css@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.7.tgz#d8fa8b4d87a125f38fa3d64afc59abfc68ba7790"
dependencies:
source-map "0.5.x"
@ -1137,12 +1352,6 @@ clean-css@^3.1.9:
commander "2.8.x"
source-map "0.4.x"
clean-css@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.7.tgz#d8fa8b4d87a125f38fa3d64afc59abfc68ba7790"
dependencies:
source-map "0.5.x"
cli-cursor@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
@ -1453,19 +1662,28 @@ create-hmac@^1.1.0, create-hmac@^1.1.2:
create-hash "^1.1.0"
inherits "^2.0.1"
cross-env@^3.1.3, cross-env@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.1.4.tgz#56e8bca96f17908a6eb1bc2012ca126f92842130"
cross-env@^3.1.3, cross-env@^3.2.3:
version "3.2.4"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.2.4.tgz#9e0585f277864ed421ce756f81a980ff0d698aba"
dependencies:
cross-spawn "^3.0.1"
cross-spawn "^5.1.0"
is-windows "^1.0.0"
cross-spawn@^3.0.0, cross-spawn@^3.0.1:
cross-spawn@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
dependencies:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
@ -3030,6 +3248,10 @@ is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
is-windows@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.0.tgz#c61d61020c3ebe99261b781bd3d1622395f547f8"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -4275,18 +4497,18 @@ postcss-value-parser@^3.2.3:
version "3.3.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
postcss@^5.0.10, postcss@^5.2.11, postcss@^5.2.12:
version "5.2.12"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.12.tgz#6a2b15e35dd65634441bb0961fa796904c7890e0"
postcss@^5.0.10, postcss@^5.2.12, postcss@^5.2.13:
version "5.2.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.13.tgz#1be52a32cf2ef58c0d75f1aedb3beabcf257cef3"
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
source-map "^0.5.6"
supports-color "^3.2.3"
postcss@^5.2.13:
version "5.2.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.13.tgz#1be52a32cf2ef58c0d75f1aedb3beabcf257cef3"
postcss@^5.2.11:
version "5.2.12"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.12.tgz#6a2b15e35dd65634441bb0961fa796904c7890e0"
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
@ -4855,6 +5077,16 @@ sha.js@^2.3.6:
dependencies:
inherits "^2.0.1"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
dependencies:
shebang-regex "^1.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
shelljs@^0.7.5:
version "0.7.6"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad"
@ -5692,6 +5924,10 @@ vuequery@^1.0.0:
dependencies:
vue "^2.1.6"
vuex@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-2.2.1.tgz#a42d0ce18cb0e0359258f84bfd76835ed468c185"
watchpack@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.2.0.tgz#15d4620f1e7471f13fcb551d5c030d2c3eb42dbb"