2015-12-13 04:42:28 +00:00
|
|
|
<template>
|
|
|
|
<div id="app" tabindex="0"
|
|
|
|
@keydown.space="togglePlayback"
|
|
|
|
@keydown.j = "playNext"
|
|
|
|
@keydown.k = "playPrev"
|
|
|
|
@keydown.f = "search"
|
2015-12-22 01:58:58 +00:00
|
|
|
@keydown.177 = "playPrev"
|
|
|
|
@keydown.176 = "playNext"
|
|
|
|
@keydown.179 = "togglePlayback"
|
2015-12-13 04:42:28 +00:00
|
|
|
>
|
|
|
|
<site-header></site-header>
|
|
|
|
<main-wrapper></main-wrapper>
|
|
|
|
<site-footer></site-footer>
|
2015-12-15 16:28:54 +00:00
|
|
|
<overlay :state.sync="overlayState"></overlay>
|
2015-12-13 04:42:28 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import $ from 'jquery';
|
|
|
|
|
|
|
|
import siteHeader from './components/site-header/index.vue';
|
|
|
|
import siteFooter from './components/site-footer/index.vue';
|
|
|
|
import mainWrapper from './components/main-wrapper/index.vue';
|
|
|
|
import overlay from './components/shared/overlay.vue';
|
|
|
|
|
|
|
|
import sharedStore from './stores/shared';
|
|
|
|
import artistStore from './stores/artist';
|
|
|
|
import playlistStore from './stores/playlist';
|
|
|
|
import queueStore from './stores/queue';
|
|
|
|
import userStore from './stores/user';
|
|
|
|
import settingStore from './stores/setting';
|
|
|
|
import preferenceStore from './stores/preference';
|
|
|
|
import playback from './services/playback';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
components: { siteHeader, siteFooter, mainWrapper, overlay },
|
|
|
|
|
|
|
|
replace: false,
|
|
|
|
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
prefs: preferenceStore.state,
|
2015-12-15 16:28:54 +00:00
|
|
|
|
|
|
|
overlayState: {
|
|
|
|
showing: true,
|
|
|
|
dismissable: false,
|
|
|
|
type: 'loading',
|
|
|
|
message: '',
|
|
|
|
},
|
2015-12-13 04:42:28 +00:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
ready() {
|
2015-12-15 16:28:54 +00:00
|
|
|
this.showOverlay();
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Make the most important HTTP request to get all necessary data from the server.
|
|
|
|
// Afterwards, init all mandatory stores and services.
|
|
|
|
sharedStore.init(() => {
|
|
|
|
this.initStores();
|
|
|
|
playback.init(this);
|
|
|
|
|
2015-12-15 16:28:54 +00:00
|
|
|
this.hideOverlay();
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Ask for user's notificatio permission.
|
|
|
|
this.requestNotifPermission();
|
|
|
|
|
|
|
|
// Let all other compoenents know we're ready.
|
|
|
|
this.$broadcast('koel:ready');
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
/**
|
|
|
|
* Initialize all stores to be used throughout the application.
|
|
|
|
*/
|
|
|
|
initStores() {
|
|
|
|
userStore.init();
|
|
|
|
preferenceStore.init();
|
|
|
|
|
|
|
|
// This will init album and song stores as well.
|
|
|
|
artistStore.init();
|
|
|
|
|
|
|
|
playlistStore.init();
|
|
|
|
queueStore.init();
|
|
|
|
settingStore.init();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle playback when user presses Space key.
|
|
|
|
*
|
|
|
|
* @param object e The keydown event
|
|
|
|
*/
|
|
|
|
togglePlayback(e) {
|
|
|
|
if ($(e.target).is('input,textarea,button,select')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ah... Good ol' jQuery. Whatever play/pause control is there, we blindly click it.
|
|
|
|
$('#mainFooter .play:visible, #mainFooter .pause:visible').click();
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Play the prev song when user presses K.
|
|
|
|
*
|
|
|
|
* @param object e The keydown event
|
|
|
|
*/
|
|
|
|
playPrev(e) {
|
|
|
|
if ($(e.target).is('input,textarea')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
playback.playPrev();
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Play the next song when user presses J.
|
|
|
|
*
|
|
|
|
* @param object e The keydown event
|
|
|
|
*/
|
|
|
|
playNext(e) {
|
|
|
|
if ($(e.target).is('input,textarea')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
playback.playNext();
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Put focus into the search field when user presses F.
|
|
|
|
*
|
|
|
|
* @param object e The keydown event
|
|
|
|
*/
|
|
|
|
search(e) {
|
|
|
|
if ($(e.target).is('input,textarea')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#searchForm input[type="search"]').focus().select();
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request for notification permission if it's not provided and the user is OK with notifs.
|
|
|
|
*/
|
|
|
|
requestNotifPermission() {
|
|
|
|
if (window.Notification && this.prefs.notify && Notification.permission !== 'granted') {
|
|
|
|
Notification.requestPermission(result => {
|
|
|
|
if (result === 'denied') {
|
|
|
|
preferenceStore.set('notify', false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load (display) a main panel (view).
|
|
|
|
*
|
|
|
|
* @param string view The view, which can be found under components/main-wrapper/main-content.
|
|
|
|
*/
|
|
|
|
loadMainView(view) {
|
|
|
|
this.$broadcast('main-content-view:load', view);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load a playlist into the main panel.
|
|
|
|
*
|
|
|
|
* @param object playlist The playlist object
|
|
|
|
*/
|
|
|
|
loadPlaylist(playlist) {
|
|
|
|
this.$broadcast('playlist:load', playlist);
|
|
|
|
this.loadMainView('playlist');
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load the Favorites view.
|
|
|
|
*/
|
|
|
|
loadFavorites() {
|
|
|
|
this.loadMainView('favorites');
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-12-15 16:28:54 +00:00
|
|
|
* Shows the overlay.
|
|
|
|
*
|
|
|
|
* @param {String} message The message to display.
|
|
|
|
* @param {String} type (loading|success|info|warning|error)
|
|
|
|
* @param {Boolean} dismissable Whether to show the Close button
|
|
|
|
*/
|
|
|
|
showOverlay(message = 'Just a little patience…', type = 'loading', dismissable = false) {
|
|
|
|
this.overlayState.message = message;
|
|
|
|
this.overlayState.type = type;
|
|
|
|
this.overlayState.dismissable = dismissable;
|
|
|
|
this.overlayState.showing = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the overlay.
|
2015-12-13 04:42:28 +00:00
|
|
|
*/
|
2015-12-15 16:28:54 +00:00
|
|
|
hideOverlay() {
|
|
|
|
this.overlayState.showing = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the close button, allowing the user to close the overlay.
|
|
|
|
*/
|
|
|
|
setOverlayDimissable() {
|
|
|
|
this.overlayState.dismissable = true;
|
|
|
|
},
|
2015-12-13 04:42:28 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2015-12-14 18:55:13 +00:00
|
|
|
/**
|
|
|
|
* Modified version of orderBy that is case insensitive
|
|
|
|
*
|
|
|
|
* @source https://github.com/vuejs/vue/blob/dev/src/filters/array-filters.js
|
|
|
|
*/
|
2015-12-15 12:26:03 +00:00
|
|
|
Vue.filter('caseInsensitiveOrderBy', (arr, sortKey, reverse) => {
|
2015-12-14 18:55:13 +00:00
|
|
|
if (!sortKey) {
|
2015-12-15 12:26:03 +00:00
|
|
|
return arr;
|
2015-12-14 18:55:13 +00:00
|
|
|
}
|
2015-12-15 12:26:03 +00:00
|
|
|
|
2015-12-14 18:55:13 +00:00
|
|
|
var order = (reverse && reverse < 0) ? -1 : 1
|
2015-12-15 12:26:03 +00:00
|
|
|
|
2015-12-14 18:55:13 +00:00
|
|
|
// sort on a copy to avoid mutating original array
|
2015-12-15 12:26:03 +00:00
|
|
|
return arr.slice().sort((a, b) => {
|
2015-12-14 19:54:56 +00:00
|
|
|
a = Vue.util.isObject(a) ? Vue.parsers.path.getPath(a, sortKey) : a
|
|
|
|
b = Vue.util.isObject(b) ? Vue.parsers.path.getPath(b, sortKey) : b
|
2015-12-14 18:55:13 +00:00
|
|
|
|
|
|
|
a = a === undefined ? a : a.toLowerCase()
|
|
|
|
b = b === undefined ? b : b.toLowerCase()
|
|
|
|
|
|
|
|
return a === b ? 0 : a > b ? order : -order
|
2015-12-15 12:26:03 +00:00
|
|
|
});
|
2015-12-14 18:55:13 +00:00
|
|
|
});
|
|
|
|
|
2015-12-13 04:42:28 +00:00
|
|
|
// Register the global directives
|
|
|
|
Vue.directive('koel-focus', require('./directives/focus'));
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="sass">
|
|
|
|
@import "resources/assets/sass/partials/_vars.scss";
|
|
|
|
@import "resources/assets/sass/partials/_mixins.scss";
|
2015-12-19 16:36:44 +00:00
|
|
|
@import "resources/assets/sass/partials/_shared.scss";
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
#app {
|
|
|
|
display: flex;
|
|
|
|
min-height: 100vh;
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
background: $colorMainBgr;
|
|
|
|
color: $colorMainText;
|
|
|
|
|
|
|
|
font-family: $fontFamily;
|
|
|
|
font-size: $fontSize;
|
|
|
|
line-height: $fontSize * 1.5;
|
|
|
|
font-weight: $fontWeight_Thin;
|
|
|
|
|
|
|
|
padding-bottom: $footerHeight;
|
|
|
|
}
|
|
|
|
</style>
|