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-07-01 13:31:34 +00:00
< a class = "active item" @ click = "selectedTab = 'library'" data -tab = " library " > < translate > Browse < / translate > < / a >
2018-03-23 14:54:04 +00:00
< a class = "item" @ click = "selectedTab = 'queue'" data -tab = " queue " >
2018-07-01 13:31:34 +00:00
< translate > Queue < / translate > & nbsp ;
2017-06-23 21:00:42 +00:00
< template v-if ="queue.tracks.length === 0" >
2018-07-01 13:31:34 +00:00
< translate > ( empty ) < / translate >
2017-06-23 21:00:42 +00:00
< / template >
2018-06-30 13:28:47 +00:00
< translate v -else : translate -params = " { index : queue.currentIndex + 1 , length : queue.tracks.length } " >
( % { index } of % { length } )
< / translate >
2017-06-23 21:00:42 +00:00
< / 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" >
2018-07-01 13:31:34 +00:00
< div class = "header" > < translate > My account < / translate > < / div >
2018-04-28 12:58:25 +00:00
< div class = "menu" >
2018-06-30 13:28:47 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}" >
< i class = "user icon" > < / i >
< translate : translate -params = " { username : $ store.state.auth.username } " >
Logged in as % { username }
< / translate >
< / r o u t e r - l i n k >
2018-07-01 13:31:34 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon" > < / i > < translate > Logout < / translate > < / r o u t e r - l i n k >
< router -link class = "item" v -else : to = "{name: 'login'}" > < i class = "sign in icon" > < / i > < translate > Login < / translate > < / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< / div >
< / div >
< div class = "item" >
2018-07-01 13:31:34 +00:00
< div class = "header" > < translate > Music < / translate > < / div >
2018-04-28 12:58:25 +00:00
< div class = "menu" >
2018-07-01 13:31:34 +00:00
< router -link class = "item" : to = "{path: '/library'}" > < i class = "sound icon" > < / i > < translate > Browse library < / translate > < / r o u t e r - l i n k >
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon" > < / i > < translate > Favorites < / translate > < / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< a
@ click = "$store.commit('playlists/chooseTrack', null)"
v - if = "$store.state.auth.authenticated"
class = "item" >
2018-07-01 13:31:34 +00:00
< i class = "list icon" > < / i > < translate > Playlists < / translate >
2018-04-28 12:58:25 +00:00
< / a >
< router -link
v - if = "$store.state.auth.authenticated"
2018-07-01 13:31:34 +00:00
class = "item" : to = "{path: '/activity'}" > < i class = "bell icon" > < / i > < translate > Activity < / translate > < / 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-07-01 13:31:34 +00:00
< div class = "header" > < translate > Administration < / translate > < / div >
2018-04-28 12:58:25 +00:00
< div class = "menu" >
2018-05-28 22:07:38 +00:00
< router -link
class = "item"
v - if = "$store.state.auth.availablePermissions['library']"
: to = "{name: 'manage.library.files'}" >
2018-07-01 13:31:34 +00:00
< i class = "book icon" > < / i > < translate > Library < / translate >
2018-06-21 21:26:17 +00:00
< div
: class = "['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']"
2018-07-01 19:50:50 +00:00
: title = "labels.pendingRequests" >
2018-06-21 21:26:17 +00:00
{ { $store . state . ui . notifications . importRequests } } < / div >
2018-05-28 22:07:38 +00:00
< / 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" >
2018-07-01 13:31:34 +00:00
< i class = "download icon" > < / i > < translate > Import music < / translate >
2018-05-24 20:39:32 +00:00
< / 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-07-01 13:31:34 +00:00
< i class = "sitemap icon" > < / i > < translate > Federation < / translate >
2018-04-29 21:19:09 +00:00
< div
2018-06-21 21:26:17 +00:00
: class = "['ui', {'teal': $store.state.ui.notifications.federation > 0}, 'label']"
2018-07-01 19:50:50 +00:00
: title = "labels.pendingFollows" >
2018-06-21 21:26:17 +00:00
{ { $store . state . ui . notifications . federation } } < / div >
2018-04-29 21:19:09 +00:00
< / 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'}" >
2018-07-01 13:31:34 +00:00
< i class = "settings icon" > < / i > < translate > Settings < / translate >
2018-05-17 21:40:41 +00:00
< / r o u t e r - l i n k >
2018-06-19 16:50:22 +00:00
< router -link
class = "item"
v - if = "$store.state.auth.availablePermissions['settings']"
2018-06-19 21:27:21 +00:00
: to = "{name: 'manage.users.users.list'}" >
2018-07-01 13:31:34 +00:00
< i class = "users icon" > < / i > < translate > Users < / translate >
2018-06-19 16:50:22 +00:00
< / 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-07-01 13:31:34 +00:00
< translate > Do you want to restore your previous queue ? < / translate >
2017-06-23 21:00:42 +00:00
< / div >
2018-06-30 13:28:47 +00:00
< p >
< translate
translate - plural = "%{ count } tracks"
: translate - n = "queue.previousQueue.tracks.length"
: translate - params = "{count: queue.previousQueue.tracks.length}" >
% { count } track
< / translate >
< / p >
2017-06-23 21:00:42 +00:00
< div class = "ui two buttons" >
2018-07-01 13:31:34 +00:00
< div @click ="queue.restore()" class = "ui basic inverted green button" > < translate > Yes < / translate > < / div >
< div @click ="queue.removePrevious()" class = "ui basic inverted red button" > < translate > No < / translate > < / 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" >
2018-06-22 21:29:54 +00:00
< draggable v-model ="tracks" element="tbody" @update="reorder" >
< tr @ click = "$store.dispatch('queue/currentIndex', index)" v-for ="(track, index) in 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" >
2018-06-23 05:25:27 +00:00
< img class = "ui mini image" v-if ="track.album.cover" :src="$store.getters['instance/absoluteUrl'](track.album.cover)" >
2017-06-28 17:34:05 +00:00
< 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-07-01 13:31:34 +00:00
< i class = "feed icon" > < / i > < translate > You have a radio playing < / translate >
2017-06-23 21:00:42 +00:00
< / div >
2018-07-01 13:31:34 +00:00
< p > < translate > New tracks will be appended here automatically . < / translate > < / p >
< div @click ="$store.dispatch('radios/stop')" class = "ui basic inverted red button" > < translate > Stop radio < / translate > < / 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'
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-06-22 21:29:54 +00:00
tracksChangeBuffer : null ,
2018-04-29 21:19:09 +00:00
isCollapsed : true ,
2018-06-21 21:26:17 +00:00
fetchInterval : null
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
} ) ,
2018-07-01 19:50:50 +00:00
labels ( ) {
let pendingRequests = this . $gettext ( 'Pending import requests' )
let pendingFollows = this . $gettext ( 'Pending follow requests' )
return {
pendingRequests ,
pendingFollows
}
} ,
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
2018-06-22 21:29:54 +00:00
} ,
tracks : {
get ( ) {
return this . $store . state . queue . tracks
} ,
set ( value ) {
this . tracksChangeBuffer = value
}
2018-04-29 21:19:09 +00:00
}
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 ( ) {
2018-06-21 21:26:17 +00:00
this . $store . dispatch ( 'ui/fetchFederationNotificationsCount' )
this . $store . dispatch ( 'ui/fetchImportRequestsCount' )
2018-04-29 21:19:09 +00:00
} ,
2018-04-23 16:32:27 +00:00
reorder : function ( event ) {
this . $store . commit ( 'queue/reorder' , {
2018-06-22 21:29:54 +00:00
tracks : this . tracksChangeBuffer , 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 >