2017-06-23 21:00:42 +00:00
< template >
2018-02-15 21:34:59 +00:00
< div : class = "['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar',]" >
2017-06-23 21:00:42 +00:00
< div class = "ui inverted segment header-wrapper" >
2018-02-15 21:34:59 +00:00
< search -bar @ search = "isCollapsed = false" >
2017-06-23 21:00:42 +00:00
< router -link :title ="'Funkwhale'" : to = "{name: 'index'}" >
< i class = "logo bordered inverted orange big icon" >
< logo class = "logo" > < / logo >
< / i >
2018-02-18 13:30:45 +00:00
< / r o u t e r - l i n k > < s p a n
2018-02-15 21:34:59 +00:00
slot = "after"
@ click = "isCollapsed = !isCollapsed"
: class = "['ui', 'basic', 'big', {'inverted': isCollapsed}, 'orange', 'icon', 'collapse', 'button']" >
< i class = "sidebar icon" > < / i > < / span >
2017-06-23 21:00:42 +00:00
< / s e a r c h - b a r >
< / div >
< div class = "menu-area" >
< div class = "ui compact fluid two item inverted menu" >
2018-03-23 14:54:04 +00:00
< a class = "active item" @ click = "selectedTab = 'library'" data -tab = " library " > Browse < / a >
< a class = "item" @ click = "selectedTab = 'queue'" data -tab = " queue " >
2018-04-19 18:52:27 +00:00
{ { $t ( 'Queue' ) } }
2017-06-23 21:00:42 +00:00
< template v-if ="queue.tracks.length === 0" >
2018-04-19 18:52:27 +00:00
{ { $t ( '(empty)' ) } }
2017-06-23 21:00:42 +00:00
< / template >
< template v-else >
2018-04-19 18:52:27 +00:00
{ { $t ( '({%index%} of {%length%})' , { index : queue . currentIndex + 1 , length : queue . tracks . length } ) } }
2017-06-23 21:00:42 +00:00
< / template >
< / a >
< / div >
< / div >
< div class = "tabs" >
2017-06-29 21:26:57 +00:00
< div class = "ui bottom attached active tab" data -tab = " library " >
2018-04-29 21:19:09 +00:00
< div class = "ui inverted vertical large fluid menu" >
2018-04-28 12:58:25 +00:00
< div class = "item" >
< div class = "header" > { { $t ( 'My account' ) } } < / div >
< div class = "menu" >
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}"><i class="user icon" > < / i > {{ $ t ( ' Logged in as { % name % } ' , { name : $ store.state.auth.username } ) }} < / router -link >
2018-05-06 08:50:40 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon" > < / i > {{ $ t ( ' Logout ' ) }} < / router -link >
< router -link class = "item" v -else : to = "{name: 'login'}" > < i class = "sign in icon" > < / i > { { $t ( 'Login' ) } } < / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< / div >
< / div >
< div class = "item" >
< div class = "header" > { { $t ( 'Music' ) } } < / div >
< div class = "menu" >
< router -link class = "item" : to = "{path: '/library'}" > < i class = "sound icon" > < / i > { { $t ( 'Browse library' ) } } < / r o u t e r - l i n k >
2018-05-06 08:50:40 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon" > < / i > {{ $ t ( ' Favorites ' ) }} < / router -link >
2018-04-28 12:58:25 +00:00
< a
@ click = "$store.commit('playlists/chooseTrack', null)"
v - if = "$store.state.auth.authenticated"
class = "item" >
2018-05-06 08:50:40 +00:00
< i class = "list icon" > < / i > { { $t ( 'Playlists' ) } }
2018-04-28 12:58:25 +00:00
< / a >
< router -link
v - if = "$store.state.auth.authenticated"
2018-05-06 08:50:40 +00:00
class = "item" : to = "{path: '/activity'}" > < i class = "bell icon" > < / i > { { $t ( 'Activity' ) } } < / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< / div >
< / div >
2018-04-29 21:19:09 +00:00
< div class = "item" v-if ="showAdmin" >
2018-04-28 12:58:25 +00:00
< div class = "header" > { { $t ( 'Administration' ) } } < / div >
< div class = "menu" >
< router -link
class = "item"
2018-05-18 17:09:47 +00:00
v - if = "$store.state.auth.availablePermissions['library']"
2018-04-29 21:19:09 +00:00
: to = "{name: 'library.requests', query: {status: 'pending' }}" >
2018-05-06 08:50:40 +00:00
< i class = "download icon" > < / i > { { $t ( 'Import requests' ) } }
2018-04-29 21:19:09 +00:00
< div
: class = "['ui', {'teal': notifications.importRequests > 0}, 'label']"
: title = "$t('Pending import requests')" >
{ { notifications . importRequests } } < / div >
< / r o u t e r - l i n k >
2018-05-28 22:07:38 +00:00
< router -link
class = "item"
v - if = "$store.state.auth.availablePermissions['library']"
: to = "{name: 'manage.library.files'}" >
< i class = "book icon" > < / i > { { $t ( 'Library' ) } }
< / r o u t e r - l i n k >
2018-05-24 20:39:32 +00:00
< router -link
class = "item"
v - else - if = "$store.state.auth.availablePermissions['upload']"
to = "/library/import/launch" >
< i class = "download icon" > < / i > { { $t ( 'Import music' ) } }
< / r o u t e r - l i n k >
2018-04-29 21:19:09 +00:00
< router -link
class = "item"
2018-05-18 17:09:47 +00:00
v - if = "$store.state.auth.availablePermissions['federation']"
2018-04-29 21:19:09 +00:00
: to = "{path: '/manage/federation/libraries'}" >
2018-05-06 08:50:40 +00:00
< i class = "sitemap icon" > < / i > { { $t ( 'Federation' ) } }
2018-04-29 21:19:09 +00:00
< div
: class = "['ui', {'teal': notifications.federation > 0}, 'label']"
: title = "$t('Pending follow requests')" >
{ { notifications . federation } } < / div >
< / r o u t e r - l i n k >
2018-05-17 21:40:41 +00:00
< router -link
class = "item"
2018-05-18 17:09:47 +00:00
v - if = "$store.state.auth.availablePermissions['settings']"
2018-05-17 21:40:41 +00:00
: to = "{path: '/manage/settings'}" >
< i class = "settings icon" > < / i > { { $t ( 'Settings' ) } }
< / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< / div >
< / div >
2017-06-23 21:00:42 +00:00
< / div >
< / div >
< div v-if ="queue.previousQueue " class="ui black icon message" >
< i class = "history icon" > < / i >
< div class = "content" >
< div class = "header" >
2018-04-19 18:52:27 +00:00
{ { $t ( 'Do you want to restore your previous queue?' ) } }
2017-06-23 21:00:42 +00:00
< / div >
2018-04-19 18:52:27 +00:00
< p > { { $t ( '{%count%} tracks' , { count : queue . previousQueue . tracks . length } ) } } < / p >
2017-06-23 21:00:42 +00:00
< div class = "ui two buttons" >
2018-04-19 18:52:27 +00:00
< div @click ="queue.restore()" class = "ui basic inverted green button" > { { $t ( 'Yes' ) } } < / div >
< div @click ="queue.removePrevious()" class = "ui basic inverted red button" > { { $t ( 'No' ) } } < / div >
2017-06-23 21:00:42 +00:00
< / div >
< / div >
< / div >
< div class = "ui bottom attached tab" data -tab = " queue " >
2018-02-15 21:32:37 +00:00
< table class = "ui compact inverted very basic fixed single line unstackable table" >
2017-06-28 17:34:05 +00:00
< draggable v-model ="queue.tracks" element="tbody" @update="reorder" >
2017-12-23 15:41:19 +00:00
< tr @ click = "$store.dispatch('queue/currentIndex', index)" v-for ="(track, index) in queue.tracks" :key="index" :class="[{'active': index === queue.currentIndex}]" >
2017-06-28 17:34:05 +00:00
< td class = "right aligned" > { { index + 1 } } < / td >
< td class = "center aligned" >
< img class = "ui mini image" v-if ="track.album.cover" :src="backend.absoluteUrl(track.album.cover)" >
< img class = "ui mini image" v -else src = "../assets/audio/default-cover.png" >
< / td >
< td colspan = "4" >
< strong > { { track . title } } < / strong > < br / >
{ { track . artist . name } }
< / td >
< td >
2017-12-24 21:48:29 +00:00
< template v-if ="$store.getters['favorites/isFavorite'](track.id)" >
2017-12-23 15:41:19 +00:00
< i class = "pink heart icon" > < / i >
2018-03-17 11:07:20 +00:00
< / template >
2017-06-28 17:34:05 +00:00
< / td >
< td >
2017-12-23 15:41:19 +00:00
< i @click.stop ="cleanTrack(index)" class = "circular trash icon" > < / i >
2017-06-28 17:34:05 +00:00
< / td >
< / tr >
< / draggable >
2017-06-23 21:00:42 +00:00
< / table >
2017-12-23 15:41:19 +00:00
< div v-if ="$store.state.radios.running" class="ui black message" >
2017-06-23 21:00:42 +00:00
< div class = "content" >
< div class = "header" >
2018-04-19 18:52:27 +00:00
< i class = "feed icon" > < / i > { { $t ( 'You have a radio playing' ) } }
2017-06-23 21:00:42 +00:00
< / div >
2018-04-19 18:52:27 +00:00
< p > { { $t ( 'New tracks will be appended here automatically.' ) } } < / p >
< div @click ="$store.dispatch('radios/stop')" class = "ui basic inverted red button" > { { $t ( 'Stop radio' ) } } < / div >
2017-06-23 21:00:42 +00:00
< / div >
< / div >
< / div >
< / div >
2018-04-23 17:05:48 +00:00
< player @next ="scrollToCurrent" @previous ="scrollToCurrent" > < / player >
2017-06-23 21:00:42 +00:00
< / div >
< / template >
< script >
2017-12-23 15:41:19 +00:00
import { mapState , mapActions } from 'vuex'
2018-04-29 21:19:09 +00:00
import axios from 'axios'
2017-12-11 20:09:17 +00:00
2017-06-23 21:00:42 +00:00
import Player from '@/components/audio/Player'
import Logo from '@/components/Logo'
import SearchBar from '@/components/audio/SearchBar'
import backend from '@/audio/backend'
2017-06-28 17:34:05 +00:00
import draggable from 'vuedraggable'
2017-06-23 21:00:42 +00:00
import $ from 'jquery'
export default {
name : 'sidebar' ,
components : {
Player ,
SearchBar ,
2017-06-28 17:34:05 +00:00
Logo ,
2017-12-23 15:41:19 +00:00
draggable
2017-06-23 21:00:42 +00:00
} ,
data ( ) {
return {
2018-03-23 14:54:04 +00:00
selectedTab : 'library' ,
2018-02-15 21:34:59 +00:00
backend : backend ,
2018-04-29 21:19:09 +00:00
isCollapsed : true ,
fetchInterval : null ,
notifications : {
federation : 0 ,
importRequests : 0
}
2017-06-23 21:00:42 +00:00
}
} ,
mounted ( ) {
$ ( this . $el ) . find ( '.menu .item' ) . tab ( )
2017-06-28 17:34:05 +00:00
} ,
2018-04-29 21:19:09 +00:00
created ( ) {
this . fetchNotificationsCount ( )
this . fetchInterval = setInterval (
this . fetchNotificationsCount , 1000 * 60 * 15 )
} ,
destroy ( ) {
if ( this . fetchInterval ) {
clearInterval ( this . fetchInterval )
}
} ,
2017-12-23 15:41:19 +00:00
computed : {
... mapState ( {
2018-02-15 21:34:59 +00:00
queue : state => state . queue ,
url : state => state . route . path
2018-04-29 21:19:09 +00:00
} ) ,
showAdmin ( ) {
let adminPermissions = [
2018-05-18 17:09:47 +00:00
this . $store . state . auth . availablePermissions [ 'federation' ] ,
2018-05-24 20:39:32 +00:00
this . $store . state . auth . availablePermissions [ 'library' ] ,
this . $store . state . auth . availablePermissions [ 'upload' ]
2018-04-29 21:19:09 +00:00
]
return adminPermissions . filter ( e => {
return e
} ) . length > 0
}
2017-12-23 15:41:19 +00:00
} ,
2017-06-28 17:34:05 +00:00
methods : {
2017-12-23 15:41:19 +00:00
... mapActions ( {
cleanTrack : 'queue/cleanTrack'
} ) ,
2018-04-29 21:19:09 +00:00
fetchNotificationsCount ( ) {
this . fetchFederationNotificationsCount ( )
this . fetchFederationImportRequestsCount ( )
} ,
fetchFederationNotificationsCount ( ) {
2018-05-18 17:09:47 +00:00
if ( ! this . $store . state . auth . availablePermissions [ 'federation' ] ) {
2018-04-29 21:19:09 +00:00
return
}
let self = this
axios . get ( 'federation/libraries/followers/' , { params : { pending : true } } ) . then ( response => {
self . notifications . federation = response . data . count
} )
} ,
fetchFederationImportRequestsCount ( ) {
2018-05-18 17:09:47 +00:00
if ( ! this . $store . state . auth . availablePermissions [ 'library' ] ) {
2018-04-29 21:19:09 +00:00
return
}
let self = this
axios . get ( 'requests/import-requests/' , { params : { status : 'pending' } } ) . then ( response => {
self . notifications . importRequests = response . data . count
} )
} ,
2018-04-23 16:32:27 +00:00
reorder : function ( event ) {
this . $store . commit ( 'queue/reorder' , {
oldIndex : event . oldIndex , newIndex : event . newIndex } )
2018-03-23 14:54:04 +00:00
} ,
scrollToCurrent ( ) {
let current = $ ( this . $el ) . find ( '[data-tab="queue"] .active' ) [ 0 ]
if ( ! current ) {
return
}
let container = $ ( this . $el ) . find ( '.tabs' ) [ 0 ]
// Position container at the top line then scroll current into view
container . scrollTop = 0
current . scrollIntoView ( true )
// Scroll back nothing if element is at bottom of container else do it
// for half the height of the containers display area
var scrollBack = ( container . scrollHeight - container . scrollTop <= container . clientHeight ) ? 0 : container . clientHeight / 2
container . scrollTop = container . scrollTop - scrollBack
2017-06-28 17:34:05 +00:00
}
2018-02-15 21:34:59 +00:00
} ,
watch : {
url : function ( ) {
this . isCollapsed = true
2018-03-23 14:54:04 +00:00
} ,
selectedTab : function ( newValue ) {
if ( newValue === 'queue' ) {
this . scrollToCurrent ( )
}
} ,
'$store.state.queue.currentIndex' : function ( ) {
if ( this . selectedTab !== 'queue' ) {
this . scrollToCurrent ( )
}
2018-04-29 21:19:09 +00:00
} ,
2018-06-10 12:14:56 +00:00
'$store.state.auth.availablePermissions' : {
2018-04-29 21:19:09 +00:00
handler ( ) {
this . fetchNotificationsCount ( )
} ,
deep : true
2018-02-15 21:34:59 +00:00
}
2017-06-23 21:00:42 +00:00
}
}
< / script >
<!-- Add "scoped" attribute to limit CSS to this component only -- >
< style scoped lang = "scss" >
2018-02-15 21:34:59 +00:00
@ import '../style/vendor/media' ;
2017-06-23 21:00:42 +00:00
2018-04-29 21:19:09 +00:00
$sidebar - color : # 3 d3e3f ;
2017-06-23 21:00:42 +00:00
. sidebar {
2018-02-18 13:31:52 +00:00
background : $sidebar - color ;
2018-02-15 21:34:59 +00:00
@ include media ( ">tablet" ) {
display : flex ;
flex - direction : column ;
justify - content : space - between ;
}
@ include media ( ">desktop" ) {
. collapse . button {
2018-03-17 13:15:10 +00:00
display : none ! important ;
2018-02-15 21:34:59 +00:00
}
}
@ include media ( "<desktop" ) {
position : static ! important ;
width : 100 % ! important ;
& . collapsed {
. menu - area , . player - wrapper , . tabs {
display : none ;
}
}
}
2017-06-23 21:00:42 +00:00
> div {
margin : 0 ;
background - color : $sidebar - color ;
}
2018-03-17 12:37:47 +00:00
. menu . vertical {
background : $sidebar - color ;
2017-06-23 21:00:42 +00:00
}
}
. menu - area {
. menu . item : not ( . active ) : not ( : hover ) {
2018-03-17 11:59:50 +00:00
opacity : 0.75 ;
2017-06-23 21:00:42 +00:00
}
2018-03-17 11:29:58 +00:00
. menu . item {
border - radius : 0 ;
}
. menu . item . active {
background - color : $sidebar - color ;
& : hover {
2018-03-17 13:15:10 +00:00
background - color : rgba ( 255 , 255 , 255 , 0.06 ) ;
2018-03-17 11:29:58 +00:00
}
}
2017-06-23 21:00:42 +00:00
}
2018-04-28 12:58:25 +00:00
. vertical . menu {
. item . item {
font - size : 1 em ;
2018-04-29 21:19:09 +00:00
> i . icon {
float : none ;
margin : 0 0.5 em 0 0 ;
}
& : not ( . active ) {
color : rgba ( 255 , 255 , 255 , 0.75 ) ;
}
2018-04-28 12:58:25 +00:00
}
}
2017-06-23 21:00:42 +00:00
. tabs {
2018-03-23 14:54:04 +00:00
flex : 1 ;
display : flex ;
flex - direction : column ;
2017-06-23 21:00:42 +00:00
overflow - y : auto ;
2018-03-23 14:54:04 +00:00
justify - content : space - between ;
2018-02-15 21:34:59 +00:00
@ include media ( "<desktop" ) {
2018-03-23 14:54:04 +00:00
max - height : 500 px ;
2018-02-15 21:34:59 +00:00
}
2017-06-23 21:00:42 +00:00
}
2018-03-23 14:54:04 +00:00
. ui . tab . active {
display : flex ;
}
2017-06-23 21:00:42 +00:00
. tab [ data - tab = "queue" ] {
2018-03-23 14:54:04 +00:00
flex - direction : column ;
2017-06-23 21:00:42 +00:00
tr {
cursor : pointer ;
}
}
2018-03-23 14:54:04 +00:00
. tab [ data - tab = "library" ] {
flex - direction : column ;
flex : 1 1 auto ;
> . menu {
flex : 1 ;
flex - grow : 1 ;
}
> . player - wrapper {
width : 100 % ;
}
}
2017-06-23 21:00:42 +00:00
. sidebar . segment {
margin : 0 ;
border - radius : 0 ;
}
. ui . inverted . segment . header - wrapper {
padding : 0 ;
}
. logo {
cursor : pointer ;
display : inline - block ;
2018-03-17 11:59:50 +00:00
margin : 0 px ;
2017-06-23 21:00:42 +00:00
}
. ui . search {
2018-03-17 11:59:50 +00:00
display : flex ;
2018-03-17 13:15:10 +00:00
. collapse . button , . collapse . button : hover , . collapse . button : active {
box - shadow : none ! important ;
margin : 0 px ;
display : flex ;
flex - direction : column ;
justify - content : center ;
2018-02-15 21:34:59 +00:00
}
2017-06-23 21:00:42 +00:00
}
2018-03-17 11:59:50 +00:00
2018-03-18 07:36:05 +00:00
. ui . message . black {
background : $sidebar - color ;
}
2018-03-17 11:59:50 +00:00
< / style >
< style lang = "scss" >
. sidebar {
. ui . search . input {
flex : 1 ;
. prompt {
border - radius : 0 ;
}
}
}
2017-06-23 21:00:42 +00:00
< / style >