mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
StyleJS fixes
This commit is contained in:
parent
c5b0ba98e8
commit
59eaa7923a
62 changed files with 2096 additions and 1662 deletions
|
@ -1,10 +1,10 @@
|
|||
import './static-loader'
|
||||
import Vue from 'vue'
|
||||
import App from './app.vue'
|
||||
import { event } from './utils'
|
||||
import { http } from './services'
|
||||
import { VirtualScroller } from 'vue-virtual-scroller/dist/vue-virtual-scroller'
|
||||
Vue.component('virtual-scroller', VirtualScroller)
|
||||
import "./static-loader";
|
||||
import Vue from "vue";
|
||||
import App from "./app.vue";
|
||||
import { event } from "./utils";
|
||||
import { http } from "./services";
|
||||
import { VirtualScroller } from "vue-virtual-scroller/dist/vue-virtual-scroller";
|
||||
Vue.component("virtual-scroller", VirtualScroller);
|
||||
|
||||
/**
|
||||
* For Ancelot, the ancient cross of war
|
||||
|
@ -13,10 +13,10 @@ Vue.component('virtual-scroller', VirtualScroller)
|
|||
* in this dawn of victory
|
||||
*/
|
||||
new Vue({
|
||||
el: '#app',
|
||||
el: "#app",
|
||||
render: h => h(App),
|
||||
created () {
|
||||
event.init()
|
||||
http.init()
|
||||
created() {
|
||||
event.init();
|
||||
http.init();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export default {
|
||||
unknownCover: (typeof window !== 'undefined' ? window.location.href.replace(window.location.hash, '') : '/') + 'public/img/covers/unknown-album.png',
|
||||
appTitle: 'Koel'
|
||||
}
|
||||
unknownCover:
|
||||
(typeof window !== "undefined"
|
||||
? window.location.href.replace(window.location.hash, "")
|
||||
: "/") + "public/img/covers/unknown-album.png",
|
||||
appTitle: "Koel"
|
||||
};
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
* @type {Object}
|
||||
*/
|
||||
export const clickawayDirective = {
|
||||
bind (el, { value }) {
|
||||
if (typeof value !== 'function') {
|
||||
console.warn(`Expect a function, got ${value}`)
|
||||
return
|
||||
bind(el, { value }) {
|
||||
if (typeof value !== "function") {
|
||||
console.warn(`Expect a function, got ${value}`);
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
el.contains(e.target) || value()
|
||||
})
|
||||
document.addEventListener("click", e => {
|
||||
el.contains(e.target) || value();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* 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,3 +1,143 @@
|
|||
/*! modernizr 3.2.0 (Custom Build) | MIT *
|
||||
* http://modernizr.com/download/?-touchevents-setclasses !*/
|
||||
!function(e,n,t){function o(e,n){return typeof e===n}function s(){var e,n,t,s,a,i,r;for(var l in c)if(c.hasOwnProperty(l)){if(e=[],n=c[l],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t<n.options.aliases.length;t++)e.push(n.options.aliases[t].toLowerCase());for(s=o(n.fn,"function")?n.fn():n.fn,a=0;a<e.length;a++)i=e[a],r=i.split("."),1===r.length?Modernizr[r[0]]=s:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=s),f.push((s?"":"no-")+r.join("-"))}}function a(e){var n=u.className,t=Modernizr._config.classPrefix||"";if(p&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+t+"no-js(\\s|$)");n=n.replace(o,"$1"+t+"js$2")}Modernizr._config.enableClasses&&(n+=" "+t+e.join(" "+t),p?u.className.baseVal=n:u.className=n)}function i(){return"function"!=typeof n.createElement?n.createElement(arguments[0]):p?n.createElementNS.call(n,"http://www.w3.org/2000/svg",arguments[0]):n.createElement.apply(n,arguments)}function r(){var e=n.body;return e||(e=i(p?"svg":"body"),e.fake=!0),e}function l(e,t,o,s){var a,l,f,c,d="modernizr",p=i("div"),h=r();if(parseInt(o,10))for(;o--;)f=i("div"),f.id=s?s[o]:d+(o+1),p.appendChild(f);return a=i("style"),a.type="text/css",a.id="s"+d,(h.fake?h:p).appendChild(a),h.appendChild(p),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(n.createTextNode(e)),p.id=d,h.fake&&(h.style.background="",h.style.overflow="hidden",c=u.style.overflow,u.style.overflow="hidden",u.appendChild(h)),l=t(p,e),h.fake?(h.parentNode.removeChild(h),u.style.overflow=c,u.offsetHeight):p.parentNode.removeChild(p),!!l}var f=[],c=[],d={_version:"3.2.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){c.push({name:e,fn:n,options:t})},addAsyncTest:function(e){c.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=d,Modernizr=new Modernizr;var u=n.documentElement,p="svg"===u.nodeName.toLowerCase(),h=d._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):[];d._prefixes=h;var m=d.testStyles=l;Modernizr.addTest("touchevents",function(){var t;if("ontouchstart"in e||e.DocumentTouch&&n instanceof DocumentTouch)t=!0;else{var o=["@media (",h.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");m(o,function(e){t=9===e.offsetTop})}return t}),s(),a(f),delete d.addTest,delete d.addAsyncTest;for(var v=0;v<Modernizr._q.length;v++)Modernizr._q[v]();e.Modernizr=Modernizr}(window,document);
|
||||
!(function(e, n, t) {
|
||||
function o(e, n) {
|
||||
return typeof e === n;
|
||||
}
|
||||
function s() {
|
||||
var e, n, t, s, a, i, r;
|
||||
for (var l in c)
|
||||
if (c.hasOwnProperty(l)) {
|
||||
if (
|
||||
((e = []),
|
||||
(n = c[l]),
|
||||
n.name &&
|
||||
(e.push(n.name.toLowerCase()),
|
||||
n.options && n.options.aliases && n.options.aliases.length))
|
||||
)
|
||||
for (t = 0; t < n.options.aliases.length; t++)
|
||||
e.push(n.options.aliases[t].toLowerCase());
|
||||
for (s = o(n.fn, "function") ? n.fn() : n.fn, a = 0; a < e.length; a++)
|
||||
(i = e[a]),
|
||||
(r = i.split(".")),
|
||||
1 === r.length
|
||||
? (Modernizr[r[0]] = s)
|
||||
: (!Modernizr[r[0]] ||
|
||||
Modernizr[r[0]] instanceof Boolean ||
|
||||
(Modernizr[r[0]] = new Boolean(Modernizr[r[0]])),
|
||||
(Modernizr[r[0]][r[1]] = s)),
|
||||
f.push((s ? "" : "no-") + r.join("-"));
|
||||
}
|
||||
}
|
||||
function a(e) {
|
||||
var n = u.className,
|
||||
t = Modernizr._config.classPrefix || "";
|
||||
if ((p && (n = n.baseVal), Modernizr._config.enableJSClass)) {
|
||||
var o = new RegExp("(^|\\s)" + t + "no-js(\\s|$)");
|
||||
n = n.replace(o, "$1" + t + "js$2");
|
||||
}
|
||||
Modernizr._config.enableClasses &&
|
||||
((n += " " + t + e.join(" " + t)),
|
||||
p ? (u.className.baseVal = n) : (u.className = n));
|
||||
}
|
||||
function i() {
|
||||
return "function" != typeof n.createElement
|
||||
? n.createElement(arguments[0])
|
||||
: p
|
||||
? n.createElementNS.call(n, "http://www.w3.org/2000/svg", arguments[0])
|
||||
: n.createElement.apply(n, arguments);
|
||||
}
|
||||
function r() {
|
||||
var e = n.body;
|
||||
return e || ((e = i(p ? "svg" : "body")), (e.fake = !0)), e;
|
||||
}
|
||||
function l(e, t, o, s) {
|
||||
var a,
|
||||
l,
|
||||
f,
|
||||
c,
|
||||
d = "modernizr",
|
||||
p = i("div"),
|
||||
h = r();
|
||||
if (parseInt(o, 10))
|
||||
for (; o--; )
|
||||
(f = i("div")), (f.id = s ? s[o] : d + (o + 1)), p.appendChild(f);
|
||||
return (
|
||||
(a = i("style")),
|
||||
(a.type = "text/css"),
|
||||
(a.id = "s" + d),
|
||||
(h.fake ? h : p).appendChild(a),
|
||||
h.appendChild(p),
|
||||
a.styleSheet
|
||||
? (a.styleSheet.cssText = e)
|
||||
: a.appendChild(n.createTextNode(e)),
|
||||
(p.id = d),
|
||||
h.fake &&
|
||||
((h.style.background = ""),
|
||||
(h.style.overflow = "hidden"),
|
||||
(c = u.style.overflow),
|
||||
(u.style.overflow = "hidden"),
|
||||
u.appendChild(h)),
|
||||
(l = t(p, e)),
|
||||
h.fake
|
||||
? (h.parentNode.removeChild(h), (u.style.overflow = c), u.offsetHeight)
|
||||
: p.parentNode.removeChild(p),
|
||||
!!l
|
||||
);
|
||||
}
|
||||
var f = [],
|
||||
c = [],
|
||||
d = {
|
||||
_version: "3.2.0",
|
||||
_config: {
|
||||
classPrefix: "",
|
||||
enableClasses: !0,
|
||||
enableJSClass: !0,
|
||||
usePrefixes: !0
|
||||
},
|
||||
_q: [],
|
||||
on: function(e, n) {
|
||||
var t = this;
|
||||
setTimeout(function() {
|
||||
n(t[e]);
|
||||
}, 0);
|
||||
},
|
||||
addTest: function(e, n, t) {
|
||||
c.push({ name: e, fn: n, options: t });
|
||||
},
|
||||
addAsyncTest: function(e) {
|
||||
c.push({ name: null, fn: e });
|
||||
}
|
||||
},
|
||||
Modernizr = function() {};
|
||||
(Modernizr.prototype = d), (Modernizr = new Modernizr());
|
||||
var u = n.documentElement,
|
||||
p = "svg" === u.nodeName.toLowerCase(),
|
||||
h = d._config.usePrefixes ? " -webkit- -moz- -o- -ms- ".split(" ") : [];
|
||||
d._prefixes = h;
|
||||
var m = (d.testStyles = l);
|
||||
Modernizr.addTest("touchevents", function() {
|
||||
var t;
|
||||
if ("ontouchstart" in e || (e.DocumentTouch && n instanceof DocumentTouch))
|
||||
t = !0;
|
||||
else {
|
||||
var o = [
|
||||
"@media (",
|
||||
h.join("touch-enabled),("),
|
||||
"heartz",
|
||||
")",
|
||||
"{#modernizr{top:9px;position:absolute}}"
|
||||
].join("");
|
||||
m(o, function(e) {
|
||||
t = 9 === e.offsetTop;
|
||||
});
|
||||
}
|
||||
return t;
|
||||
}),
|
||||
s(),
|
||||
a(f),
|
||||
delete d.addTest,
|
||||
delete d.addAsyncTest;
|
||||
for (var v = 0; v < Modernizr._q.length; v++) Modernizr._q[v]();
|
||||
e.Modernizr = Modernizr;
|
||||
})(window, document);
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { secondsToHis } from '@/utils'
|
||||
import { secondsToHis } from "@/utils";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
length () {
|
||||
length() {
|
||||
return this.album.songs.reduce((acc, song) => {
|
||||
return acc + song.length
|
||||
}, 0)
|
||||
return acc + song.length;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
fmtLength () {
|
||||
return secondsToHis(this.length)
|
||||
fmtLength() {
|
||||
return secondsToHis(this.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
import { secondsToHis } from '@/utils'
|
||||
import config from '@/config'
|
||||
import { secondsToHis } from "@/utils";
|
||||
import config from "@/config";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
length () {
|
||||
length() {
|
||||
return this.artist.songs.reduce((acc, song) => {
|
||||
return acc + song.length
|
||||
}, 0)
|
||||
return acc + song.length;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
fmtLength () {
|
||||
return secondsToHis(this.length)
|
||||
fmtLength() {
|
||||
return secondsToHis(this.length);
|
||||
},
|
||||
|
||||
image () {
|
||||
image() {
|
||||
if (!this.artist.image) {
|
||||
this.artist.image = config.unknownCover
|
||||
this.artist.image = config.unknownCover;
|
||||
|
||||
this.artist.albums.every(album => {
|
||||
// If there's a "real" cover, use it.
|
||||
if (album.image !== config.unknownCover) {
|
||||
this.artist.image = album.cover
|
||||
this.artist.image = album.cover;
|
||||
// I want to break free.
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return this.artist.image
|
||||
return this.artist.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,50 +2,50 @@
|
|||
* Add necessary functionalities into a view that contains a song-list component.
|
||||
*/
|
||||
|
||||
import { assignIn } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { assignIn } 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
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
setSelectedSongs (songs) {
|
||||
this.selectedSongs = songs
|
||||
setSelectedSongs(songs) {
|
||||
this.selectedSongs = songs;
|
||||
},
|
||||
|
||||
updateMeta (meta) {
|
||||
assignIn(this.meta, meta)
|
||||
updateMeta(meta) {
|
||||
assignIn(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,4 +1,4 @@
|
|||
import toTopButton from '@/components/shared/to-top-button.vue'
|
||||
import toTopButton from "@/components/shared/to-top-button.vue";
|
||||
|
||||
/**
|
||||
* Add a "infinite scroll" functionality to any component using this mixin.
|
||||
|
@ -8,27 +8,27 @@ 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"
|
||||
}
|
||||
perPage: 30 // Number of items to be loaded per "page"
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrolling ({ target: { scrollTop, clientHeight, scrollHeight }}) {
|
||||
scrolling({ target: { scrollTop, clientHeight, scrollHeight } }) {
|
||||
// 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 (scrollTop + clientHeight >= scrollHeight - 32) {
|
||||
this.displayMore()
|
||||
this.displayMore();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load and display more items into the scrollable area.
|
||||
*/
|
||||
displayMore () {
|
||||
this.numOfItems += this.perPage
|
||||
displayMore() {
|
||||
this.numOfItems += this.perPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { each } from 'lodash'
|
||||
import { each } from "lodash";
|
||||
|
||||
import { queueStore, playlistStore, favoriteStore } from '@/stores'
|
||||
import { queueStore, playlistStore, favoriteStore } from "@/stores";
|
||||
|
||||
/**
|
||||
* Includes the methods triggerable on a song (context) menu.
|
||||
|
@ -9,57 +9,57 @@ import { queueStore, playlistStore, favoriteStore } from '@/stores'
|
|||
* for example close() and open().
|
||||
*/
|
||||
export default {
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
shown: false,
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
open () {},
|
||||
open() {},
|
||||
|
||||
/**
|
||||
* Close all submenus.
|
||||
*/
|
||||
close () {
|
||||
each(Array.from(this.$el.querySelectorAll('.submenu')), el => {
|
||||
el.style.display = 'none'
|
||||
})
|
||||
this.shown = false
|
||||
close() {
|
||||
each(Array.from(this.$el.querySelectorAll(".submenu")), el => {
|
||||
el.style.display = "none";
|
||||
});
|
||||
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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -67,9 +67,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,12 +1,12 @@
|
|||
import './static-loader'
|
||||
import Vue from 'vue'
|
||||
import { http } from '@/services'
|
||||
import App from './app.vue'
|
||||
import "./static-loader";
|
||||
import Vue from "vue";
|
||||
import { http } from "@/services";
|
||||
import App from "./app.vue";
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
el: "#app",
|
||||
render: h => h(App),
|
||||
created () {
|
||||
http.init()
|
||||
created() {
|
||||
http.init();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'babel-polyfill/dist/polyfill.min.js'
|
||||
import '@/libs/modernizr-custom.js'
|
||||
import '@/../css/meyer-reset.min.css'
|
||||
import 'nouislider/distribute/nouislider.min.css'
|
||||
import 'font-awesome/css/font-awesome.min.css'
|
||||
import "babel-polyfill/dist/polyfill.min.js";
|
||||
import "@/libs/modernizr-custom.js";
|
||||
import "@/../css/meyer-reset.min.css";
|
||||
import "nouislider/distribute/nouislider.min.css";
|
||||
import "font-awesome/css/font-awesome.min.css";
|
||||
|
|
|
@ -1,109 +1,114 @@
|
|||
import isMobile from 'ismobilejs'
|
||||
import { each } from 'lodash'
|
||||
import isMobile from "ismobilejs";
|
||||
import { each } from "lodash";
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
each(Object.keys(this.routes), 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;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -111,15 +116,15 @@ export default {
|
|||
*
|
||||
* @param {String} path
|
||||
*/
|
||||
go (path) {
|
||||
if (path[0] !== '/') {
|
||||
path = `/${path}`
|
||||
go(path) {
|
||||
if (path[0] !== "/") {
|
||||
path = `/${path}`;
|
||||
}
|
||||
|
||||
if (path.indexOf('/#!') !== 0) {
|
||||
path = `/#!${path}`
|
||||
if (path.indexOf("/#!") !== 0) {
|
||||
path = `/#!${path}`;
|
||||
}
|
||||
|
||||
document.location.href = `${document.location.origin}${path}`
|
||||
document.location.href = `${document.location.origin}${path}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reduce } from 'lodash'
|
||||
import { reduce } from "lodash";
|
||||
|
||||
import { playlistStore, favoriteStore } from '@/stores'
|
||||
import { ls } from '.'
|
||||
import { playlistStore, favoriteStore } from "@/stores";
|
||||
import { ls } from ".";
|
||||
|
||||
export const download = {
|
||||
/**
|
||||
|
@ -9,10 +9,10 @@ export const download = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
fromSongs (songs) {
|
||||
songs = [].concat(songs)
|
||||
const query = reduce(songs, (q, song) => `songs[]=${song.id}&${q}`, '')
|
||||
return this.trigger(`songs?${query}`)
|
||||
fromSongs(songs) {
|
||||
songs = [].concat(songs);
|
||||
const query = reduce(songs, (q, song) => `songs[]=${song.id}&${q}`, "");
|
||||
return this.trigger(`songs?${query}`);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -20,8 +20,8 @@ export const download = {
|
|||
*
|
||||
* @param {Object} album
|
||||
*/
|
||||
fromAlbum (album) {
|
||||
return this.trigger(`album/${album.id}`)
|
||||
fromAlbum(album) {
|
||||
return this.trigger(`album/${album.id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -29,11 +29,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}`);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -41,24 +41,24 @@ export const download = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
fromPlaylist (playlist) {
|
||||
fromPlaylist(playlist) {
|
||||
if (!playlistStore.getSongs(playlist).length) {
|
||||
return
|
||||
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");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -67,11 +67,14 @@ 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 iframe = document.createElement('iframe')
|
||||
iframe.style.display = 'none'
|
||||
iframe.setAttribute('src', `/api/download/${uri}${sep}jwt-token=${ls.get('jwt-token')}`)
|
||||
document.body.appendChild(iframe)
|
||||
trigger(uri) {
|
||||
const sep = uri.indexOf("?") === -1 ? "?" : "&";
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.display = "none";
|
||||
iframe.setAttribute(
|
||||
"src",
|
||||
`/api/download/${uri}${sep}jwt-token=${ls.get("jwt-token")}`
|
||||
);
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,72 +1,84 @@
|
|||
import axios from 'axios'
|
||||
import NProgress from 'nprogress'
|
||||
import axios from "axios";
|
||||
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) {
|
||||
axios.request({
|
||||
url,
|
||||
data,
|
||||
method: method.toLowerCase()
|
||||
}).then(successCb).catch(errorCb)
|
||||
request(method, url, data, successCb = null, errorCb = null) {
|
||||
axios
|
||||
.request({
|
||||
url,
|
||||
data,
|
||||
method: method.toLowerCase()
|
||||
})
|
||||
.then(successCb)
|
||||
.catch(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 () {
|
||||
axios.defaults.baseURL = '/api'
|
||||
init() {
|
||||
axios.defaults.baseURL = "/api";
|
||||
|
||||
// Intercept the request to make sure the token is injected into the header.
|
||||
axios.interceptors.request.use(config => {
|
||||
config.headers.Authorization = `Bearer ${ls.get('jwt-token')}`
|
||||
return config
|
||||
})
|
||||
config.headers.Authorization = `Bearer ${ls.get("jwt-token")}`;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Intercept the response and…
|
||||
axios.interceptors.response.use(response => {
|
||||
NProgress.done()
|
||||
axios.interceptors.response.use(
|
||||
response => {
|
||||
NProgress.done();
|
||||
|
||||
// …get the token from the header or response data if exists, and save it.
|
||||
const token = response.headers['Authorization'] || response.data['token']
|
||||
if (token) {
|
||||
ls.set('jwt-token', token)
|
||||
}
|
||||
|
||||
return response
|
||||
}, error => {
|
||||
NProgress.done()
|
||||
// Also, if we receive a Bad Request / Unauthorized error
|
||||
if (error.response.status === 400 || error.response.status === 401) {
|
||||
// and we're not trying to login
|
||||
if (!(error.config.method === 'post' && /\/api\/me\/?$/.test(error.config.url))) {
|
||||
// the token must have expired. Log out.
|
||||
event.emit('logout')
|
||||
// …get the token from the header or response data if exists, and save it.
|
||||
const token =
|
||||
response.headers["Authorization"] || response.data["token"];
|
||||
if (token) {
|
||||
ls.set("jwt-token", token);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
})
|
||||
return response;
|
||||
},
|
||||
error => {
|
||||
NProgress.done();
|
||||
// Also, if we receive a Bad Request / Unauthorized error
|
||||
if (error.response.status === 400 || error.response.status === 401) {
|
||||
// and we're not trying to login
|
||||
if (
|
||||
!(
|
||||
error.config.method === "post" &&
|
||||
/\/api\/me\/?$/.test(error.config.url)
|
||||
)
|
||||
) {
|
||||
// the token must have expired. Log out.
|
||||
event.emit("logout");
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export * from './info'
|
||||
export * from './download'
|
||||
export * from './http'
|
||||
export * from './ls'
|
||||
export * from './playback'
|
||||
export * from './youtube'
|
||||
export * from './socket'
|
||||
export * from "./info";
|
||||
export * from "./download";
|
||||
export * from "./http";
|
||||
export * from "./ls";
|
||||
export * from "./playback";
|
||||
export * from "./youtube";
|
||||
export * from "./socket";
|
||||
|
|
|
@ -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,22 @@ 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)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.get(
|
||||
`album/${album.id}/info`,
|
||||
({ data }) => {
|
||||
data && this.merge(album, data);
|
||||
resolve(album);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -29,22 +33,23 @@ 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
|
||||
if (typeof info.image !== "string") {
|
||||
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,22 @@ 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)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.get(
|
||||
`artist/${artist.id}/info`,
|
||||
({ data }) => {
|
||||
data && this.merge(artist, data);
|
||||
resolve(artist);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -26,17 +30,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
|
||||
if (typeof info.image !== "string") {
|
||||
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,6 +1,6 @@
|
|||
/*eslint-disable camelcase*/
|
||||
|
||||
import { http, albumInfo, artistInfo } from '..'
|
||||
import { http, albumInfo, artistInfo } from "..";
|
||||
|
||||
export const songInfo = {
|
||||
/**
|
||||
|
@ -8,22 +8,26 @@ 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: { artist_info, album_info, youtube, lyrics }}) => {
|
||||
song.lyrics = lyrics
|
||||
artist_info && artistInfo.merge(song.artist, artist_info)
|
||||
album_info && albumInfo.merge(song.album, album_info)
|
||||
song.youtube = youtube
|
||||
song.infoRetrieved = true
|
||||
resolve(song)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.get(
|
||||
`${song.id}/info`,
|
||||
({ data: { artist_info, album_info, youtube, lyrics } }) => {
|
||||
song.lyrics = lyrics;
|
||||
artist_info && artistInfo.merge(song.artist, artist_info);
|
||||
album_info && albumInfo.merge(song.album, album_info);
|
||||
song.youtube = youtube;
|
||||
song.infoRetrieved = true;
|
||||
resolve(song);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import localStore from 'local-storage'
|
||||
import localStore from "local-storage";
|
||||
|
||||
export const ls = {
|
||||
get (key, defaultVal = null) {
|
||||
return localStore(key) || defaultVal
|
||||
get(key, defaultVal = null) {
|
||||
return localStore(key) || 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,118 +1,140 @@
|
|||
import { shuffle, orderBy } from 'lodash'
|
||||
import plyr from 'plyr'
|
||||
import Vue from 'vue'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { shuffle, orderBy } from "lodash";
|
||||
import plyr from "plyr";
|
||||
import Vue from "vue";
|
||||
import isMobile from "ismobilejs";
|
||||
|
||||
import { event, isMediaSessionSupported } from '@/utils'
|
||||
import { queueStore, sharedStore, userStore, songStore, preferenceStore as preferences } from '@/stores'
|
||||
import { socket } from '@/services'
|
||||
import config from '@/config'
|
||||
import router from '@/router'
|
||||
import { event, isMediaSessionSupported } from "@/utils";
|
||||
import {
|
||||
queueStore,
|
||||
sharedStore,
|
||||
userStore,
|
||||
songStore,
|
||||
preferenceStore as preferences
|
||||
} from "@/stores";
|
||||
import { socket } from "@/services";
|
||||
import config from "@/config";
|
||||
import router from "@/router";
|
||||
|
||||
export const playback = {
|
||||
player: null,
|
||||
volumeInput: null,
|
||||
repeatModes: ['NO_REPEAT', 'REPEAT_ALL', 'REPEAT_ONE'],
|
||||
repeatModes: ["NO_REPEAT", "REPEAT_ALL", "REPEAT_ONE"],
|
||||
initialized: false,
|
||||
|
||||
/**
|
||||
* 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]
|
||||
})[0];
|
||||
|
||||
this.audio = document.querySelector('audio')
|
||||
this.volumeInput = document.getElementById('volumeRange')
|
||||
this.audio = document.querySelector("audio");
|
||||
this.volumeInput = document.getElementById("volumeRange");
|
||||
|
||||
const player = document.querySelector('.plyr')
|
||||
const player = document.querySelector(".plyr");
|
||||
|
||||
/**
|
||||
* Listen to 'error' event on the audio player and play the next song if any.
|
||||
*/
|
||||
player.addEventListener('error', () => this.playNext(), true)
|
||||
player.addEventListener("error", () => this.playNext(), true);
|
||||
|
||||
/**
|
||||
* Listen to 'ended' event on the audio player and play the next song in the queue.
|
||||
*/
|
||||
player.addEventListener('ended', e => {
|
||||
if (sharedStore.state.useLastfm && userStore.current.preferences.lastfm_session_key) {
|
||||
songStore.scrobble(queueStore.current)
|
||||
player.addEventListener("ended", e => {
|
||||
if (
|
||||
sharedStore.state.useLastfm &&
|
||||
userStore.current.preferences.lastfm_session_key
|
||||
) {
|
||||
songStore.scrobble(queueStore.current);
|
||||
}
|
||||
|
||||
preferences.repeatMode === 'REPEAT_ONE' ? this.restart() : this.playNext()
|
||||
})
|
||||
preferences.repeatMode === "REPEAT_ONE"
|
||||
? this.restart()
|
||||
: this.playNext();
|
||||
});
|
||||
|
||||
/**
|
||||
* Attempt to preload the next song.
|
||||
*/
|
||||
player.addEventListener('canplaythrough', e => {
|
||||
const nextSong = queueStore.next
|
||||
if (!nextSong || nextSong.preloaded || (isMobile.any && preferences.transcodeOnMobile)) {
|
||||
player.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
|
||||
return;
|
||||
}
|
||||
|
||||
const audio = document.createElement('audio')
|
||||
audio.setAttribute('src', songStore.getSourceUrl(nextSong))
|
||||
audio.setAttribute('preload', 'auto')
|
||||
audio.load()
|
||||
nextSong.preloaded = true
|
||||
})
|
||||
const audio = document.createElement("audio");
|
||||
audio.setAttribute("src", songStore.getSourceUrl(nextSong));
|
||||
audio.setAttribute("preload", "auto");
|
||||
audio.load();
|
||||
nextSong.preloaded = true;
|
||||
});
|
||||
|
||||
player.addEventListener('timeupdate', e => {
|
||||
const song = queueStore.current
|
||||
player.addEventListener("timeupdate", e => {
|
||||
const song = queueStore.current;
|
||||
|
||||
if (this.player.media.currentTime > 10 && !song.registeredPlayCount) {
|
||||
// After 10 seconds, register a play count and add it into "recently played" list
|
||||
songStore.addRecentlyPlayed(song)
|
||||
songStore.registerPlay(song)
|
||||
songStore.addRecentlyPlayed(song);
|
||||
songStore.registerPlay(song);
|
||||
|
||||
song.registeredPlayCount = true
|
||||
song.registeredPlayCount = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
if (isMediaSessionSupported()) {
|
||||
navigator.mediaSession.setActionHandler('play', () => this.resume())
|
||||
navigator.mediaSession.setActionHandler('pause', () => this.pause())
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => this.playPrev())
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => this.playNext())
|
||||
navigator.mediaSession.setActionHandler("play", () => this.resume());
|
||||
navigator.mediaSession.setActionHandler("pause", () => this.pause());
|
||||
navigator.mediaSession.setActionHandler("previoustrack", () =>
|
||||
this.playPrev()
|
||||
);
|
||||
navigator.mediaSession.setActionHandler("nexttrack", () =>
|
||||
this.playNext()
|
||||
);
|
||||
}
|
||||
|
||||
socket.listen('playback:toggle', () => this.toggle())
|
||||
.listen('playback:next', () => this.playNext())
|
||||
.listen('playback:prev', () => this.playPrev())
|
||||
.listen('status:get', () => {
|
||||
const data = queueStore.current ? songStore.generateDataToBroadcast(queueStore.current) : {}
|
||||
data.volume = this.volumeInput.value
|
||||
socket.broadcast('status', data)
|
||||
socket
|
||||
.listen("playback:toggle", () => this.toggle())
|
||||
.listen("playback:next", () => this.playNext())
|
||||
.listen("playback:prev", () => this.playPrev())
|
||||
.listen("status:get", () => {
|
||||
const data = queueStore.current
|
||||
? songStore.generateDataToBroadcast(queueStore.current)
|
||||
: {};
|
||||
data.volume = this.volumeInput.value;
|
||||
socket.broadcast("status", data);
|
||||
})
|
||||
.listen('song:getcurrent', () => {
|
||||
.listen("song:getcurrent", () => {
|
||||
socket.broadcast(
|
||||
'song',
|
||||
"song",
|
||||
queueStore.current
|
||||
? songStore.generateDataToBroadcast(queueStore.current)
|
||||
: { song: null }
|
||||
)
|
||||
);
|
||||
})
|
||||
.listen('volume:set', ({ volume }) => this.setVolume(volume))
|
||||
.listen("volume:set", ({ volume }) => this.setVolume(volume));
|
||||
|
||||
this.initialized = true
|
||||
this.initialized = true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -125,29 +147,31 @@ 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;
|
||||
|
||||
// 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);
|
||||
|
||||
document.title = `${song.title} ♫ ${config.appTitle}`
|
||||
document.querySelector('.plyr audio').setAttribute('title', `${song.artist.name} - ${song.title}`)
|
||||
document.title = `${song.title} ♫ ${config.appTitle}`;
|
||||
document
|
||||
.querySelector(".plyr audio")
|
||||
.setAttribute("title", `${song.artist.name} - ${song.title}`);
|
||||
|
||||
// We'll just "restart" playing the song, which will handle notification, scrobbling etc.
|
||||
this.restart()
|
||||
this.restart();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -155,59 +179,59 @@ export const playback = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
showNotification (song) {
|
||||
showNotification(song) {
|
||||
// Show the notification if we're allowed to
|
||||
if (!window.Notification || !preferences.notify) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const notif = new window.Notification(`♫ ${song.title}`, {
|
||||
icon: song.album.cover,
|
||||
body: `${song.album.name} – ${song.artist.name}`
|
||||
})
|
||||
});
|
||||
|
||||
notif.onclick = () => window.focus()
|
||||
notif.onclick = () => window.focus();
|
||||
|
||||
// Close the notif after 5 secs.
|
||||
window.setTimeout(() => notif.close(), 5000)
|
||||
window.setTimeout(() => notif.close(), 5000);
|
||||
} catch (e) {
|
||||
// Notification fails.
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
|
||||
}
|
||||
|
||||
if ('mediaSession' in navigator) {
|
||||
if ("mediaSession" in navigator) {
|
||||
/* global MediaMetadata */
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: song.title,
|
||||
artist: song.artist.name,
|
||||
album: song.album.name,
|
||||
artwork: [
|
||||
{ src: song.album.cover, sizes: '256x256', type: 'image/png' }
|
||||
{ src: song.album.cover, sizes: "256x256", type: "image/png" }
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restart playing a song.
|
||||
*/
|
||||
restart () {
|
||||
const song = queueStore.current
|
||||
restart() {
|
||||
const song = queueStore.current;
|
||||
|
||||
this.showNotification(song)
|
||||
this.showNotification(song);
|
||||
|
||||
// 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);
|
||||
|
||||
song.registeredPlayCount = false
|
||||
song.registeredPlayCount = false;
|
||||
|
||||
event.emit('song:played', song)
|
||||
event.emit("song:played", song);
|
||||
|
||||
socket.broadcast('song', songStore.generateDataToBroadcast(song))
|
||||
socket.broadcast("song", songStore.generateDataToBroadcast(song));
|
||||
|
||||
this.player.restart()
|
||||
this.player.play()
|
||||
this.player.restart();
|
||||
this.player.play();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -216,13 +240,13 @@ export const playback = {
|
|||
*
|
||||
* @return {Object} The song
|
||||
*/
|
||||
get next () {
|
||||
get next() {
|
||||
if (queueStore.next) {
|
||||
return queueStore.next
|
||||
return queueStore.next;
|
||||
}
|
||||
|
||||
if (preferences.repeatMode === 'REPEAT_ALL') {
|
||||
return queueStore.first
|
||||
if (preferences.repeatMode === "REPEAT_ALL") {
|
||||
return queueStore.first;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -232,13 +256,13 @@ export const playback = {
|
|||
*
|
||||
* @return {Object} The song
|
||||
*/
|
||||
get previous () {
|
||||
get previous() {
|
||||
if (queueStore.previous) {
|
||||
return queueStore.previous
|
||||
return queueStore.previous;
|
||||
}
|
||||
|
||||
if (preferences.repeatMode === 'REPEAT_ALL') {
|
||||
return queueStore.last
|
||||
if (preferences.repeatMode === "REPEAT_ALL") {
|
||||
return queueStore.last;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -246,44 +270,44 @@ export const playback = {
|
|||
* Circle through the repeat mode.
|
||||
* The selected mode will be stored into local storage as well.
|
||||
*/
|
||||
changeRepeatMode () {
|
||||
let index = this.repeatModes.indexOf(preferences.repeatMode) + 1
|
||||
changeRepeatMode() {
|
||||
let index = this.repeatModes.indexOf(preferences.repeatMode) + 1;
|
||||
|
||||
if (index >= this.repeatModes.length) {
|
||||
index = 0
|
||||
index = 0;
|
||||
}
|
||||
|
||||
preferences.repeatMode = this.repeatModes[index]
|
||||
preferences.repeatMode = this.repeatModes[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
!prev && preferences.repeatMode === 'NO_REPEAT'
|
||||
const prev = this.previous;
|
||||
!prev && preferences.repeatMode === "NO_REPEAT"
|
||||
? this.stop()
|
||||
: 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
|
||||
!next && preferences.repeatMode === 'NO_REPEAT'
|
||||
playNext() {
|
||||
const next = this.next;
|
||||
!next && preferences.repeatMode === "NO_REPEAT"
|
||||
? this.stop() // Nothing lasts forever, even cold November rain.
|
||||
: this.play(next)
|
||||
: this.play(next);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -292,84 +316,90 @@ 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.value = volume
|
||||
this.volumeInput.value = 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
|
||||
if (preferences.volume === "0" || preferences.volume === 0) {
|
||||
preferences.volume = 7;
|
||||
}
|
||||
|
||||
this.setVolume(preferences.volume)
|
||||
this.setVolume(preferences.volume);
|
||||
},
|
||||
|
||||
/**
|
||||
* Completely stop playback.
|
||||
*/
|
||||
stop () {
|
||||
document.title = config.appTitle
|
||||
this.player.pause()
|
||||
this.player.seek(0)
|
||||
stop() {
|
||||
document.title = config.appTitle;
|
||||
this.player.pause();
|
||||
this.player.seek(0);
|
||||
|
||||
if (queueStore.current) {
|
||||
queueStore.current.playbackState = 'stopped'
|
||||
queueStore.current.playbackState = "stopped";
|
||||
}
|
||||
|
||||
socket.broadcast('playback:stopped')
|
||||
socket.broadcast("playback:stopped");
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause playback.
|
||||
*/
|
||||
pause () {
|
||||
this.player.pause()
|
||||
queueStore.current.playbackState = 'paused'
|
||||
socket.broadcast('song', songStore.generateDataToBroadcast(queueStore.current))
|
||||
pause() {
|
||||
this.player.pause();
|
||||
queueStore.current.playbackState = "paused";
|
||||
socket.broadcast(
|
||||
"song",
|
||||
songStore.generateDataToBroadcast(queueStore.current)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume playback.
|
||||
*/
|
||||
resume () {
|
||||
this.player.play()
|
||||
queueStore.current.playbackState = 'playing'
|
||||
event.emit('song:played', queueStore.current)
|
||||
socket.broadcast('song', songStore.generateDataToBroadcast(queueStore.current))
|
||||
resume() {
|
||||
this.player.play();
|
||||
queueStore.current.playbackState = "playing";
|
||||
event.emit("song:played", queueStore.current);
|
||||
socket.broadcast(
|
||||
"song",
|
||||
songStore.generateDataToBroadcast(queueStore.current)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle playback.
|
||||
*/
|
||||
toggle () {
|
||||
toggle() {
|
||||
if (!queueStore.current) {
|
||||
this.playFirstInQueue()
|
||||
return
|
||||
this.playFirstInQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (queueStore.current.playbackState !== 'playing') {
|
||||
this.resume()
|
||||
return
|
||||
if (queueStore.current.playbackState !== "playing") {
|
||||
this.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
this.pause()
|
||||
this.pause();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -378,35 +408,35 @@ 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 () {
|
||||
queueStore.all.length ? this.play(queueStore.first) : this.queueAndPlay()
|
||||
playFirstInQueue() {
|
||||
queueStore.all.length ? this.play(queueStore.first) : this.queueAndPlay();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -415,10 +445,10 @@ export const playback = {
|
|||
* @param {Object} artist The artist object
|
||||
* @param {Boolean=true} shuffled Whether to shuffle the songs
|
||||
*/
|
||||
playAllByArtist ({ songs }, shuffled = true) {
|
||||
playAllByArtist({ songs }, shuffled = true) {
|
||||
shuffled
|
||||
? this.queueAndPlay(songs, true)
|
||||
: this.queueAndPlay(orderBy(songs, 'album_id', 'track'))
|
||||
: this.queueAndPlay(orderBy(songs, "album_id", "track"));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -427,9 +457,9 @@ export const playback = {
|
|||
* @param {Object} album The album object
|
||||
* @param {Boolean=true} shuffled Whether to shuffle the songs
|
||||
*/
|
||||
playAllInAlbum ({ songs }, shuffled = true) {
|
||||
playAllInAlbum({ songs }, shuffled = true) {
|
||||
shuffled
|
||||
? this.queueAndPlay(songs, true)
|
||||
: this.queueAndPlay(orderBy(songs, 'track'))
|
||||
: this.queueAndPlay(orderBy(songs, "track"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import Pusher from 'pusher-js'
|
||||
import Pusher from "pusher-js";
|
||||
|
||||
import { userStore } from '@/stores'
|
||||
import { ls } from '.'
|
||||
import { userStore } from "@/stores";
|
||||
import { ls } from ".";
|
||||
|
||||
export const socket = {
|
||||
pusher: null,
|
||||
channel: null,
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.PUSHER_APP_KEY) {
|
||||
return resolve()
|
||||
return resolve();
|
||||
}
|
||||
|
||||
this.pusher = new Pusher(window.PUSHER_APP_KEY, {
|
||||
authEndpoint: '/api/broadcasting/auth',
|
||||
authEndpoint: "/api/broadcasting/auth",
|
||||
auth: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${ls.get('jwt-token')}`
|
||||
Authorization: `Bearer ${ls.get("jwt-token")}`
|
||||
}
|
||||
},
|
||||
cluster: window.PUSHER_APP_CLUSTER,
|
||||
encrypted: true
|
||||
})
|
||||
});
|
||||
|
||||
this.channel = this.pusher.subscribe('private-koel')
|
||||
this.channel.bind('pusher:subscription_succeeded', () => {
|
||||
return resolve()
|
||||
})
|
||||
this.channel.bind('pusher:subscription_error', () => {
|
||||
return resolve()
|
||||
})
|
||||
})
|
||||
this.channel = this.pusher.subscribe("private-koel");
|
||||
this.channel.bind("pusher:subscription_succeeded", () => {
|
||||
return resolve();
|
||||
});
|
||||
this.channel.bind("pusher:subscription_error", () => {
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -39,10 +39,11 @@ export const socket = {
|
|||
* @param {string} eventName The event's name
|
||||
* @param {Object} data The event's data
|
||||
*/
|
||||
broadcast (eventName, data = {}) {
|
||||
this.channel && this.channel.trigger(`client-${eventName}.${userStore.current.id}`, data)
|
||||
broadcast(eventName, data = {}) {
|
||||
this.channel &&
|
||||
this.channel.trigger(`client-${eventName}.${userStore.current.id}`, data);
|
||||
|
||||
return this
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -50,9 +51,12 @@ export const socket = {
|
|||
* @param {string} eventName The event's name
|
||||
* @param {Function} cb
|
||||
*/
|
||||
listen (eventName, cb) {
|
||||
this.channel && this.channel.bind(`client-${eventName}.${userStore.current.id}`, data => cb(data))
|
||||
listen(eventName, cb) {
|
||||
this.channel &&
|
||||
this.channel.bind(`client-${eventName}.${userStore.current.id}`, data =>
|
||||
cb(data)
|
||||
);
|
||||
|
||||
return this
|
||||
return this;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { http } from '.'
|
||||
import { event } from '@/utils'
|
||||
import router from '@/router'
|
||||
import { http } from ".";
|
||||
import { event } from "@/utils";
|
||||
import router from "@/router";
|
||||
|
||||
export const youtube = {
|
||||
/**
|
||||
|
@ -8,21 +8,23 @@ export const youtube = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
searchVideosRelatedToSong (song) {
|
||||
searchVideosRelatedToSong(song) {
|
||||
if (!song.youtube) {
|
||||
song.youtube = {}
|
||||
song.youtube = {};
|
||||
}
|
||||
|
||||
const pageToken = song.youtube.nextPageToken || ''
|
||||
const pageToken = song.youtube.nextPageToken || "";
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(`youtube/search/song/${song.id}?pageToken=${pageToken}`,
|
||||
({ data: { nextPageToken, items }}) => {
|
||||
song.youtube.nextPageToken = nextPageToken
|
||||
song.youtube.items.push(...items)
|
||||
resolve()
|
||||
}, error => reject(error)
|
||||
)
|
||||
})
|
||||
http.get(
|
||||
`youtube/search/song/${song.id}?pageToken=${pageToken}`,
|
||||
({ data: { nextPageToken, items } }) => {
|
||||
song.youtube.nextPageToken = nextPageToken;
|
||||
song.youtube.items.push(...items);
|
||||
resolve();
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -30,11 +32,11 @@ export const youtube = {
|
|||
*
|
||||
* @param {Object} vide The video object
|
||||
*/
|
||||
play (video) {
|
||||
event.emit('youtube:play', {
|
||||
play(video) {
|
||||
event.emit("youtube:play", {
|
||||
id: video.id.videoId,
|
||||
title: video.snippet.title
|
||||
})
|
||||
router.go('youtube')
|
||||
});
|
||||
router.go("youtube");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'babel-polyfill/dist/polyfill.min.js'
|
||||
import 'plyr/dist/plyr.js'
|
||||
import './libs/modernizr-custom.js'
|
||||
import '../css/meyer-reset.min.css'
|
||||
import 'nouislider/distribute/nouislider.min.css'
|
||||
import 'intersection-observer'
|
||||
import 'font-awesome/css/font-awesome.min.css'
|
||||
import "babel-polyfill/dist/polyfill.min.js";
|
||||
import "plyr/dist/plyr.js";
|
||||
import "./libs/modernizr-custom.js";
|
||||
import "../css/meyer-reset.min.css";
|
||||
import "nouislider/distribute/nouislider.min.css";
|
||||
import "intersection-observer";
|
||||
import "font-awesome/css/font-awesome.min.css";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/*eslint camelcase: ["error", {properties: "never"}]*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from 'lodash'
|
||||
import Vue from "vue";
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from "lodash";
|
||||
|
||||
import stub from '@/stubs/album'
|
||||
import { artistStore } from '.'
|
||||
import stub from "@/stubs/album";
|
||||
import { artistStore } from ".";
|
||||
|
||||
export const albumStore = {
|
||||
stub,
|
||||
|
@ -19,24 +19,24 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>} albums The array of album objects
|
||||
*/
|
||||
init (albums) {
|
||||
init(albums) {
|
||||
// Traverse through the artists array and add their albums into our master album list.
|
||||
this.all = albums
|
||||
each(this.all, album => this.setupAlbum(album))
|
||||
this.all = albums;
|
||||
each(this.all, album => this.setupAlbum(album));
|
||||
},
|
||||
|
||||
setupAlbum (album) {
|
||||
const artist = artistStore.byId(album.artist_id)
|
||||
artist.albums = union(artist.albums, [album])
|
||||
setupAlbum(album) {
|
||||
const artist = artistStore.byId(album.artist_id);
|
||||
artist.albums = union(artist.albums, [album]);
|
||||
|
||||
Vue.set(album, 'artist', artist)
|
||||
Vue.set(album, 'info', null)
|
||||
Vue.set(album, 'songs', [])
|
||||
Vue.set(album, 'playCount', 0)
|
||||
Vue.set(album, "artist", artist);
|
||||
Vue.set(album, "info", null);
|
||||
Vue.set(album, "songs", []);
|
||||
Vue.set(album, "playCount", 0);
|
||||
|
||||
this.cache[album.id] = album
|
||||
this.cache[album.id] = album;
|
||||
|
||||
return album
|
||||
return album;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -44,8 +44,8 @@ export const albumStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.albums
|
||||
get all() {
|
||||
return this.state.albums;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -53,12 +53,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 this.cache[id]
|
||||
byId(id) {
|
||||
return this.cache[id];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,31 +66,35 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
add (albums) {
|
||||
albums = [].concat(albums)
|
||||
add(albums) {
|
||||
albums = [].concat(albums);
|
||||
each(albums, album => {
|
||||
this.setupAlbum(album, album.artist)
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0)
|
||||
})
|
||||
this.setupAlbum(album, album.artist);
|
||||
album.playCount = reduce(
|
||||
album.songs,
|
||||
(count, song) => count + song.playCount,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
this.all = union(this.all, albums)
|
||||
this.all = union(this.all, albums);
|
||||
},
|
||||
|
||||
purify () {
|
||||
this.compact()
|
||||
purify() {
|
||||
this.compact();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove empty albums from the store.
|
||||
*/
|
||||
compact () {
|
||||
const emptyAlbums = filter(this.all, album => album.songs.length === 0)
|
||||
compact() {
|
||||
const emptyAlbums = filter(this.all, album => album.songs.length === 0);
|
||||
if (!emptyAlbums.length) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.all = difference(this.all, emptyAlbums)
|
||||
each(emptyAlbums, album => delete this.cache[album.id])
|
||||
this.all = difference(this.all, emptyAlbums);
|
||||
each(emptyAlbums, album => delete this.cache[album.id]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -100,10 +104,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 => album.playCount && album.id !== 1)
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n)
|
||||
const applicable = filter(
|
||||
this.all,
|
||||
album => album.playCount && album.id !== 1
|
||||
);
|
||||
return take(orderBy(applicable, "playCount", "desc"), n);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -113,8 +120,8 @@ export const albumStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getRecentlyAdded (n = 6) {
|
||||
const applicable = filter(this.all, album => album.id !== 1)
|
||||
return take(orderBy(applicable, 'created_at', 'desc'), n)
|
||||
getRecentlyAdded(n = 6) {
|
||||
const applicable = filter(this.all, album => album.id !== 1);
|
||||
return take(orderBy(applicable, "created_at", "desc"), n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/*eslint camelcase: ["error", {properties: "never"}]*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from 'lodash'
|
||||
import Vue from "vue";
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from "lodash";
|
||||
|
||||
import stub from '@/stubs/artist'
|
||||
import stub from "@/stubs/artist";
|
||||
|
||||
const UNKNOWN_ARTIST_ID = 1
|
||||
const VARIOUS_ARTISTS_ID = 2
|
||||
const UNKNOWN_ARTIST_ID = 1;
|
||||
const VARIOUS_ARTISTS_ID = 2;
|
||||
|
||||
export const artistStore = {
|
||||
stub,
|
||||
|
@ -21,11 +21,11 @@ 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;
|
||||
|
||||
// Traverse through artists array to get the cover and number of songs for each.
|
||||
each(this.all, artist => this.setupArtist(artist))
|
||||
each(this.all, artist => this.setupArtist(artist));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -33,15 +33,15 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Object} artist
|
||||
*/
|
||||
setupArtist (artist) {
|
||||
Vue.set(artist, 'playCount', 0)
|
||||
Vue.set(artist, 'info', null)
|
||||
Vue.set(artist, 'albums', [])
|
||||
Vue.set(artist, 'songs', [])
|
||||
setupArtist(artist) {
|
||||
Vue.set(artist, "playCount", 0);
|
||||
Vue.set(artist, "info", null);
|
||||
Vue.set(artist, "albums", []);
|
||||
Vue.set(artist, "songs", []);
|
||||
|
||||
this.cache[artist.id] = artist
|
||||
this.cache[artist.id] = artist;
|
||||
|
||||
return artist
|
||||
return artist;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -49,8 +49,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.artists
|
||||
get all() {
|
||||
return this.state.artists;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -58,8 +58,8 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all (value) {
|
||||
this.state.artists = value
|
||||
set all(value) {
|
||||
this.state.artists = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -67,8 +67,8 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Number} id
|
||||
*/
|
||||
byId (id) {
|
||||
return this.cache[id]
|
||||
byId(id) {
|
||||
return this.cache[id];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -76,31 +76,35 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} artists
|
||||
*/
|
||||
add (artists) {
|
||||
artists = [].concat(artists)
|
||||
add(artists) {
|
||||
artists = [].concat(artists);
|
||||
each(artists, artist => {
|
||||
this.setupArtist(artist)
|
||||
artist.playCount = reduce(artist.songs, (count, song) => count + song.playCount, 0)
|
||||
})
|
||||
this.setupArtist(artist);
|
||||
artist.playCount = reduce(
|
||||
artist.songs,
|
||||
(count, song) => count + song.playCount,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
this.all = union(this.all, artists)
|
||||
this.all = union(this.all, artists);
|
||||
},
|
||||
|
||||
purify () {
|
||||
this.compact()
|
||||
purify() {
|
||||
this.compact();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove empty artists from the store.
|
||||
*/
|
||||
compact () {
|
||||
const emptyArtists = filter(this.all, artist => artist.songs.length === 0)
|
||||
compact() {
|
||||
const emptyArtists = filter(this.all, artist => artist.songs.length === 0);
|
||||
if (!emptyArtists.length) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.all = difference(this.all, emptyArtists)
|
||||
each(emptyArtists, artist => delete this.cache[artist.id])
|
||||
this.all = difference(this.all, emptyArtists);
|
||||
each(emptyArtists, artist => delete this.cache[artist.id]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -110,8 +114,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isVariousArtists (artist) {
|
||||
return artist.id === VARIOUS_ARTISTS_ID
|
||||
isVariousArtists(artist) {
|
||||
return artist.id === VARIOUS_ARTISTS_ID;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -121,8 +125,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isUnknownArtist (artist) {
|
||||
return artist.id === UNKNOWN_ARTIST_ID
|
||||
isUnknownArtist(artist) {
|
||||
return artist.id === UNKNOWN_ARTIST_ID;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -132,8 +136,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getSongsByArtist (artist) {
|
||||
return artist.songs
|
||||
getSongsByArtist(artist) {
|
||||
return artist.songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -143,15 +147,17 @@ 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 &&
|
||||
return (
|
||||
artist.playCount &&
|
||||
!this.isUnknownArtist(artist) &&
|
||||
!this.isVariousArtists(artist)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n)
|
||||
return take(orderBy(applicable, "playCount", "desc"), n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,97 +1,97 @@
|
|||
import { find } from 'lodash'
|
||||
import { find } from "lodash";
|
||||
|
||||
import { preferenceStore } from '.'
|
||||
import { preferenceStore } from ".";
|
||||
|
||||
export const equalizerStore = {
|
||||
presets: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Default',
|
||||
name: "Default",
|
||||
preamp: 0,
|
||||
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Classical',
|
||||
name: "Classical",
|
||||
preamp: -1,
|
||||
gains: [-1, -1, -1, -1, -1, -1, -7, -7, -7, -9]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Club',
|
||||
name: "Club",
|
||||
preamp: -6.7,
|
||||
gains: [-1, -1, 8, 5, 5, 5, 3, -1, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Dance',
|
||||
name: "Dance",
|
||||
preamp: -4.3,
|
||||
gains: [9, 7, 2, -1, -1, -5, -7, -7, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Full Bass',
|
||||
name: "Full Bass",
|
||||
preamp: -7.2,
|
||||
gains: [-8, 9, 9, 5, 1, -4, -8, -10, -11, -11]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Full Treble',
|
||||
name: "Full Treble",
|
||||
preamp: -12,
|
||||
gains: [-9, -9, -9, -4, 2, 11, 16, 16, 16, 16]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Headphone',
|
||||
name: "Headphone",
|
||||
preamp: -8,
|
||||
gains: [4, 11, 5, -3, -2, 1, 4, 9, 12, 14]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Large Hall',
|
||||
name: "Large Hall",
|
||||
preamp: -7.2,
|
||||
gains: [10, 10, 5, 5, -1, -4, -4, -4, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Live',
|
||||
name: "Live",
|
||||
preamp: -5.3,
|
||||
gains: [-4, -1, 4, 5, 5, 5, 4, 2, 2, 2]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'Pop',
|
||||
name: "Pop",
|
||||
preamp: -6.2,
|
||||
gains: [-1, 4, 7, 8, 5, -1, -2, -2, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Reggae',
|
||||
name: "Reggae",
|
||||
preamp: -8.2,
|
||||
gains: [-1, -1, -1, -5, -1, 6, 6, -1, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Rock',
|
||||
name: "Rock",
|
||||
preamp: -10,
|
||||
gains: [8, 4, -5, -8, -3, 4, 8, 11, 11, 11]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'Soft Rock',
|
||||
name: "Soft Rock",
|
||||
preamp: -5.3,
|
||||
gains: [4, 4, 2, -1, -4, -5, -3, -1, 2, 8]
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'Techno',
|
||||
name: "Techno",
|
||||
preamp: -7.7,
|
||||
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,13 +99,13 @@ 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.
|
||||
return this.getPresetById(preferenceStore.selectedPreset)
|
||||
return this.getPresetById(preferenceStore.selectedPreset);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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,14 +1,14 @@
|
|||
import { each, map, difference, union } from 'lodash'
|
||||
import NProgress from 'nprogress'
|
||||
import { each, map, difference, union } from "lodash";
|
||||
import NProgress from "nprogress";
|
||||
|
||||
import { http } from '@/services'
|
||||
import { alerts, pluralize } from '@/utils'
|
||||
import { http } from "@/services";
|
||||
import { alerts, pluralize } from "@/utils";
|
||||
|
||||
export const favoriteStore = {
|
||||
state: {
|
||||
songs: [],
|
||||
length: 0,
|
||||
fmtLength: ''
|
||||
fmtLength: ""
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -16,8 +16,8 @@ export const favoriteStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.songs
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -25,8 +25,8 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all (value) {
|
||||
this.state.songs = value
|
||||
set all(value) {
|
||||
this.state.songs = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -35,20 +35,25 @@ 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);
|
||||
|
||||
NProgress.start()
|
||||
NProgress.start();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/like', { song: song.id }, ({ data }) => {
|
||||
// We don't really need to notify just for one song.
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"interaction/like",
|
||||
{ song: song.id },
|
||||
({ data }) => {
|
||||
// We don't really need to notify just for one song.
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -56,8 +61,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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -65,15 +70,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 = [];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -81,22 +86,29 @@ 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)
|
||||
song.liked = true;
|
||||
});
|
||||
this.add(songs);
|
||||
|
||||
NProgress.start()
|
||||
NProgress.start();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/like', { songs: map(songs, 'id') }, ({ data }) => {
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into Favorites.`)
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"interaction/batch/like",
|
||||
{ songs: map(songs, "id") },
|
||||
({ data }) => {
|
||||
alerts.success(
|
||||
`Added ${pluralize(songs.length, "song")} into Favorites.`
|
||||
);
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -104,19 +116,26 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
unlike (songs) {
|
||||
unlike(songs) {
|
||||
each(songs, song => {
|
||||
song.liked = false
|
||||
})
|
||||
this.remove(songs)
|
||||
song.liked = false;
|
||||
});
|
||||
this.remove(songs);
|
||||
|
||||
NProgress.start()
|
||||
NProgress.start();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, ({ data }) => {
|
||||
alerts.success(`Removed ${pluralize(songs.length, 'song')} from Favorites.`)
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"interaction/batch/unlike",
|
||||
{ songs: map(songs, "id") },
|
||||
({ data }) => {
|
||||
alerts.success(
|
||||
`Removed ${pluralize(songs.length, "song")} from Favorites.`
|
||||
);
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,10 +1,10 @@
|
|||
import { each, find, map, difference, union } 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 { alerts, pluralize } from '@/utils'
|
||||
import { songStore } from '.'
|
||||
import stub from "@/stubs/playlist";
|
||||
import { http } from "@/services";
|
||||
import { alerts, pluralize } from "@/utils";
|
||||
import { songStore } from ".";
|
||||
|
||||
export const playlistStore = {
|
||||
stub,
|
||||
|
@ -13,9 +13,9 @@ export const playlistStore = {
|
|||
playlists: []
|
||||
},
|
||||
|
||||
init (playlists) {
|
||||
this.all = playlists
|
||||
each(this.all, this.objectifySongs)
|
||||
init(playlists) {
|
||||
this.all = playlists;
|
||||
each(this.all, this.objectifySongs);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -23,8 +23,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.playlists
|
||||
get all() {
|
||||
return this.state.playlists;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -32,8 +32,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all (value) {
|
||||
this.state.playlists = value
|
||||
set all(value) {
|
||||
this.state.playlists = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -43,8 +43,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -53,8 +53,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
objectifySongs (playlist) {
|
||||
playlist.songs = songStore.byIds(playlist.songs)
|
||||
objectifySongs(playlist) {
|
||||
playlist.songs = songStore.byIds(playlist.songs);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -64,8 +64,8 @@ export const playlistStore = {
|
|||
*
|
||||
* return {Array.<Object>}
|
||||
*/
|
||||
getSongs (playlist) {
|
||||
return playlist.songs
|
||||
getSongs(playlist) {
|
||||
return playlist.songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -73,8 +73,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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -82,8 +82,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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -92,23 +92,28 @@ 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 }, ({ data: playlist }) => {
|
||||
playlist.songs = songs
|
||||
this.objectifySongs(playlist)
|
||||
this.add(playlist)
|
||||
alerts.success(`Created playlist "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"playlist",
|
||||
{ name, songs },
|
||||
({ data: playlist }) => {
|
||||
playlist.songs = songs;
|
||||
this.objectifySongs(playlist);
|
||||
this.add(playlist);
|
||||
alerts.success(`Created playlist "${playlist.name}".`);
|
||||
resolve(playlist);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -116,16 +121,21 @@ 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)
|
||||
alerts.success(`Deleted playlist "${playlist.name}".`)
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.delete(
|
||||
`playlist/${playlist.id}`,
|
||||
{},
|
||||
({ data }) => {
|
||||
this.remove(playlist);
|
||||
alerts.success(`Deleted playlist "${playlist.name}".`);
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -134,23 +144,33 @@ 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;
|
||||
}
|
||||
|
||||
NProgress.start()
|
||||
NProgress.start();
|
||||
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') }, () => {
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.put(
|
||||
`playlist/${playlist.id}/sync`,
|
||||
{ songs: map(playlist.songs, "id") },
|
||||
() => {
|
||||
alerts.success(
|
||||
`Added ${pluralize(
|
||||
songs.length,
|
||||
"song"
|
||||
)} into "${playlist.name}".`
|
||||
);
|
||||
resolve(playlist);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -159,17 +179,27 @@ export const playlistStore = {
|
|||
* @param {Object} playlist
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
removeSongs (playlist, songs) {
|
||||
NProgress.start()
|
||||
removeSongs(playlist, songs) {
|
||||
NProgress.start();
|
||||
|
||||
playlist.songs = difference(playlist.songs, songs)
|
||||
playlist.songs = difference(playlist.songs, songs);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') }, () => {
|
||||
alerts.success(`Removed ${pluralize(songs.length, 'song')} from "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.put(
|
||||
`playlist/${playlist.id}/sync`,
|
||||
{ songs: map(playlist.songs, "id") },
|
||||
() => {
|
||||
alerts.success(
|
||||
`Removed ${pluralize(
|
||||
songs.length,
|
||||
"song"
|
||||
)} from "${playlist.name}".`
|
||||
);
|
||||
resolve(playlist);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -177,8 +207,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
update (playlist) {
|
||||
NProgress.start()
|
||||
update(playlist) {
|
||||
NProgress.start();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(
|
||||
|
@ -186,7 +216,7 @@ export const playlistStore = {
|
|||
{ name: playlist.name },
|
||||
() => resolve(playlist),
|
||||
error => reject(error)
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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: '',
|
||||
storeKey: "",
|
||||
|
||||
state: {
|
||||
volume: 7,
|
||||
notify: true,
|
||||
repeatMode: 'NO_REPEAT',
|
||||
repeatMode: "NO_REPEAT",
|
||||
showExtraPanel: true,
|
||||
confirmClosing: false,
|
||||
equalizer: {
|
||||
|
@ -27,39 +27,39 @@ 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.get(key),
|
||||
set: value => this.set(key, value),
|
||||
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,4 +1,14 @@
|
|||
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: {
|
||||
|
@ -6,7 +16,7 @@ export const queueStore = {
|
|||
current: null
|
||||
},
|
||||
|
||||
init () {
|
||||
init() {
|
||||
// We don't have anything to do here yet.
|
||||
// How about another song then?
|
||||
//
|
||||
|
@ -36,8 +46,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.songs
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -45,8 +55,8 @@ export const queueStore = {
|
|||
*
|
||||
* @param {Array.<Object>}
|
||||
*/
|
||||
set all (songs) {
|
||||
this.state.songs = songs
|
||||
set all(songs) {
|
||||
this.state.songs = songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -54,8 +64,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get first () {
|
||||
return head(this.all)
|
||||
get first() {
|
||||
return head(this.all);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -63,8 +73,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get last () {
|
||||
return last(this.all)
|
||||
get last() {
|
||||
return last(this.all);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -74,8 +84,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
contains (song) {
|
||||
return includes(this.all, song)
|
||||
contains(song) {
|
||||
return includes(this.all, song);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -86,13 +96,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 +111,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 +130,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,20 +140,20 @@ 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 = []
|
||||
clear() {
|
||||
this.all = [];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -153,8 +163,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Integer}
|
||||
*/
|
||||
indexOf (song) {
|
||||
return this.all.indexOf(song)
|
||||
indexOf(song) {
|
||||
return this.all.indexOf(song);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -162,14 +172,14 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get next () {
|
||||
get next() {
|
||||
if (!this.current) {
|
||||
return first(this.all)
|
||||
return first(this.all);
|
||||
}
|
||||
|
||||
const index = map(this.all, 'id').indexOf(this.current.id) + 1
|
||||
const index = map(this.all, "id").indexOf(this.current.id) + 1;
|
||||
|
||||
return index >= this.all.length ? null : this.all[index]
|
||||
return index >= this.all.length ? null : this.all[index];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -177,14 +187,14 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get previous () {
|
||||
get previous() {
|
||||
if (!this.current) {
|
||||
return last(this.all)
|
||||
return last(this.all);
|
||||
}
|
||||
|
||||
const index = map(this.all, 'id').indexOf(this.current.id) - 1
|
||||
const index = map(this.all, "id").indexOf(this.current.id) - 1;
|
||||
|
||||
return index < 0 ? null : this.all[index]
|
||||
return index < 0 ? null : this.all[index];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -192,8 +202,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get current () {
|
||||
return this.state.current
|
||||
get current() {
|
||||
return this.state.current;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -203,9 +213,9 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Object} The queued song.
|
||||
*/
|
||||
set current (song) {
|
||||
this.state.current = song
|
||||
return this.state.current
|
||||
set current(song) {
|
||||
this.state.current = song;
|
||||
return this.state.current;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -213,8 +223,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Array.<Object>} The shuffled array of song objects
|
||||
*/
|
||||
shuffle () {
|
||||
this.all = _shuffle(this.all)
|
||||
return this.all
|
||||
shuffle() {
|
||||
this.all = _shuffle(this.all);
|
||||
return this.all;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { http } from '@/services'
|
||||
import { alerts } from '@/utils'
|
||||
import stub from '@/stubs/settings'
|
||||
import { http } from "@/services";
|
||||
import { alerts } from "@/utils";
|
||||
import stub from "@/stubs/settings";
|
||||
|
||||
export const settingStore = {
|
||||
stub,
|
||||
|
@ -9,20 +9,25 @@ export const settingStore = {
|
|||
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 }) => {
|
||||
alerts.success('Settings saved.')
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"settings",
|
||||
this.all,
|
||||
({ data }) => {
|
||||
alerts.success("Settings saved.");
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import { assign } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
import { assign } from "lodash";
|
||||
import isMobile from "ismobilejs";
|
||||
|
||||
import { http } from '@/services'
|
||||
import { userStore, preferenceStore, artistStore, albumStore, songStore, playlistStore, queueStore, settingStore } from '.'
|
||||
import { http } from "@/services";
|
||||
import {
|
||||
userStore,
|
||||
preferenceStore,
|
||||
artistStore,
|
||||
albumStore,
|
||||
songStore,
|
||||
playlistStore,
|
||||
queueStore,
|
||||
settingStore
|
||||
} from ".";
|
||||
|
||||
export const sharedStore = {
|
||||
state: {
|
||||
|
@ -20,42 +29,46 @@ export const sharedStore = {
|
|||
useYouTube: false,
|
||||
useiTunes: false,
|
||||
allowDownload: false,
|
||||
currentVersion: '',
|
||||
latestVersion: '',
|
||||
cdnUrl: '',
|
||||
originalMediaPath: ''
|
||||
currentVersion: "",
|
||||
latestVersion: "",
|
||||
cdnUrl: "",
|
||||
originalMediaPath: ""
|
||||
},
|
||||
|
||||
init () {
|
||||
init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('data', ({ data }) => {
|
||||
assign(this.state, data)
|
||||
// Don't allow downloading on mobile devices
|
||||
this.state.allowDownload = this.state.allowDownload && !isMobile.any
|
||||
http.get(
|
||||
"data",
|
||||
({ data }) => {
|
||||
assign(this.state, data);
|
||||
// Don't allow downloading on mobile devices
|
||||
this.state.allowDownload = this.state.allowDownload && !isMobile.any;
|
||||
|
||||
// Always disable YouTube integration on mobile.
|
||||
this.state.useYouTube = this.state.useYouTube && !isMobile.phone
|
||||
// Always disable YouTube integration on mobile.
|
||||
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 = {}
|
||||
}
|
||||
// If this is a new user, initialize his preferences to be an empty object.
|
||||
if (!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)
|
||||
albumStore.init(this.state.albums)
|
||||
songStore.init(this.state.songs)
|
||||
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);
|
||||
albumStore.init(this.state.albums);
|
||||
songStore.init(this.state.songs);
|
||||
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
|
||||
// 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;
|
||||
|
||||
resolve(this.state)
|
||||
}, error => reject(error))
|
||||
})
|
||||
resolve(this.state);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,12 +1,28 @@
|
|||
import Vue from 'vue'
|
||||
import slugify from 'slugify'
|
||||
import { assign, without, map, take, remove, orderBy, each, unionBy, compact } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
import Vue from "vue";
|
||||
import slugify from "slugify";
|
||||
import {
|
||||
assign,
|
||||
without,
|
||||
map,
|
||||
take,
|
||||
remove,
|
||||
orderBy,
|
||||
each,
|
||||
unionBy,
|
||||
compact
|
||||
} from "lodash";
|
||||
import isMobile from "ismobilejs";
|
||||
|
||||
import { secondsToHis, alerts, pluralize } from '@/utils'
|
||||
import { http, ls } from '@/services'
|
||||
import { sharedStore, favoriteStore, albumStore, artistStore, preferenceStore } from '.'
|
||||
import stub from '@/stubs/song'
|
||||
import { secondsToHis, alerts, pluralize } from "@/utils";
|
||||
import { http, ls } from "@/services";
|
||||
import {
|
||||
sharedStore,
|
||||
favoriteStore,
|
||||
albumStore,
|
||||
artistStore,
|
||||
preferenceStore
|
||||
} from ".";
|
||||
import stub from "@/stubs/song";
|
||||
|
||||
export const songStore = {
|
||||
stub,
|
||||
|
@ -34,39 +50,39 @@ export const songStore = {
|
|||
*
|
||||
* @param {Array.<Object>} songs The array of song objects
|
||||
*/
|
||||
init (songs) {
|
||||
this.all = songs
|
||||
each(this.all, song => this.setupSong(song))
|
||||
this.state.recentlyPlayed = this.gatherRecentlyPlayedFromLocalStorage()
|
||||
init(songs) {
|
||||
this.all = songs;
|
||||
each(this.all, song => this.setupSong(song));
|
||||
this.state.recentlyPlayed = this.gatherRecentlyPlayedFromLocalStorage();
|
||||
},
|
||||
|
||||
setupSong (song) {
|
||||
song.fmtLength = secondsToHis(song.length)
|
||||
setupSong(song) {
|
||||
song.fmtLength = secondsToHis(song.length);
|
||||
|
||||
const album = albumStore.byId(song.album_id)
|
||||
const artist = artistStore.byId(song.artist_id)
|
||||
const album = albumStore.byId(song.album_id);
|
||||
const artist = artistStore.byId(song.artist_id);
|
||||
|
||||
// Manually set these additional properties to be reactive
|
||||
Vue.set(song, 'playCount', song.playCount || 0)
|
||||
Vue.set(song, 'album', album)
|
||||
Vue.set(song, 'artist', artist)
|
||||
Vue.set(song, 'liked', song.liked || false)
|
||||
Vue.set(song, 'lyrics', song.lyrics || null)
|
||||
Vue.set(song, 'playbackState', song.playbackState || 'stopped')
|
||||
Vue.set(song, "playCount", song.playCount || 0);
|
||||
Vue.set(song, "album", album);
|
||||
Vue.set(song, "artist", artist);
|
||||
Vue.set(song, "liked", song.liked || false);
|
||||
Vue.set(song, "lyrics", song.lyrics || null);
|
||||
Vue.set(song, "playbackState", song.playbackState || "stopped");
|
||||
|
||||
artist.songs = unionBy(artist.songs || [], [song], 'id')
|
||||
album.songs = unionBy(album.songs || [], [song], 'id')
|
||||
artist.songs = unionBy(artist.songs || [], [song], "id");
|
||||
album.songs = unionBy(album.songs || [], [song], "id");
|
||||
|
||||
// now if the song is part of a compilation album, the album must be added
|
||||
// into its artist as well
|
||||
if (album.is_compilation) {
|
||||
artist.albums = unionBy(artist.albums, [album], 'id')
|
||||
artist.albums = unionBy(artist.albums, [album], "id");
|
||||
}
|
||||
|
||||
// Cache the song, so that byId() is faster
|
||||
this.cache[song.id] = song
|
||||
this.cache[song.id] = song;
|
||||
|
||||
return song
|
||||
return song;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -74,23 +90,23 @@ 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;
|
||||
|
||||
song.liked && favoriteStore.add(song)
|
||||
})
|
||||
song.liked && favoriteStore.add(song);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,10 +117,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 +128,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.songs
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -121,8 +137,8 @@ export const songStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all (value) {
|
||||
this.state.songs = value
|
||||
set all(value) {
|
||||
this.state.songs = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -132,8 +148,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId (id) {
|
||||
return this.cache[id]
|
||||
byId(id) {
|
||||
return this.cache[id];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -143,8 +159,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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -156,16 +172,16 @@ export const songStore = {
|
|||
*
|
||||
* @return {Object|false}
|
||||
*/
|
||||
guess (title, album) {
|
||||
title = slugify(title.toLowerCase())
|
||||
let found = false
|
||||
guess(title, album) {
|
||||
title = slugify(title.toLowerCase());
|
||||
let found = false;
|
||||
each(album.songs, song => {
|
||||
if (slugify(song.title.toLowerCase()) === title) {
|
||||
found = song
|
||||
found = song;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return found
|
||||
return found;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -173,19 +189,24 @@ 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
|
||||
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;
|
||||
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -193,15 +214,15 @@ export const songStore = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
addRecentlyPlayed (song) {
|
||||
remove(this.state.recentlyPlayed, s => s.id === song.id)
|
||||
addRecentlyPlayed(song) {
|
||||
remove(this.state.recentlyPlayed, s => s.id === song.id);
|
||||
|
||||
// Then we prepend the song into the list.
|
||||
this.state.recentlyPlayed.unshift(song)
|
||||
this.state.recentlyPlayed.unshift(song);
|
||||
// Only take first 7 songs
|
||||
this.state.recentlyPlayed.splice(7)
|
||||
this.state.recentlyPlayed.splice(7);
|
||||
// Save to local storage as well
|
||||
preferenceStore.set('recent-songs', map(this.state.recentlyPlayed, 'id'))
|
||||
preferenceStore.set("recent-songs", map(this.state.recentlyPlayed, "id"));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -209,12 +230,17 @@ 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)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
`${song.id}/scrobble/${song.playStartTime}`,
|
||||
{},
|
||||
({ data }) => {
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -223,41 +249,58 @@ 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')
|
||||
}, ({ data: { songs, artists, albums }}) => {
|
||||
// Add the artist and album into stores if they're new
|
||||
each(artists, artist => !artistStore.byId(artist.id) && artistStore.add(artist))
|
||||
each(albums, album => !albumStore.byId(album.id) && albumStore.add(album))
|
||||
http.put(
|
||||
"songs",
|
||||
{
|
||||
data,
|
||||
songs: map(songs, "id")
|
||||
},
|
||||
({ data: { songs, artists, albums } }) => {
|
||||
// Add the artist and album into stores if they're new
|
||||
each(
|
||||
artists,
|
||||
artist => !artistStore.byId(artist.id) && artistStore.add(artist)
|
||||
);
|
||||
each(
|
||||
albums,
|
||||
album => !albumStore.byId(album.id) && albumStore.add(album)
|
||||
);
|
||||
|
||||
each(songs, song => {
|
||||
const originalSong = this.byId(song.id)
|
||||
each(songs, song => {
|
||||
const originalSong = this.byId(song.id);
|
||||
|
||||
if (originalSong.album_id !== song.album_id) {
|
||||
// album has been changed. Remove the song from its old album.
|
||||
originalSong.album.songs = without(originalSong.album.songs, originalSong)
|
||||
}
|
||||
if (originalSong.album_id !== song.album_id) {
|
||||
// album has been changed. Remove the song from its old album.
|
||||
originalSong.album.songs = without(
|
||||
originalSong.album.songs,
|
||||
originalSong
|
||||
);
|
||||
}
|
||||
|
||||
if (originalSong.artist_id !== song.artist_id) {
|
||||
// artist has been changed. Remove the song from its old artist
|
||||
originalSong.artist.songs = without(originalSong.artist.songs, originalSong)
|
||||
}
|
||||
if (originalSong.artist_id !== song.artist_id) {
|
||||
// artist has been changed. Remove the song from its old artist
|
||||
originalSong.artist.songs = without(
|
||||
originalSong.artist.songs,
|
||||
originalSong
|
||||
);
|
||||
}
|
||||
|
||||
assign(originalSong, song)
|
||||
// re-setup the song
|
||||
this.setupSong(originalSong)
|
||||
})
|
||||
assign(originalSong, song);
|
||||
// re-setup the song
|
||||
this.setupSong(originalSong);
|
||||
});
|
||||
|
||||
artistStore.compact()
|
||||
albumStore.compact()
|
||||
artistStore.compact();
|
||||
albumStore.compact();
|
||||
|
||||
alerts.success(`Updated ${pluralize(songs.length, 'song')}.`)
|
||||
resolve(songs)
|
||||
}, error => reject(error))
|
||||
})
|
||||
alerts.success(`Updated ${pluralize(songs.length, "song")}.`);
|
||||
resolve(songs);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -267,11 +310,14 @@ export const songStore = {
|
|||
*
|
||||
* @return {string} The source URL, with JWT token appended.
|
||||
*/
|
||||
getSourceUrl (song) {
|
||||
getSourceUrl(song) {
|
||||
if (isMobile.any && preferenceStore.transcodeOnMobile) {
|
||||
return `${sharedStore.state.cdnUrl}api/${song.id}/play/1/128?jwt-token=${ls.get('jwt-token')}`
|
||||
return `${sharedStore.state
|
||||
.cdnUrl}api/${song.id}/play/1/128?jwt-token=${ls.get("jwt-token")}`;
|
||||
}
|
||||
return `${sharedStore.state.cdnUrl}api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`
|
||||
return `${sharedStore.state.cdnUrl}api/${song.id}/play?jwt-token=${ls.get(
|
||||
"jwt-token"
|
||||
)}`;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -282,24 +328,24 @@ export const songStore = {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getShareableUrl (song) {
|
||||
return `${window.location.origin}/#!/song/${song.id}`
|
||||
getShareableUrl(song) {
|
||||
return `${window.location.origin}/#!/song/${song.id}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* The recently played songs.
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get recentlyPlayed () {
|
||||
return this.state.recentlyPlayed
|
||||
get recentlyPlayed() {
|
||||
return this.state.recentlyPlayed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gather the recently played songs from local storage.
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
gatherRecentlyPlayedFromLocalStorage () {
|
||||
return compact(this.byIds(preferenceStore.get('recent-songs') || []))
|
||||
gatherRecentlyPlayedFromLocalStorage() {
|
||||
return compact(this.byIds(preferenceStore.get("recent-songs") || []));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -309,13 +355,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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -323,8 +369,8 @@ 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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -332,7 +378,7 @@ export const songStore = {
|
|||
* @param {Object} song
|
||||
* @return {Object}
|
||||
*/
|
||||
generateDataToBroadcast (song) {
|
||||
generateDataToBroadcast(song) {
|
||||
return {
|
||||
song: {
|
||||
id: song.id,
|
||||
|
@ -347,6 +393,6 @@ export const songStore = {
|
|||
name: song.artist.name
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
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 { alerts } from '@/utils'
|
||||
import stub from '@/stubs/user'
|
||||
import { http } from "@/services";
|
||||
import { alerts } from "@/utils";
|
||||
import stub from "@/stubs/user";
|
||||
|
||||
export const userStore = {
|
||||
stub,
|
||||
|
@ -21,15 +21,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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -37,8 +37,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all () {
|
||||
return this.state.users
|
||||
get all() {
|
||||
return this.state.users;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -46,8 +46,8 @@ export const userStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all (value) {
|
||||
this.state.users = value
|
||||
set all(value) {
|
||||
this.state.users = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -57,8 +57,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,8 +66,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get current () {
|
||||
return this.state.current
|
||||
get current() {
|
||||
return this.state.current;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -77,9 +77,9 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
set current (user) {
|
||||
this.state.current = user
|
||||
return this.state.current
|
||||
set current(user) {
|
||||
this.state.current = user;
|
||||
return this.state.current;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -87,12 +87,16 @@ 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`
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,36 +105,50 @@ 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)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"me",
|
||||
{ email, password },
|
||||
({ data }) => {
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Log the current user out.
|
||||
*/
|
||||
logout () {
|
||||
logout() {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete('me', {}, ({ data }) => {
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.delete(
|
||||
"me",
|
||||
{},
|
||||
({ data }) => {
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current user's profile.
|
||||
*/
|
||||
getProfile () {
|
||||
getProfile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('me', ({ data }) => {
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.get(
|
||||
"me",
|
||||
({ data }) => {
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -138,21 +156,25 @@ 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()
|
||||
alerts.success('Profile updated.')
|
||||
resolve(this.current)
|
||||
},
|
||||
error => reject(error))
|
||||
})
|
||||
http.put(
|
||||
"me",
|
||||
{
|
||||
password,
|
||||
name: this.current.name,
|
||||
email: this.current.email
|
||||
},
|
||||
() => {
|
||||
this.setAvatar();
|
||||
alerts.success("Profile updated.");
|
||||
resolve(this.current);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -162,17 +184,22 @@ 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 }, ({ data: user }) => {
|
||||
this.setAvatar(user)
|
||||
this.all.unshift(user)
|
||||
alerts.success(`New user "${name}" created.`)
|
||||
resolve(user)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.post(
|
||||
"user",
|
||||
{ name, email, password },
|
||||
({ data: user }) => {
|
||||
this.setAvatar(user);
|
||||
this.all.unshift(user);
|
||||
alerts.success(`New user "${name}" created.`);
|
||||
resolve(user);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -183,17 +210,22 @@ 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.name, user.email, user.password] = [name, email, '']
|
||||
alerts.success('User profile updated.')
|
||||
resolve(user)
|
||||
}, error => reject(error))
|
||||
})
|
||||
http.put(
|
||||
`user/${user.id}`,
|
||||
{ name, email, password },
|
||||
() => {
|
||||
this.setAvatar(user);
|
||||
[user.name, user.email, user.password] = [name, email, ""];
|
||||
alerts.success("User profile updated.");
|
||||
resolve(user);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -201,38 +233,43 @@ 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)
|
||||
alerts.success(`User "${user.name}" deleted.`)
|
||||
http.delete(
|
||||
`user/${user.id}`,
|
||||
{},
|
||||
({ data }) => {
|
||||
this.all = without(this.all, user);
|
||||
alerts.success(`User "${user.name}" deleted.`);
|
||||
|
||||
// Mama, just killed a man
|
||||
// Put a gun against his head
|
||||
// Pulled my trigger, now he's dead
|
||||
// Mama, life had just begun
|
||||
// But now I've gone and thrown it all away
|
||||
// Mama, oooh
|
||||
// Didn't mean to make you cry
|
||||
// If I'm not back again this time tomorrow
|
||||
// Carry on, carry on, as if nothing really matters
|
||||
//
|
||||
// Too late, my time has come
|
||||
// Sends shivers down my spine
|
||||
// Body's aching all the time
|
||||
// Goodbye everybody - I've got to go
|
||||
// Gotta leave you all behind and face the truth
|
||||
// Mama, oooh
|
||||
// I don't want to die
|
||||
// I sometimes wish I'd never been born at all
|
||||
// Mama, just killed a man
|
||||
// Put a gun against his head
|
||||
// Pulled my trigger, now he's dead
|
||||
// Mama, life had just begun
|
||||
// But now I've gone and thrown it all away
|
||||
// Mama, oooh
|
||||
// Didn't mean to make you cry
|
||||
// If I'm not back again this time tomorrow
|
||||
// Carry on, carry on, as if nothing really matters
|
||||
//
|
||||
// Too late, my time has come
|
||||
// Sends shivers down my spine
|
||||
// Body's aching all the time
|
||||
// Goodbye everybody - I've got to go
|
||||
// Gotta leave you all behind and face the truth
|
||||
// Mama, oooh
|
||||
// I don't want to die
|
||||
// I sometimes wish I'd never been born at all
|
||||
|
||||
/**
|
||||
/**
|
||||
* Brian May enters the stage.
|
||||
*/
|
||||
resolve(data)
|
||||
}, error => reject(error))
|
||||
})
|
||||
resolve(data);
|
||||
},
|
||||
error => reject(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import config from '@/config'
|
||||
import artist from './artist'
|
||||
import config from "@/config";
|
||||
import artist from "./artist";
|
||||
|
||||
export default {
|
||||
artist,
|
||||
id: 0,
|
||||
artist_id: 0,
|
||||
name: '',
|
||||
name: "",
|
||||
cover: config.unknownCover,
|
||||
playCount: 0,
|
||||
length: 0,
|
||||
fmtLength: '00:00',
|
||||
fmtLength: "00:00",
|
||||
songs: []
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
export default {
|
||||
id: 0,
|
||||
name: '',
|
||||
name: "",
|
||||
image: null,
|
||||
playCount: 0,
|
||||
albums: [],
|
||||
songs: []
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
name: '',
|
||||
name: "",
|
||||
songs: []
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default {
|
||||
media_path: ''
|
||||
}
|
||||
media_path: ""
|
||||
};
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import album from './album'
|
||||
import artist from './artist'
|
||||
import album from "./album";
|
||||
import artist from "./artist";
|
||||
|
||||
export default {
|
||||
album,
|
||||
artist,
|
||||
id: null,
|
||||
album_id: 0,
|
||||
title: '',
|
||||
title: "",
|
||||
length: 0,
|
||||
fmtLength: '00:00',
|
||||
lyrics: '',
|
||||
fmtLength: "00:00",
|
||||
lyrics: "",
|
||||
liked: false,
|
||||
playCount: 0,
|
||||
playbackState: 'stopped'
|
||||
}
|
||||
playbackState: "stopped"
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
id: 0,
|
||||
name: '',
|
||||
email: '',
|
||||
avatar: '',
|
||||
name: "",
|
||||
email: "",
|
||||
avatar: "",
|
||||
is_admin: false
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,222 +2,222 @@ export default {
|
|||
artists: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Unknown Artist'
|
||||
name: "Unknown Artist"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Various Artists'
|
||||
name: "Various Artists"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'All-4-One'
|
||||
name: "All-4-One"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Boy Dylan'
|
||||
name: "Boy Dylan"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'James Blunt'
|
||||
name: "James Blunt"
|
||||
}
|
||||
],
|
||||
albums: [
|
||||
{
|
||||
id: 1193,
|
||||
artist_id: 3,
|
||||
name: 'All-4-One',
|
||||
cover: '/public/img/covers/565c0f7067425.jpeg'
|
||||
name: "All-4-One",
|
||||
cover: "/public/img/covers/565c0f7067425.jpeg"
|
||||
},
|
||||
{
|
||||
id: 1194,
|
||||
artist_id: 3,
|
||||
name: 'And The Music Speaks',
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
name: "And The Music Speaks",
|
||||
cover: "/public/img/covers/unknown-album.png"
|
||||
},
|
||||
{
|
||||
id: 1195,
|
||||
artist_id: 3,
|
||||
name: 'Space Jam',
|
||||
cover: '/public/img/covers/565c0f7115e0f.png'
|
||||
name: "Space Jam",
|
||||
cover: "/public/img/covers/565c0f7115e0f.png"
|
||||
},
|
||||
{
|
||||
id: 1217,
|
||||
artist_id: 4,
|
||||
name: 'Highway 61 Revisited',
|
||||
cover: '/public/img/covers/565c0f76dc6e8.jpeg'
|
||||
name: "Highway 61 Revisited",
|
||||
cover: "/public/img/covers/565c0f76dc6e8.jpeg"
|
||||
},
|
||||
{
|
||||
id: 1218,
|
||||
artist_id: 4,
|
||||
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"
|
||||
},
|
||||
{
|
||||
id: 1219,
|
||||
artist_id: 4,
|
||||
name: "The Times They Are A-Changin",
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
cover: "/public/img/covers/unknown-album.png"
|
||||
},
|
||||
{
|
||||
id: 1268,
|
||||
artist_id: 5,
|
||||
name: 'Back To Bedlam',
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
name: "Back To Bedlam",
|
||||
cover: "/public/img/covers/unknown-album.png"
|
||||
}
|
||||
],
|
||||
|
||||
songs: [
|
||||
{
|
||||
id: '39189f4545f9d5671fb3dc964f0080a0',
|
||||
id: "39189f4545f9d5671fb3dc964f0080a0",
|
||||
album_id: 1193,
|
||||
artist_id: 3,
|
||||
title: 'I Swear',
|
||||
title: "I Swear",
|
||||
length: 259.92,
|
||||
playCount: 4
|
||||
},
|
||||
{
|
||||
id: 'a6a550f7d950d2a2520f9bf1a60f025a',
|
||||
id: "a6a550f7d950d2a2520f9bf1a60f025a",
|
||||
album_id: 1194,
|
||||
artist_id: 3,
|
||||
title: 'I can love you like that',
|
||||
title: "I can love you like that",
|
||||
length: 262.61,
|
||||
playCount: 2
|
||||
},
|
||||
{
|
||||
id: 'd86c30fd34f13c1aff8db59b7fc9c610',
|
||||
id: "d86c30fd34f13c1aff8db59b7fc9c610",
|
||||
album_id: 1195,
|
||||
artist_id: 3,
|
||||
title: 'I turn to you',
|
||||
title: "I turn to you",
|
||||
length: 293.04
|
||||
},
|
||||
{
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55e',
|
||||
id: "e6d3977f3ffa147801ca5d1fdf6fa55e",
|
||||
album_id: 1217,
|
||||
artist_id: 4,
|
||||
title: 'Like a rolling stone',
|
||||
title: "Like a rolling stone",
|
||||
length: 373.63
|
||||
},
|
||||
{
|
||||
id: 'aa16bbef6a9710eb9a0f41ecc534fad5',
|
||||
id: "aa16bbef6a9710eb9a0f41ecc534fad5",
|
||||
album_id: 1218,
|
||||
artist_id: 4,
|
||||
title: "Knockin' on heaven's door",
|
||||
length: 151.9
|
||||
},
|
||||
{
|
||||
id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
id: "cb7edeac1f097143e65b1b2cde102482",
|
||||
album_id: 1219,
|
||||
artist_id: 4,
|
||||
title: "The times they are a-changin'",
|
||||
length: 196
|
||||
},
|
||||
{
|
||||
id: '0ba9fb128427b32683b9eb9140912a70',
|
||||
id: "0ba9fb128427b32683b9eb9140912a70",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'No bravery',
|
||||
title: "No bravery",
|
||||
length: 243.12
|
||||
},
|
||||
{
|
||||
id: '123fd1ad32240ecab28a4e86ed5173',
|
||||
id: "123fd1ad32240ecab28a4e86ed5173",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'So long, Jimmy',
|
||||
title: "So long, Jimmy",
|
||||
length: 265.04
|
||||
},
|
||||
{
|
||||
id: '6a54c674d8b16732f26df73f59c63e21',
|
||||
id: "6a54c674d8b16732f26df73f59c63e21",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Wisemen',
|
||||
title: "Wisemen",
|
||||
length: 223.14
|
||||
},
|
||||
{
|
||||
id: '6df7d82a9a8701e40d1c291cf14a16bc',
|
||||
id: "6df7d82a9a8701e40d1c291cf14a16bc",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Goodbye my lover',
|
||||
title: "Goodbye my lover",
|
||||
length: 258.61
|
||||
},
|
||||
{
|
||||
id: '74a2000d343e4587273d3ad14e2fd741',
|
||||
id: "74a2000d343e4587273d3ad14e2fd741",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'High',
|
||||
title: "High",
|
||||
length: 245.86
|
||||
},
|
||||
{
|
||||
id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
id: "7900ab518f51775fe6cf06092c074ee5",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: "You're beautiful",
|
||||
length: 213.29
|
||||
},
|
||||
{
|
||||
id: '803910a51f9893347e087af851e38777',
|
||||
id: "803910a51f9893347e087af851e38777",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Cry',
|
||||
title: "Cry",
|
||||
length: 246.91
|
||||
},
|
||||
{
|
||||
id: 'd82b0d4d4803ebbcb61000a5b6a868f5',
|
||||
id: "d82b0d4d4803ebbcb61000a5b6a868f5",
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Tears and rain',
|
||||
title: "Tears and rain",
|
||||
length: 244.45
|
||||
}
|
||||
],
|
||||
interactions: [
|
||||
{
|
||||
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
|
||||
}
|
||||
],
|
||||
currentUser: {
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
name: "Phan An",
|
||||
email: "me@phanan.net",
|
||||
is_admin: true
|
||||
},
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
name: "Phan An",
|
||||
email: "me@phanan.net",
|
||||
is_admin: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'John Doe',
|
||||
email: 'john@doe.tld',
|
||||
name: "John Doe",
|
||||
email: "john@doe.tld",
|
||||
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) => {
|
||||
Object.keys(window).forEach(key => {
|
||||
if (!(key in global)) {
|
||||
global[key] = window[key]
|
||||
global[key] = window[key];
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,35 +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'))
|
||||
describe("services/ls", () => {
|
||||
beforeEach(() => localStorage.remove("foo"));
|
||||
|
||||
describe('#get', () => {
|
||||
it('correctly gets an existing item from local storage', () => {
|
||||
localStorage('foo', 'bar')
|
||||
ls.get('foo').should.equal('bar')
|
||||
})
|
||||
describe("#get", () => {
|
||||
it("correctly gets an existing item from local storage", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
it("correctly returns the default value for a non exising item", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
describe("#set", () => {
|
||||
it("correctly sets an item into local storage", () => {
|
||||
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')
|
||||
var result = localStorage('foo') === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
describe("#remove", () => {
|
||||
it("correctly removes an item from local storage", () => {
|
||||
localStorage("foo", "bar");
|
||||
ls.remove("foo");
|
||||
var result = localStorage("foo") === null;
|
||||
result.should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
require("chai").should();
|
||||
import { cloneDeep, last } from "lodash";
|
||||
|
||||
import { albumStore, artistStore } from '../../stores'
|
||||
import data from '../blobs/data'
|
||||
import { albumStore, artistStore } from "../../stores";
|
||||
import data from "../blobs/data";
|
||||
|
||||
const { artists, albums } = data
|
||||
const { artists, albums } = data;
|
||||
|
||||
describe('stores/album', () => {
|
||||
describe("stores/album", () => {
|
||||
beforeEach(() => {
|
||||
artistStore.init(cloneDeep(artists))
|
||||
albumStore.init(cloneDeep(albums))
|
||||
})
|
||||
artistStore.init(cloneDeep(artists));
|
||||
albumStore.init(cloneDeep(albums));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
artistStore.state.artists = []
|
||||
albumStore.state.albums = []
|
||||
})
|
||||
artistStore.state.artists = [];
|
||||
albumStore.state.albums = [];
|
||||
});
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers albums', () => {
|
||||
albumStore.state.albums.length.should.equal(7)
|
||||
})
|
||||
describe("#init", () => {
|
||||
it("correctly gathers albums", () => {
|
||||
albumStore.state.albums.length.should.equal(7);
|
||||
});
|
||||
|
||||
it('correctly sets album artists', () => {
|
||||
albumStore.state.albums[0].artist.id.should.equal(3)
|
||||
})
|
||||
})
|
||||
it("correctly sets album artists", () => {
|
||||
albumStore.state.albums[0].artist.id.should.equal(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets an album by ID', () => {
|
||||
albumStore.byId(1193).name.should.equal('All-4-One')
|
||||
})
|
||||
})
|
||||
describe("#byId", () => {
|
||||
it("correctly gets an album by ID", () => {
|
||||
albumStore.byId(1193).name.should.equal("All-4-One");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compact', () => {
|
||||
it('correctly compacts albums', () => {
|
||||
albumStore.compact()
|
||||
albumStore.state.albums.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
describe("#compact", () => {
|
||||
it("correctly compacts albums", () => {
|
||||
albumStore.compact();
|
||||
albumStore.state.albums.length.should.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all albums', () => {
|
||||
albumStore.all.length.should.equal(7)
|
||||
})
|
||||
})
|
||||
})
|
||||
describe("#all", () => {
|
||||
it("correctly returns all albums", () => {
|
||||
albumStore.all.length.should.equal(7);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
require("chai").should();
|
||||
import { cloneDeep, last } from "lodash";
|
||||
|
||||
import { artistStore } from '../../stores'
|
||||
import data from '../blobs/data'
|
||||
const artists = data.artists
|
||||
import { artistStore } from "../../stores";
|
||||
import data from "../blobs/data";
|
||||
const artists = data.artists;
|
||||
|
||||
describe('stores/artist', () => {
|
||||
beforeEach(() => artistStore.init(cloneDeep(artists)))
|
||||
afterEach(() => artistStore.state.artists = [])
|
||||
describe("stores/artist", () => {
|
||||
beforeEach(() => artistStore.init(cloneDeep(artists)));
|
||||
afterEach(() => (artistStore.state.artists = []));
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers artists', () => {
|
||||
artistStore.state.artists.length.should.equal(5)
|
||||
})
|
||||
})
|
||||
describe("#init", () => {
|
||||
it("correctly gathers artists", () => {
|
||||
artistStore.state.artists.length.should.equal(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets an artist by ID', () => {
|
||||
artistStore.byId(3).name.should.equal('All-4-One')
|
||||
})
|
||||
})
|
||||
describe("#byId", () => {
|
||||
it("correctly gets an artist by ID", () => {
|
||||
artistStore.byId(3).name.should.equal("All-4-One");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compact', () => {
|
||||
it('correctly compact artists', () => {
|
||||
artistStore.compact()
|
||||
describe("#compact", () => {
|
||||
it("correctly compact artists", () => {
|
||||
artistStore.compact();
|
||||
// because we've not processed songs/albums, all artists here have no songs
|
||||
// and should be removed after compact()ing
|
||||
artistStore.state.artists.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
artistStore.state.artists.length.should.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
describe('stores/preference', () => {
|
||||
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)
|
||||
it("correctly sets preferences", () => {
|
||||
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)
|
||||
it("returns correct preference values", () => {
|
||||
preferenceStore.get("volume").should.equal(8);
|
||||
|
||||
// Test the proxy
|
||||
preferenceStore.volume.should.equal(8)
|
||||
})
|
||||
})
|
||||
})
|
||||
preferenceStore.volume.should.equal(8);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,111 +1,115 @@
|
|||
require('chai').should()
|
||||
require("chai").should();
|
||||
|
||||
import { queueStore } from '../../stores'
|
||||
import data from '../blobs/data'
|
||||
import { queueStore } from "../../stores";
|
||||
import data from "../blobs/data";
|
||||
|
||||
const { songs: allSongs } = data
|
||||
const { songs: allSongs } = data;
|
||||
// only get the songs by James Blunt
|
||||
const songs = allSongs.filter(song => song.artist_id === 5)
|
||||
const songs = allSongs.filter(song => song.artist_id === 5);
|
||||
|
||||
describe('stores/queue', () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
describe("#all", () => {
|
||||
it("correctly returns all queued songs", () => {
|
||||
queueStore.all.should.equal(songs);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#first', () => {
|
||||
it('correctly returns the first queued song', () => {
|
||||
queueStore.first.title.should.equal('No bravery')
|
||||
})
|
||||
})
|
||||
describe("#first", () => {
|
||||
it("correctly returns the first queued song", () => {
|
||||
queueStore.first.title.should.equal("No bravery");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#last', () => {
|
||||
it('correctly returns the last queued song', () => {
|
||||
queueStore.last.title.should.equal('Tears and rain')
|
||||
})
|
||||
})
|
||||
describe("#last", () => {
|
||||
it("correctly returns the last queued song", () => {
|
||||
queueStore.last.title.should.equal("Tears and rain");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#queue', () => {
|
||||
beforeEach(() => queueStore.state.songs = songs)
|
||||
describe("#queue", () => {
|
||||
beforeEach(() => (queueStore.state.songs = songs));
|
||||
|
||||
const song = allSongs[0]
|
||||
const song = allSongs[0];
|
||||
|
||||
it('correctly appends a song to end of the queue', () => {
|
||||
queueStore.queue(song)
|
||||
queueStore.last.title.should.equal('I Swear')
|
||||
})
|
||||
it("correctly appends a song to end of the queue", () => {
|
||||
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')
|
||||
})
|
||||
it("correctly prepends a song to top of the queue", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
it("correctly replaces the whole queue", () => {
|
||||
queueStore.queue(song, true);
|
||||
queueStore.all.length.should.equal(1);
|
||||
queueStore.first.title.should.equal("I Swear");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#unqueue', () => {
|
||||
beforeEach(() => queueStore.state.songs = songs)
|
||||
describe("#unqueue", () => {
|
||||
beforeEach(() => (queueStore.state.songs = songs));
|
||||
|
||||
it('correctly removes a song from queue', () => {
|
||||
queueStore.unqueue(queueStore.state.songs[0])
|
||||
queueStore.first.title.should.equal('So long, Jimmy'); // Oh the irony.
|
||||
})
|
||||
it("correctly removes a song from queue", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
it("correctly removes mutiple songs from queue", () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
describe("#clear", () => {
|
||||
it("correctly clears all songs from queue", () => {
|
||||
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')
|
||||
})
|
||||
describe("#current", () => {
|
||||
it("returns the correct current song", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
it("successfully sets the current song", () => {
|
||||
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')
|
||||
})
|
||||
describe("#getNextSong", () => {
|
||||
it("correctly gets the next song in queue", () => {
|
||||
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]
|
||||
var result = queueStore.next === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
it("correctly returns null if at end of queue", () => {
|
||||
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')
|
||||
})
|
||||
describe("#getPrevSong", () => {
|
||||
it("correctly gets the previous song in queue", () => {
|
||||
queueStore.previous.title.should.equal("No bravery");
|
||||
});
|
||||
|
||||
it('correctly returns null if at end of queue', () => {
|
||||
queueStore.current = queueStore.state.songs[0]
|
||||
var result = queueStore.previous === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
it("correctly returns null if at end of queue", () => {
|
||||
queueStore.current = queueStore.state.songs[0];
|
||||
var result = queueStore.previous === null;
|
||||
result.should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,77 +1,97 @@
|
|||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
require("chai").should();
|
||||
import { cloneDeep, last } from "lodash";
|
||||
|
||||
import { songStore, albumStore, artistStore, preferenceStore } from '../../stores'
|
||||
import data from '../blobs/data'
|
||||
import {
|
||||
songStore,
|
||||
albumStore,
|
||||
artistStore,
|
||||
preferenceStore
|
||||
} from "../../stores";
|
||||
import data from "../blobs/data";
|
||||
|
||||
const { songs, artists, albums, interactions } = data
|
||||
const { songs, artists, albums, interactions } = data;
|
||||
|
||||
describe('stores/song', () => {
|
||||
describe("stores/song", () => {
|
||||
beforeEach(() => {
|
||||
artistStore.init(artists)
|
||||
albumStore.init(albums)
|
||||
songStore.init(songs)
|
||||
})
|
||||
artistStore.init(artists);
|
||||
albumStore.init(albums);
|
||||
songStore.init(songs);
|
||||
});
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers all songs', () => {
|
||||
songStore.state.songs.length.should.equal(14)
|
||||
})
|
||||
describe("#init", () => {
|
||||
it("correctly gathers all songs", () => {
|
||||
songStore.state.songs.length.should.equal(14);
|
||||
});
|
||||
|
||||
it ('coverts lengths to formatted lengths', () => {
|
||||
songStore.state.songs[0].fmtLength.should.be.a.string
|
||||
})
|
||||
it("coverts lengths to formatted lengths", () => {
|
||||
songStore.state.songs[0].fmtLength.should.be.a.string;
|
||||
});
|
||||
|
||||
it('correctly sets albums', () => {
|
||||
songStore.state.songs[0].album.id.should.equal(1193)
|
||||
})
|
||||
})
|
||||
it("correctly sets albums", () => {
|
||||
songStore.state.songs[0].album.id.should.equal(1193);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all songs', () => {
|
||||
songStore.all.length.should.equal(14)
|
||||
})
|
||||
})
|
||||
describe("#all", () => {
|
||||
it("correctly returns all songs", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
describe("#byId", () => {
|
||||
it("correctly gets a song by ID", () => {
|
||||
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")
|
||||
})
|
||||
})
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#initInteractions', () => {
|
||||
beforeEach(() => songStore.initInteractions(interactions))
|
||||
describe("#initInteractions", () => {
|
||||
beforeEach(() => songStore.initInteractions(interactions));
|
||||
|
||||
it('correctly sets interaction status', () => {
|
||||
const song = songStore.byId('cb7edeac1f097143e65b1b2cde102482')
|
||||
song.liked.should.be.true
|
||||
song.playCount.should.equal(3)
|
||||
})
|
||||
})
|
||||
it("correctly sets interaction status", () => {
|
||||
const song = songStore.byId("cb7edeac1f097143e65b1b2cde102482");
|
||||
song.liked.should.be.true;
|
||||
song.playCount.should.equal(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addRecentlyPlayed', () => {
|
||||
it('correctly adds a recently played song', () => {
|
||||
songStore.addRecentlyPlayed(songStore.byId('cb7edeac1f097143e65b1b2cde102482'))
|
||||
songStore.recentlyPlayed[0].id.should.equal('cb7edeac1f097143e65b1b2cde102482')
|
||||
preferenceStore.get('recent-songs')[0].should.equal('cb7edeac1f097143e65b1b2cde102482')
|
||||
})
|
||||
describe("#addRecentlyPlayed", () => {
|
||||
it("correctly adds a recently played song", () => {
|
||||
songStore.addRecentlyPlayed(
|
||||
songStore.byId("cb7edeac1f097143e65b1b2cde102482")
|
||||
);
|
||||
songStore.recentlyPlayed[0].id.should.equal(
|
||||
"cb7edeac1f097143e65b1b2cde102482"
|
||||
);
|
||||
preferenceStore
|
||||
.get("recent-songs")[0]
|
||||
.should.equal("cb7edeac1f097143e65b1b2cde102482");
|
||||
});
|
||||
|
||||
it('correctly gathers the songs from local storage', () => {
|
||||
songStore.gatherRecentlyPlayedFromLocalStorage()[0].id.should.equal('cb7edeac1f097143e65b1b2cde102482')
|
||||
})
|
||||
})
|
||||
it("correctly gathers the songs from local storage", () => {
|
||||
songStore
|
||||
.gatherRecentlyPlayedFromLocalStorage()[0]
|
||||
.id.should.equal("cb7edeac1f097143e65b1b2cde102482");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#guess', () => {
|
||||
it('correcty guesses a song', () => {
|
||||
songStore.guess('i swear', albumStore.byId(1193)).id.should.equal('39189f4545f9d5671fb3dc964f0080a0')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe("#guess", () => {
|
||||
it("correcty guesses a song", () => {
|
||||
songStore
|
||||
.guess("i swear", albumStore.byId(1193))
|
||||
.id.should.equal("39189f4545f9d5671fb3dc964f0080a0");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,52 +1,56 @@
|
|||
require('chai').should()
|
||||
require("chai").should();
|
||||
|
||||
import { userStore } from '../../stores'
|
||||
import data from '../blobs/data'
|
||||
import { userStore } from "../../stores";
|
||||
import data from "../blobs/data";
|
||||
|
||||
const { users } = data
|
||||
const { users } = data;
|
||||
|
||||
describe('stores/user', () => {
|
||||
beforeEach(() => userStore.init(data.users, data.currentUser))
|
||||
describe("stores/user", () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
describe("#init", () => {
|
||||
it("correctly sets data state", () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
describe("#all", () => {
|
||||
it("correctly returns all users", () => {
|
||||
userStore.all.should.equal(data.users);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets a user by ID', () => {
|
||||
userStore.byId(1).should.equal(data.users[0])
|
||||
})
|
||||
})
|
||||
describe("#byId", () => {
|
||||
it("correctly gets a user by ID", () => {
|
||||
userStore.byId(1).should.equal(data.users[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#current', () => {
|
||||
it('correctly gets the current user', () => {
|
||||
userStore.current.id.should.equal(1)
|
||||
})
|
||||
describe("#current", () => {
|
||||
it("correctly gets the current user", () => {
|
||||
userStore.current.id.should.equal(1);
|
||||
});
|
||||
|
||||
it('correctly sets the current user', () => {
|
||||
userStore.current = data.users[1]
|
||||
userStore.current.id.should.equal(2)
|
||||
})
|
||||
})
|
||||
it("correctly sets the current user", () => {
|
||||
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')
|
||||
})
|
||||
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"
|
||||
);
|
||||
});
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
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"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
describe("services/utils", () => {
|
||||
describe("#secondsToHis", () => {
|
||||
it("correctly formats a duration to H:i:s", () => {
|
||||
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')
|
||||
})
|
||||
})
|
||||
it("ommits hours from short duration when formats to H:i:s", () => {
|
||||
secondsToHis(314).should.equal("05:14");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parseValidationError', () => {
|
||||
it('correctly parses single-level validation error', () => {
|
||||
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', () => {
|
||||
it("correctly parses multi-level validation error", () => {
|
||||
const error = {
|
||||
err_1: ['Foo', 'Bar'],
|
||||
err_2: ['Baz', 'Qux']
|
||||
}
|
||||
err_1: ["Foo", "Bar"],
|
||||
err_2: ["Baz", "Qux"]
|
||||
};
|
||||
|
||||
parseValidationError(error).should.eql(['Foo', 'Bar', 'Baz', 'Qux'])
|
||||
})
|
||||
})
|
||||
})
|
||||
parseValidationError(error).should.eql(["Foo", "Bar", "Baz", "Qux"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,52 +3,59 @@
|
|||
* @type {Object}
|
||||
*/
|
||||
export const $ = {
|
||||
is (el, selector) {
|
||||
is(el, selector) {
|
||||
return (el.matches ||
|
||||
el.matchesSelector ||
|
||||
el.msMatchesSelector ||
|
||||
el.mozMatchesSelector ||
|
||||
el.webkitMatchesSelector ||
|
||||
el.oMatchesSelector).call(el, selector)
|
||||
el.oMatchesSelector
|
||||
).call(el, selector);
|
||||
},
|
||||
|
||||
addClass (el, className) {
|
||||
addClass(el, className) {
|
||||
if (!el) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(className)
|
||||
el.classList.add(className);
|
||||
} else {
|
||||
el.className += ` ${className}`
|
||||
el.className += ` ${className}`;
|
||||
}
|
||||
},
|
||||
|
||||
removeClass (el, className) {
|
||||
removeClass(el, className) {
|
||||
if (!el) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(className)
|
||||
el.classList.remove(className);
|
||||
} else {
|
||||
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ')
|
||||
el.className = el.className.replace(
|
||||
new RegExp(
|
||||
"(^|\\b)" + className.split(" ").join("|") + "(\\b|$)",
|
||||
"gi"
|
||||
),
|
||||
" "
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
scrollTo (el, to, duration, cb = null) {
|
||||
scrollTo(el, to, duration, cb = null) {
|
||||
if (duration <= 0 || !el) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const difference = to - el.scrollTop
|
||||
const perTick = difference / duration * 10
|
||||
const difference = to - el.scrollTop;
|
||||
const perTick = difference / duration * 10;
|
||||
window.setTimeout(() => {
|
||||
el.scrollTop = el.scrollTop + perTick
|
||||
el.scrollTop = el.scrollTop + perTick;
|
||||
if (el.scrollTop === to) {
|
||||
cb && cb()
|
||||
return
|
||||
cb && cb();
|
||||
return;
|
||||
}
|
||||
this.scrollTo(el, to, duration - 10)
|
||||
}, 10)
|
||||
this.scrollTo(el, to, duration - 10);
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import alertify from 'alertify.js'
|
||||
import alertify from "alertify.js";
|
||||
|
||||
const alerts = {
|
||||
alert (msg) {
|
||||
alertify.alert(msg)
|
||||
alert(msg) {
|
||||
alertify.alert(msg);
|
||||
},
|
||||
|
||||
confirm (msg, okFunc, cancelFunc = null) {
|
||||
alertify.confirm(msg, okFunc, cancelFunc)
|
||||
confirm(msg, okFunc, cancelFunc = null) {
|
||||
alertify.confirm(msg, okFunc, cancelFunc);
|
||||
},
|
||||
|
||||
log (msg, type, cb = null) {
|
||||
alertify.logPosition('top right')
|
||||
alertify.closeLogOnClick(true)
|
||||
log(msg, type, cb = null) {
|
||||
alertify.logPosition("top right");
|
||||
alertify.closeLogOnClick(true);
|
||||
switch (type) {
|
||||
case 'success':
|
||||
alertify.success(msg, cb)
|
||||
break
|
||||
case 'error':
|
||||
alertify.error(msg, cb)
|
||||
break
|
||||
case "success":
|
||||
alertify.success(msg, cb);
|
||||
break;
|
||||
case "error":
|
||||
alertify.error(msg, cb);
|
||||
break;
|
||||
default:
|
||||
alertify.log(msg, cb)
|
||||
break
|
||||
alertify.log(msg, cb);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
success (msg, cb = null) {
|
||||
return this.log(msg, 'success', cb)
|
||||
success(msg, cb = null) {
|
||||
return this.log(msg, "success", cb);
|
||||
},
|
||||
|
||||
error (msg, cb = null) {
|
||||
return this.log(msg, 'error', cb)
|
||||
error(msg, cb = null) {
|
||||
return this.log(msg, "error", cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { alerts }
|
||||
export { alerts };
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* Other common methods.
|
||||
*/
|
||||
import select from 'select'
|
||||
import { event } from '@/utils'
|
||||
import select from "select";
|
||||
import { event } from "@/utils";
|
||||
|
||||
/**
|
||||
* Load (display) a main panel (view).
|
||||
|
@ -10,17 +10,17 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,15 +30,19 @@ 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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,10 +50,11 @@ export function hideOverlay () {
|
|||
*
|
||||
* @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");
|
||||
}
|
||||
|
|
|
@ -1,84 +1,84 @@
|
|||
import { each, isObject, isNumber, get } from 'lodash'
|
||||
import { each, isObject, isNumber, get } from "lodash";
|
||||
|
||||
export function orderBy (arr, sortKey, reverse) {
|
||||
export function orderBy(arr, sortKey, reverse) {
|
||||
if (!sortKey) {
|
||||
return arr
|
||||
return arr;
|
||||
}
|
||||
|
||||
const order = (reverse && reverse < 0) ? -1 : 1
|
||||
const order = reverse && reverse < 0 ? -1 : 1;
|
||||
|
||||
function compareRecordsByKey (a, b, key) {
|
||||
let aKey = isObject(a) ? get(a, key) : a
|
||||
let bKey = isObject(b) ? get(b, key) : b
|
||||
function compareRecordsByKey(a, b, key) {
|
||||
let aKey = isObject(a) ? get(a, key) : a;
|
||||
let bKey = isObject(b) ? get(b, key) : b;
|
||||
|
||||
if (isNumber(aKey) && isNumber(bKey)) {
|
||||
return aKey === bKey ? 0 : aKey > bKey
|
||||
return aKey === bKey ? 0 : aKey > bKey;
|
||||
}
|
||||
|
||||
aKey = aKey === undefined ? aKey : `${aKey}`.toLowerCase()
|
||||
bKey = bKey === undefined ? bKey : `${bKey}`.toLowerCase()
|
||||
aKey = aKey === undefined ? aKey : `${aKey}`.toLowerCase();
|
||||
bKey = bKey === undefined ? bKey : `${bKey}`.toLowerCase();
|
||||
|
||||
return aKey === bKey ? 0 : aKey > bKey
|
||||
return aKey === bKey ? 0 : aKey > bKey;
|
||||
}
|
||||
|
||||
// sort on a copy to avoid mutating original array
|
||||
return arr.slice().sort((a, b) => {
|
||||
if (sortKey.constructor === Array) {
|
||||
let diff = 0
|
||||
let diff = 0;
|
||||
for (let i = 0; i < sortKey.length; i++) {
|
||||
diff = compareRecordsByKey(a, b, sortKey[i])
|
||||
diff = compareRecordsByKey(a, b, sortKey[i]);
|
||||
if (diff !== 0) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return diff === 0 ? 0 : diff === true ? order : -order
|
||||
return diff === 0 ? 0 : diff === true ? order : -order;
|
||||
}
|
||||
|
||||
a = isObject(a) ? get(a, sortKey) : a
|
||||
b = isObject(b) ? get(b, sortKey) : b
|
||||
a = isObject(a) ? get(a, sortKey) : a;
|
||||
b = isObject(b) ? get(b, sortKey) : b;
|
||||
|
||||
if (isNumber(a) && isNumber(b)) {
|
||||
return a === b ? 0 : a > b ? order : -order
|
||||
return a === b ? 0 : a > b ? order : -order;
|
||||
}
|
||||
|
||||
a = a === undefined ? a : a.toLowerCase()
|
||||
b = b === undefined ? b : b.toLowerCase()
|
||||
a = a === undefined ? a : a.toLowerCase();
|
||||
b = b === undefined ? b : b.toLowerCase();
|
||||
|
||||
return a === b ? 0 : a > b ? order : -order
|
||||
})
|
||||
return a === b ? 0 : a > b ? order : -order;
|
||||
});
|
||||
}
|
||||
|
||||
export function limitBy (arr, n, offset = 0) {
|
||||
return arr.slice(offset, offset + n)
|
||||
export function limitBy(arr, n, offset = 0) {
|
||||
return arr.slice(offset, offset + n);
|
||||
}
|
||||
|
||||
export function filterBy (arr, search, ...keys) {
|
||||
export function filterBy(arr, search, ...keys) {
|
||||
if (!search) {
|
||||
return arr
|
||||
return arr;
|
||||
}
|
||||
|
||||
// cast to lowercase string
|
||||
search = (`${search}`).toLowerCase()
|
||||
search = `${search}`.toLowerCase();
|
||||
|
||||
const res = []
|
||||
const res = [];
|
||||
|
||||
each(arr, item => {
|
||||
each(keys, key => {
|
||||
if (`${get(item, key)}`.toLowerCase().indexOf(search) !== -1) {
|
||||
res.push(item)
|
||||
return false
|
||||
res.push(item);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
export function pluralize () {
|
||||
export function pluralize() {
|
||||
if (!arguments[0] || arguments[0] > 1) {
|
||||
return `${arguments[0]} ${arguments[1]}s`
|
||||
return `${arguments[0]} ${arguments[1]}s`;
|
||||
}
|
||||
|
||||
return `${arguments[0]} ${arguments[1]}`
|
||||
return `${arguments[0]} ${arguments[1]}`;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
* Convert a duration in seconds into H:i:s format.
|
||||
* If H is 0, it will be ommited.
|
||||
*/
|
||||
export function secondsToHis (d) {
|
||||
d = ~~d
|
||||
export function secondsToHis(d) {
|
||||
d = ~~d;
|
||||
|
||||
let s = d % 60
|
||||
let s = d % 60;
|
||||
|
||||
if (s < 10) {
|
||||
s = '0' + s
|
||||
s = "0" + s;
|
||||
}
|
||||
|
||||
let i = Math.floor((d / 60) % 60)
|
||||
let i = Math.floor((d / 60) % 60);
|
||||
|
||||
if (i < 10) {
|
||||
i = '0' + i
|
||||
i = "0" + i;
|
||||
}
|
||||
|
||||
let h = Math.floor(d / 3600)
|
||||
let h = Math.floor(d / 3600);
|
||||
|
||||
if (h < 10) {
|
||||
h = '0' + h
|
||||
h = "0" + h;
|
||||
}
|
||||
|
||||
return (h === '00' ? '' : h + ':') + i + ':' + s
|
||||
return (h === "00" ? "" : h + ":") + i + ":" + s;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,8 +33,11 @@ export function secondsToHis (d) {
|
|||
*
|
||||
* @return {Array.<String>}
|
||||
*/
|
||||
export function parseValidationError (error) {
|
||||
return Object.keys(error).reduce((messages, field) => messages.concat(error[field]), [])
|
||||
export function parseValidationError(error) {
|
||||
return Object.keys(error).reduce(
|
||||
(messages, field) => messages.concat(error[field]),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +47,6 @@ export function parseValidationError (error) {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
export function br2nl (str) {
|
||||
return str.replace(/<br\s*[\/]?>/gi, '\n')
|
||||
export function br2nl(str) {
|
||||
return str.replace(/<br\s*[\/]?>/gi, "\n");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from './alerts'
|
||||
export * from './filters'
|
||||
export * from './formatters'
|
||||
export * from './supports'
|
||||
export * from './common'
|
||||
export * from './$'
|
||||
export * from "./alerts";
|
||||
export * from "./filters";
|
||||
export * from "./formatters";
|
||||
export * from "./supports";
|
||||
export * from "./common";
|
||||
export * from "./$";
|
||||
|
|
|
@ -1,51 +1,52 @@
|
|||
import isMobile from 'ismobilejs'
|
||||
import Vue from 'vue'
|
||||
import { each } from 'lodash'
|
||||
import isMobile from "ismobilejs";
|
||||
import Vue from "vue";
|
||||
import { each } from "lodash";
|
||||
|
||||
/**
|
||||
* Check if AudioContext is supported by the current browser.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isAudioContextSupported () {
|
||||
export function isAudioContextSupported() {
|
||||
// Apple device just doesn't love AudioContext that much.
|
||||
if (isMobile.apple.device) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const AudioContext = (window.AudioContext ||
|
||||
const AudioContext =
|
||||
window.AudioContext ||
|
||||
window.webkitAudioContext ||
|
||||
window.mozAudioContext ||
|
||||
window.oAudioContext ||
|
||||
window.msAudioContext)
|
||||
window.msAudioContext;
|
||||
|
||||
if (!AudioContext) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safari (MacOS & iOS alike) has webkitAudioContext, but is buggy.
|
||||
// @link http://caniuse.com/#search=audiocontext
|
||||
if (!(new AudioContext()).createMediaElementSource) {
|
||||
return false
|
||||
if (!new AudioContext().createMediaElementSource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if HTML5 clipboard can be used.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isClipboardSupported () {
|
||||
return 'execCommand' in document
|
||||
export function isClipboardSupported() {
|
||||
return "execCommand" in document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Media Session API is supported.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isMediaSessionSupported () {
|
||||
return 'mediaSession' in navigator
|
||||
export function isMediaSessionSupported() {
|
||||
return "mediaSession" in navigator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,28 +57,30 @@ export function isMediaSessionSupported () {
|
|||
const event = {
|
||||
bus: null,
|
||||
|
||||
init () {
|
||||
init() {
|
||||
if (!this.bus) {
|
||||
this.bus = new Vue()
|
||||
this.bus = new Vue();
|
||||
}
|
||||
|
||||
return this
|
||||
return this;
|
||||
},
|
||||
|
||||
emit (name, ...args) {
|
||||
this.bus.$emit(name, ...args)
|
||||
return this
|
||||
emit(name, ...args) {
|
||||
this.bus.$emit(name, ...args);
|
||||
return this;
|
||||
},
|
||||
|
||||
on () {
|
||||
on() {
|
||||
if (arguments.length === 2) {
|
||||
this.bus.$on(arguments[0], arguments[1])
|
||||
this.bus.$on(arguments[0], arguments[1]);
|
||||
} else {
|
||||
each(Object.keys(arguments[0]), key => this.bus.$on(key, arguments[0][key]))
|
||||
each(Object.keys(arguments[0]), key =>
|
||||
this.bus.$on(key, arguments[0][key])
|
||||
);
|
||||
}
|
||||
|
||||
return this
|
||||
return this;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { event }
|
||||
export { event };
|
||||
|
|
|
@ -1,33 +1,36 @@
|
|||
const mix = require('laravel-mix')
|
||||
const fs = require('fs')
|
||||
const mix = require("laravel-mix");
|
||||
const fs = require("fs");
|
||||
|
||||
mix.setResourceRoot('./public/')
|
||||
mix.setResourceRoot("./public/");
|
||||
|
||||
mix.config.detectHotReloading()
|
||||
mix.config.detectHotReloading();
|
||||
if (mix.config.hmr) {
|
||||
// There's a bug with Mix/copy plugin which prevents HMR from working:
|
||||
// https://github.com/JeffreyWay/laravel-mix/issues/150
|
||||
console.log('In HMR mode. If assets are missing, Ctr+C and run `yarn dev` first.')
|
||||
console.log(
|
||||
"In HMR mode. If assets are missing, Ctr+C and run `yarn dev` first."
|
||||
);
|
||||
|
||||
// Somehow public/hot isn't being removed by Mix. We'll handle it ourselves.
|
||||
process.on('SIGINT', () => {
|
||||
process.on("SIGINT", () => {
|
||||
try {
|
||||
fs.unlinkSync(mix.config.publicPath + '/hot')
|
||||
} catch (e) {
|
||||
}
|
||||
process.exit()
|
||||
})
|
||||
fs.unlinkSync(mix.config.publicPath + "/hot");
|
||||
} catch (e) {}
|
||||
process.exit();
|
||||
});
|
||||
} else {
|
||||
mix.copy('resources/assets/img', 'public/img', false)
|
||||
.copy('node_modules/font-awesome/fonts', 'public/fonts', false)
|
||||
mix
|
||||
.copy("resources/assets/img", "public/img", false)
|
||||
.copy("node_modules/font-awesome/fonts", "public/fonts", false);
|
||||
}
|
||||
|
||||
mix.js('resources/assets/js/app.js', 'public/js')
|
||||
.sass('resources/assets/sass/app.scss', 'public/css')
|
||||
.js('resources/assets/js/remote/app.js', 'public/js/remote')
|
||||
.sass('resources/assets/sass/remote.scss', 'public/css')
|
||||
mix
|
||||
.js("resources/assets/js/app.js", "public/js")
|
||||
.sass("resources/assets/sass/app.scss", "public/css")
|
||||
.js("resources/assets/js/remote/app.js", "public/js/remote")
|
||||
.sass("resources/assets/sass/remote.scss", "public/css");
|
||||
|
||||
if (mix.config.inProduction) {
|
||||
mix.version()
|
||||
mix.disableNotifications()
|
||||
mix.version();
|
||||
mix.disableNotifications();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue