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" >
2018-07-17 11:09:13 +00:00
< router -link :title ="'Funkwhale'" : to = "{name: logoUrl}" >
2017-06-23 21:00:42 +00:00
< 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-10-21 13:41:31 +00:00
< a class = "active item" href @ click.prevent.stop = " selectedTab = 'library' " data -tab = " library " > < translate > Browse < / translate > < / a >
< a class = "item" href @ click.prevent.stop = " 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 >
2018-07-17 11:09:13 +00:00
< img class = "ui right floated circular tiny avatar image" v -if = " $ store.state.auth.profile.avatar.square_crop " :src ="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" / >
2018-06-30 13:28:47 +00:00
< / r o u t e r - l i n k >
2018-09-12 07:09:30 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{path: '/settings'}"><i class="setting icon" > < / i > < translate > Settings < / translate > < / r o u t e r - l i n k >
2018-09-13 15:18:23 +00:00
< router -link class = "item" v-if ="$store.state.auth.authenticated" :to="{name: 'notifications'}" >
< i class = "feed icon" > < / i >
< translate > Notifications < / translate >
< div
v - if = "$store.state.ui.notifications.inbox > 0"
: class = "['ui', 'teal', 'label']" >
{ { $store . state . ui . notifications . inbox } } < / div >
< / 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 >
2018-09-07 15:08:07 +00:00
< template v-else >
< router -link class = "item" : to = "{name: 'login'}" > < i class = "sign in icon" > < / i > < translate > Login < / translate > < / r o u t e r - l i n k >
< router -link class = "item" : to = "{path: '/signup'}" >
2018-10-05 17:30:21 +00:00
< i class = "corner add icon" > < / i >
2018-09-07 15:08:07 +00:00
< translate > Create an account < / translate >
< / r o u t e r - l i n k >
< / template >
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-06 18:44:47 +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 >
2018-07-01 13:31:34 +00:00
< 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 >
2018-09-06 18:35:02 +00:00
< router -link
v - if = "$store.state.auth.authenticated"
class = "item" : to = "{name: 'content.index'}" > < i class = "upload icon" > < / i > < translate > Add content < / translate > < / r o u t e r - l i n k >
2018-04-28 12:58:25 +00:00
< / div >
< / div >
2018-11-03 22:07:47 +00:00
< div class = "item" v-if ="$store.state.auth.availablePermissions['settings']" >
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-17 21:40:41 +00:00
< router -link
class = "item"
: 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"
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-07-18 13:37:07 +00:00
< img class = "ui mini image" v-if ="track.album.cover && track.album.cover.original" :src="$store.getters['instance/absoluteUrl'](track.album.cover.small_square_crop)" >
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" >
2018-10-21 13:41:31 +00:00
< button class = "title reset ellipsis" >
< strong > { { track . title } } < / strong > < br / >
{ { track . artist . name } }
< / button >
2017-06-28 17:34:05 +00:00
< / 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 >
2018-10-21 13:41:31 +00:00
< button @click.stop ="cleanTrack(index)" : class = "['ui', {'inverted': index != queue.currentIndex}, 'really', 'tiny', 'basic', 'circular', 'icon', 'button']" >
< i class = "trash icon" > < / i >
< / button >
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-08-19 14:42:41 +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
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-06-22 21:29:54 +00:00
tracks : {
get ( ) {
return this . $store . state . queue . tracks
} ,
set ( value ) {
this . tracksChangeBuffer = value
}
2018-07-17 11:09:13 +00:00
} ,
logoUrl ( ) {
if ( this . $store . state . auth . authenticated ) {
return 'library.index'
} else {
return 'index'
}
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-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
} ,
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-07-11 13:58:02 +00:00
td : nth - child ( 2 ) {
width : 55 px ;
}
2017-06-23 21:00:42 +00:00
}
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-07-11 13:58:02 +00:00
. ui . mini . image {
width : 100 % ;
}
2018-03-17 11:59:50 +00:00
< / style >
< style lang = "scss" >
. sidebar {
. ui . search . input {
flex : 1 ;
. prompt {
border - radius : 0 ;
}
}
}
2018-07-17 11:09:13 +00:00
. ui . tiny . avatar . image {
2018-07-13 12:10:39 +00:00
position : relative ;
top : - 0.5 em ;
2018-07-17 11:09:13 +00:00
width : 3 em ;
2018-07-13 12:10:39 +00:00
}
2018-10-21 13:41:31 +00:00
: not ( . active ) button . title {
outline - color : white ;
}
2017-06-23 21:00:42 +00:00
< / style >