2022-04-15 14:24:30 +00:00
|
|
|
<template>
|
|
|
|
<div id="main" v-if="authenticated">
|
|
|
|
<Hotkeys/>
|
|
|
|
<EventListeners/>
|
|
|
|
<AppHeader/>
|
|
|
|
<MainWrapper/>
|
|
|
|
<AppFooter/>
|
|
|
|
<SupportKoel/>
|
|
|
|
<Overlay ref="overlay"/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
<div class="login-wrapper">
|
|
|
|
<LoginForm @loggedin="onUserLoggedIn"/>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2022-04-20 10:20:09 +00:00
|
|
|
<SongContextMenu ref="songContextMenu"/>
|
2022-04-20 10:27:10 +00:00
|
|
|
<AlbumContextMenu ref="albumContextMenu"/>
|
|
|
|
<ArtistContextMenu ref="artistContextMenu"/>
|
2022-04-15 14:24:30 +00:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import { defineAsyncComponent, nextTick, onMounted, ref } from 'vue'
|
|
|
|
|
2022-04-24 08:29:14 +00:00
|
|
|
import AppHeader from '@/components/layout/AppHeader.vue'
|
2022-04-15 14:24:30 +00:00
|
|
|
import AppFooter from '@/components/layout/app-footer/index.vue'
|
|
|
|
import EventListeners from '@/components/utils/event-listeners.vue'
|
2022-04-24 08:29:14 +00:00
|
|
|
import Hotkeys from '@/components/utils/HotkeyListener.vue'
|
2022-04-23 23:11:37 +00:00
|
|
|
import LoginForm from '@/components/auth/LoginForm.vue'
|
2022-04-15 14:24:30 +00:00
|
|
|
import MainWrapper from '@/components/layout/main-wrapper/index.vue'
|
2022-04-24 08:29:14 +00:00
|
|
|
import Overlay from '@/components/ui/Overlay.vue'
|
2022-04-15 14:24:30 +00:00
|
|
|
|
2022-04-20 09:37:22 +00:00
|
|
|
import { $, eventBus, hideOverlay, showOverlay, arrayify } from '@/utils'
|
2022-04-15 14:24:30 +00:00
|
|
|
import { favoriteStore, preferenceStore as preferences, queueStore, sharedStore } from '@/stores'
|
|
|
|
import { auth, playback, socket } from '@/services'
|
|
|
|
|
2022-04-20 10:20:09 +00:00
|
|
|
const SongContextMenu = defineAsyncComponent(() => import('@/components/song/SongContextMenu.vue'))
|
2022-04-20 10:27:10 +00:00
|
|
|
const AlbumContextMenu = defineAsyncComponent(() => import('@/components/album/AlbumContextMenu.vue'))
|
|
|
|
const ArtistContextMenu = defineAsyncComponent(() => import('@/components/artist/ArtistContextMenu.vue'))
|
2022-04-24 08:29:14 +00:00
|
|
|
const SupportKoel = defineAsyncComponent(() => import('@/components/meta/SupportKoel.vue'))
|
2022-04-15 14:24:30 +00:00
|
|
|
|
2022-04-15 17:00:08 +00:00
|
|
|
const overlay = ref<HTMLElement>()
|
|
|
|
const songContextMenu = ref<InstanceType<typeof SongContextMenu>>()
|
|
|
|
const albumContextMenu = ref<InstanceType<typeof AlbumContextMenu>>()
|
|
|
|
const artistContextMenu = ref<InstanceType<typeof ArtistContextMenu>>()
|
2022-04-15 14:24:30 +00:00
|
|
|
|
2022-04-20 10:27:10 +00:00
|
|
|
const authenticated = ref(false)
|
|
|
|
|
2022-04-15 14:24:30 +00:00
|
|
|
/**
|
|
|
|
* Request for notification permission if it's not provided and the user is OK with notifications.
|
|
|
|
*/
|
|
|
|
const requestNotificationPermission = async () => {
|
|
|
|
if (window.Notification && preferences.notify && window.Notification.permission !== 'granted') {
|
|
|
|
preferences.notify = await window.Notification.requestPermission() === 'denied'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const onUserLoggedIn = () => {
|
|
|
|
authenticated.value = true
|
|
|
|
init()
|
|
|
|
}
|
|
|
|
|
|
|
|
const subscribeToBroadcastEvents = () => {
|
|
|
|
socket.listen('SOCKET_TOGGLE_FAVORITE', (): void => {
|
|
|
|
if (queueStore.current) {
|
|
|
|
favoriteStore.toggleOne(queueStore.current)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
// The app has just been initialized, check if we can get the user data with an already existing token
|
|
|
|
if (auth.hasToken()) {
|
|
|
|
authenticated.value = true
|
|
|
|
await init()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add an ugly mac/non-mac class for OS-targeting styles.
|
|
|
|
// I'm crying inside.
|
|
|
|
$.addClass(document.documentElement, navigator.userAgent.includes('Mac') ? 'mac' : 'non-mac')
|
|
|
|
})
|
|
|
|
|
2022-04-20 09:37:22 +00:00
|
|
|
eventBus.on('SONG_CONTEXT_MENU_REQUESTED', async (e: MouseEvent, songs: Song | Song[]) => {
|
2022-04-15 14:24:30 +00:00
|
|
|
await nextTick()
|
2022-04-20 10:20:09 +00:00
|
|
|
songContextMenu.value?.open(e.pageY, e.pageX, { songs: arrayify(songs) })
|
2022-04-15 14:24:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
eventBus.on('ALBUM_CONTEXT_MENU_REQUESTED', async (e: MouseEvent, album: Album) => {
|
|
|
|
await nextTick()
|
2022-04-20 10:27:10 +00:00
|
|
|
albumContextMenu.value?.open(e.pageY, e.pageX, { album })
|
2022-04-15 14:24:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
eventBus.on('ARTIST_CONTEXT_MENU_REQUESTED', async (e: MouseEvent, artist: Artist) => {
|
|
|
|
await nextTick()
|
2022-04-20 10:27:10 +00:00
|
|
|
artistContextMenu.value?.open(e.pageY, e.pageX, { artist })
|
2022-04-15 14:24:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const init = async () => {
|
|
|
|
showOverlay()
|
|
|
|
await socket.init()
|
|
|
|
|
|
|
|
// Make the most important HTTP request to get all necessary data from the server.
|
|
|
|
// Afterwards, init all mandatory stores and services.
|
|
|
|
try {
|
|
|
|
await sharedStore.init()
|
|
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
playback.init()
|
|
|
|
hideOverlay()
|
|
|
|
requestNotificationPermission()
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', (e: BeforeUnloadEvent): void => {
|
|
|
|
if (!preferences.confirmClosing) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
e.returnValue = ''
|
|
|
|
})
|
|
|
|
|
|
|
|
subscribeToBroadcastEvents()
|
|
|
|
|
|
|
|
// Let all other components know we're ready.
|
|
|
|
eventBus.emit('KOEL_READY')
|
|
|
|
}, 100)
|
|
|
|
} catch (err) {
|
|
|
|
authenticated.value = false
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
2022-04-15 17:00:08 +00:00
|
|
|
@import "#/app.scss";
|
2022-04-15 14:24:30 +00:00
|
|
|
|
|
|
|
#dragGhost {
|
|
|
|
display: inline-block;
|
|
|
|
background: var(--color-green);
|
|
|
|
padding: .8rem;
|
|
|
|
border-radius: .2rem;
|
|
|
|
color: var(--color-text-primary);
|
|
|
|
font-family: var(--font-family);
|
|
|
|
font-size: 1rem;
|
|
|
|
font-weight: var(--font-weight-light);
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
z-index: -1;
|
|
|
|
|
|
|
|
@media (hover: none) {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#copyArea {
|
|
|
|
position: absolute;
|
|
|
|
left: -9999px;
|
|
|
|
width: 1px;
|
|
|
|
height: 1px;
|
|
|
|
bottom: 1px;
|
|
|
|
|
|
|
|
@media (hover: none) {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#main, .login-wrapper {
|
|
|
|
display: flex;
|
|
|
|
height: 100vh;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
|
|
|
|
.login-wrapper {
|
|
|
|
@include vertical-center();
|
|
|
|
user-select: none;
|
|
|
|
padding-bottom: 0;
|
|
|
|
}
|
|
|
|
</style>
|