koel/resources/assets/js/app.vue

332 lines
10 KiB
Vue
Raw Normal View History

2015-12-13 04:42:28 +00:00
<template>
2015-12-29 01:35:22 +00:00
<div id="app" tabindex="0" v-show="authenticated"
2015-12-13 04:42:28 +00:00
@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>
<overlay v-ref:overlay></overlay>
2016-03-05 09:01:12 +00:00
<edit-songs-form v-ref:edit-songs-form></edit-songs-form>
2015-12-13 04:42:28 +00:00
</div>
2015-12-29 01:16:36 +00:00
<div class="login-wrapper" v-else>
<login-form></login-form>
</div>
2015-12-13 04:42:28 +00:00
</template>
<script>
2016-02-09 04:57:08 +00:00
import Vue from 'vue';
2015-12-13 04:42:28 +00:00
import $ from 'jquery';
2015-12-13 04:42:28 +00:00
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';
2015-12-29 01:16:36 +00:00
import loginForm from './components/auth/login-form.vue';
2016-03-05 09:01:12 +00:00
import editSongsForm from './components/modals/edit-songs-form.vue';
2015-12-13 04:42:28 +00:00
import sharedStore from './stores/shared';
2015-12-30 04:14:47 +00:00
import queueStore from './stores/queue';
import songStore from './stores/song';
2016-01-25 10:37:14 +00:00
import userStore from './stores/user';
2016-04-05 09:19:20 +00:00
import preferences from './stores/preference';
2015-12-13 04:42:28 +00:00
import playback from './services/playback';
2016-01-03 08:23:29 +00:00
import focusDirective from './directives/focus';
2015-12-29 01:35:22 +00:00
import ls from './services/ls';
2016-03-24 04:37:39 +00:00
import { filterSongBy, caseInsensitiveOrderBy } from './filters/index';
2015-12-13 04:42:28 +00:00
export default {
2016-03-05 09:01:12 +00:00
components: { siteHeader, siteFooter, mainWrapper, overlay, loginForm, editSongsForm },
2015-12-13 04:42:28 +00:00
replace: false,
data() {
return {
2015-12-29 01:35:22 +00:00
authenticated: false,
2015-12-13 04:42:28 +00:00
};
},
ready() {
2015-12-29 01:35:22 +00:00
// The app has just been initialized, check if we can get the user data with an already existing token
2016-03-28 13:38:14 +00:00
const token = ls.get('jwt-token');
2015-12-29 01:35:22 +00:00
if (token) {
this.authenticated = true;
2015-12-29 01:16:36 +00:00
this.init();
}
// Create the element to be the ghost drag image.
$('<div id="dragGhost"></div>').appendTo('body');
2016-03-13 03:28:43 +00:00
// Add an ugly mac/non-mac class for OS-targeting styles.
// I'm crying inside.
2016-03-13 03:32:37 +00:00
$('html').addClass(navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac');
2015-12-29 01:16:36 +00:00
},
2015-12-13 04:42:28 +00:00
2015-12-29 01:16:36 +00:00
methods: {
init() {
this.showOverlay();
2015-12-13 04:42:28 +00:00
2015-12-29 01:16:36 +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(() => {
playback.init(this);
2015-12-13 04:42:28 +00:00
2015-12-29 01:16:36 +00:00
this.hideOverlay();
2015-12-13 04:42:28 +00:00
2016-01-01 07:47:03 +00:00
// Load the default view.
this.loadMainView('home');
2016-01-01 07:47:03 +00:00
// Ask for user's notification permission.
2015-12-29 01:16:36 +00:00
this.requestNotifPermission();
// To confirm or not to confirm closing, it's a question.
window.onbeforeunload = e => {
2016-04-05 09:19:20 +00:00
if (!preferences.confirmClosing) {
return;
}
return 'You asked Koel to confirm before closing, so here it is.';
};
2016-03-05 09:01:12 +00:00
// Let all other components know we're ready.
2015-12-29 01:16:36 +00:00
this.$broadcast('koel:ready');
2015-12-29 01:35:22 +00:00
}, () => this.authenticated = false);
2015-12-29 01:16:36 +00:00
},
2015-12-13 04:42:28 +00:00
/**
* Toggle playback when user presses Space key.
*
* @param {Object} e The keydown event
2015-12-13 04:42:28 +00:00
*/
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();
},
/**
2015-12-29 19:02:19 +00:00
* Play the previous song when user presses K.
2015-12-13 04:42:28 +00:00
*
* @param {Object} e The keydown event
2015-12-13 04:42:28 +00:00
*/
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
2015-12-13 04:42:28 +00:00
*/
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
2015-12-13 04:42:28 +00:00
*/
search(e) {
if ($(e.target).is('input,textarea') || e.metaKey || e.ctrlKey) {
2015-12-13 04:42:28 +00:00
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() {
2016-04-05 09:19:20 +00:00
if (window.Notification && preferences.notify && Notification.permission !== 'granted') {
2015-12-13 04:42:28 +00:00
Notification.requestPermission(result => {
if (result === 'denied') {
2016-04-05 09:19:20 +00:00
preferences.notify = false;
2015-12-13 04:42:28 +00:00
}
});
}
},
/**
* Load (display) a main panel (view).
*
2016-01-17 14:26:24 +00:00
* @param {String} view The view, which can be found under components/main-wrapper/main-content.
* @param {...*} Extra data to attach to the view.
2015-12-13 04:42:28 +00:00
*/
2016-01-15 08:13:23 +00:00
loadMainView(view, ...args) {
this.$broadcast('main-content-view:load', view, ...args);
2015-12-13 04:42:28 +00:00
},
/**
* Load a playlist into the main panel.
*
* @param {Object} playlist The playlist object
2015-12-13 04:42:28 +00:00
*/
loadPlaylist(playlist) {
2016-01-15 08:13:23 +00:00
this.loadMainView('playlist', playlist);
2015-12-13 04:42:28 +00:00
},
/**
* Load the Favorites view.
*/
loadFavorites() {
this.loadMainView('favorites');
},
2016-01-15 07:27:25 +00:00
/**
* Load an album into the main panel.
*
2016-01-15 07:27:25 +00:00
* @param {Object} album The album object
*/
loadAlbum(album) {
2016-01-15 08:13:23 +00:00
this.loadMainView('album', album);
2016-01-15 07:27:25 +00:00
},
/**
* Load an artist into the main panel.
*
2016-01-15 07:27:25 +00:00
* @param {Object} artist The artist object
*/
loadArtist(artist) {
2016-01-15 08:13:23 +00:00
this.loadMainView('artist', artist);
2016-01-15 07:27:25 +00:00
},
2015-12-13 04:42:28 +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.$refs.overlay.show(message, type, dismissable);
},
/**
* Hides the overlay.
2015-12-13 04:42:28 +00:00
*/
hideOverlay() {
this.$refs.overlay.hide();
},
/**
* Shows the close button, allowing the user to close the overlay.
*/
setOverlayDimissable() {
this.$refs.overlay.setDismissable();
},
2015-12-30 04:14:47 +00:00
2016-03-05 09:01:12 +00:00
/**
* Shows the "Edit Song" form.
*
* @param {Array.<Object>} An array of songs to edit
*/
showEditSongsForm(songs) {
this.$refs.editSongsForm.open(songs);
},
2015-12-30 04:14:47 +00:00
/**
* Log the current user out and reset the application state.
*/
logout() {
2016-01-25 10:37:14 +00:00
userStore.logout(() => {
ls.remove('jwt-token');
this.authenticated = false;
playback.stop();
queueStore.clear();
songStore.teardown();
2016-01-25 10:37:14 +00:00
this.loadMainView('queue');
this.$broadcast('koel:teardown');
2016-01-25 10:37:14 +00:00
});
2015-12-30 04:14:47 +00:00
},
2015-12-13 04:42:28 +00:00
},
2015-12-29 01:35:22 +00:00
events: {
/**
* When the user logs in, set the whole app to be "authenticated" and initialize it.
*/
2015-12-29 01:35:22 +00:00
'user:loggedin': function () {
this.authenticated = true;
this.init();
},
},
2015-12-13 04:42:28 +00:00
};
2016-03-24 04:37:39 +00:00
// Register our custom filters…
Vue.filter('filterSongBy', filterSongBy);
Vue.filter('caseInsensitiveOrderBy', caseInsensitiveOrderBy);
2016-03-24 04:37:39 +00:00
// …and the global directives
2016-01-03 08:23:29 +00:00
Vue.directive('koel-focus', focusDirective);
2015-12-13 04:42:28 +00:00
</script>
<style lang="sass">
@import "resources/assets/sass/partials/_vars.scss";
@import "resources/assets/sass/partials/_mixins.scss";
@import "resources/assets/sass/partials/_shared.scss";
2015-12-13 04:42:28 +00:00
#dragGhost {
position: relative;
display: inline-block;
background: $colorGreen;
padding: 10px;
border-radius: 3px;
color: #fff;
font-family: $fontFamily;
font-size: $fontSize;
font-weight: $fontWeight_Thin;
2016-02-14 13:07:07 +00:00
/**
* We can totally hide this element on touch devices, because there's
* no drag and drop support there anyway.
*/
html.touchevents & {
display: none;
}
}
2015-12-29 01:16:36 +00:00
#app, .login-wrapper {
2015-12-13 04:42:28 +00:00
display: flex;
min-height: 100vh;
flex-direction: column;
2015-12-13 04:42:28 +00:00
background: $colorMainBgr;
color: $colorMainText;
font-family: $fontFamily;
font-size: $fontSize;
line-height: $fontSize * 1.5;
font-weight: $fontWeight_Thin;
padding-bottom: $footerHeight;
}
2015-12-29 01:16:36 +00:00
.login-wrapper {
@include vertical-center();
2016-01-12 15:09:30 +00:00
padding-bottom: 0;
2015-12-29 01:16:36 +00:00
}
2015-12-13 04:42:28 +00:00
</style>