mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-05 08:29:40 +02:00
Resolve "Redesign the sidebar/navigation to simplify the UI"
This commit is contained in:
parent
cdd6f3d759
commit
e15d806634
38 changed files with 2073 additions and 1579 deletions
|
@ -1,216 +1,178 @@
|
|||
<template>
|
||||
<aside :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar',]">
|
||||
<header class="ui inverted segment header-wrapper">
|
||||
<search-bar @search="isCollapsed = false">
|
||||
<router-link :title="'Funkwhale'" :to="{name: logoUrl}">
|
||||
<i class="logo bordered inverted orange big icon">
|
||||
<logo class="logo"></logo>
|
||||
</i>
|
||||
</router-link><span
|
||||
slot="after"
|
||||
@click="isCollapsed = !isCollapsed"
|
||||
:class="['ui', 'basic', 'big', {'inverted': isCollapsed}, 'orange', 'icon', 'collapse', 'button']">
|
||||
<i class="sidebar icon"></i></span>
|
||||
</search-bar>
|
||||
</header>
|
||||
<header class="ui basic segment header-wrapper">
|
||||
<router-link :title="'Funkwhale'" :to="{name: logoUrl}">
|
||||
<i class="logo bordered inverted orange big icon">
|
||||
<logo class="logo"></logo>
|
||||
</i>
|
||||
</router-link>
|
||||
<router-link v-if="!$store.state.auth.authenticated" class="logo-wrapper" :to="{name: logoUrl}">
|
||||
<img src="../assets/logo/text-white.svg" />
|
||||
</router-link>
|
||||
<nav class="top ui compact right aligned inverted text menu">
|
||||
<template v-if="$store.state.auth.authenticated">
|
||||
|
||||
<div class="menu-area">
|
||||
<div class="ui compact fluid two item inverted menu">
|
||||
<a :class="[{active: selectedTab === 'library'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate translate-context="*/Library/*/Verb">Browse</translate></a>
|
||||
<a :class="[{active: selectedTab === 'queue'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
|
||||
<translate translate-context="Sidebar/Queue/Tab.Title/Noun">Queue</translate>
|
||||
<template v-if="queue.tracks.length === 0">
|
||||
<translate translate-context="Sidebar/Queue/Tab.Title">(empty)</translate>
|
||||
</template>
|
||||
<translate translate-context="Sidebar/Queue/Tab.Title" v-else :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
|
||||
(%{ index } of %{ length })
|
||||
</translate>
|
||||
</a>
|
||||
</div>
|
||||
<div class="right menu">
|
||||
<div class="item" :title="labels.administration" v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']">
|
||||
<div class="item ui inline admin-dropdown dropdown">
|
||||
<i class="wrench icon"></i>
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewEdits + $store.state.ui.notifications.pendingReviewReports > 0"
|
||||
:class="['ui', 'teal', 'mini', 'bottom floating', 'circular', 'label']">{{ $store.state.ui.notifications.pendingReviewEdits + $store.state.ui.notifications.pendingReviewReports }}</div>
|
||||
<div class="menu">
|
||||
<div class="header">
|
||||
<translate translate-context="Sidebar/Admin/Title/Noun">Administration</translate>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['library']"
|
||||
class="item"
|
||||
:to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}">
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewEdits > 0"
|
||||
:title="labels.pendingReviewEdits"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'teal', 'label']">
|
||||
{{ $store.state.ui.notifications.pendingReviewEdits }}</div>
|
||||
<translate translate-context="*/*/*/Noun">Library</translate>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['moderation']"
|
||||
class="item"
|
||||
:to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}">
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewReports > 0"
|
||||
:title="labels.pendingReviewReports"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'teal', 'label']">{{ $store.state.ui.notifications.pendingReviewReports }}</div>
|
||||
<translate translate-context="*/Moderation/*">Moderation</translate>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['settings']"
|
||||
class="item"
|
||||
:to="{name: 'manage.users.users.list'}">
|
||||
<translate translate-context="*/*/*/Noun">Users</translate>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['settings']"
|
||||
class="item"
|
||||
:to="{path: '/manage/settings'}">
|
||||
<translate translate-context="*/*/*/Noun">Settings</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
class="item"
|
||||
v-if="$store.state.auth.authenticated"
|
||||
:title="labels.addContent"
|
||||
:to="{name: 'content.index'}"><i class="upload icon"></i></router-link>
|
||||
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :title="labels.notifications" :to="{name: 'notifications'}">
|
||||
<i class="bell icon"></i><div
|
||||
v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
|
||||
:class="['ui', 'teal', 'mini', 'bottom floating', 'circular', 'label']">{{ $store.state.ui.notifications.inbox + additionalNotifications }}</div>
|
||||
</router-link>
|
||||
<div class="item">
|
||||
<div class="ui user-dropdown dropdown" >
|
||||
<img class="ui avatar image" v-if="$store.state.auth.profile.avatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
|
||||
<actor-avatar v-else :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username}" />
|
||||
<div class="menu">
|
||||
<router-link class="item" :to="{name: 'profile', params: {username: $store.state.auth.username}}"><translate translate-context="*/*/*/Noun">Profile</translate></router-link>
|
||||
<router-link class="item" :to="{path: '/settings'}"></i><translate translate-context="*/*/*/Noun">Settings</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'logout'}"></i><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="item collapse-button-wrapper">
|
||||
|
||||
<span
|
||||
@click="isCollapsed = !isCollapsed"
|
||||
:class="['ui', 'basic', 'big', {'orange': !isCollapsed}, 'inverted icon', 'collapse', 'button']">
|
||||
<i class="sidebar icon"></i></span>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="ui basic search-wrapper segment">
|
||||
<search-bar @search="isCollapsed = false"></search-bar>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<div v-if="!$store.state.auth.authenticated" class="ui basic signup segment">
|
||||
<router-link class="ui fluid tiny primary button" :to="{name: 'login'}"><translate translate-context="*/Login/*/Verb">Login</translate></router-link>
|
||||
<div class="ui small hidden divider"></div>
|
||||
<router-link class="ui fluid tiny button" :to="{path: '/signup'}">
|
||||
<translate translate-context="*/Signup/Link/Verb">Create an account</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
<nav class="secondary" role="navigation">
|
||||
<div class="ui small hidden divider"></div>
|
||||
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu">
|
||||
<nav class="ui inverted vertical large fluid menu" role="navigation" :aria-label="labels.mainMenu">
|
||||
<div class="item">
|
||||
<header class="header"><translate translate-context="Sidebar/Profile/Title">My account</translate></header>
|
||||
<nav class="ui vertical large fluid inverted menu" role="navigation" :aria-label="labels.mainMenu">
|
||||
<div :class="[{collapsed: !exploreExpanded}, 'collaspable item']">
|
||||
<header class="header" @click="exploreExpanded = true" tabindex="0" @focus="exploreExpanded = true">
|
||||
<translate translate-context="*/*/*/Verb">Explore</translate>
|
||||
<i class="angle right icon" v-if="!exploreExpanded"></i>
|
||||
</header>
|
||||
<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>
|
||||
<translate translate-context="Sidebar/Profile/List item.Link" :translate-params="{username: $store.state.auth.username}">
|
||||
Logged in as %{ username }
|
||||
</translate>
|
||||
<img class="ui right floated circular tiny avatar image" v-if="$store.state.auth.profile.avatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" />
|
||||
</router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/settings'}"><i class="setting icon"></i><translate translate-context="*/*/*/Noun">Settings</translate></router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'notifications'}">
|
||||
<i class="feed icon"></i>
|
||||
<translate translate-context="*/Notifications/*">Notifications</translate>
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
|
||||
:class="['ui', 'teal', 'label']">
|
||||
{{ $store.state.ui.notifications.inbox + additionalNotifications }}</div>
|
||||
</router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link>
|
||||
<template v-else>
|
||||
<router-link class="item" :to="{name: 'login'}"><i class="sign in icon"></i><translate translate-context="*/Login/*/Verb">Login</translate></router-link>
|
||||
<router-link class="item" :to="{path: '/signup'}">
|
||||
<i class="corner add icon"></i>
|
||||
<translate translate-context="*/Signup/Link/Verb">Create an account</translate>
|
||||
</router-link>
|
||||
</template>
|
||||
<router-link class="item" :exact="true" :to="{name: 'library.index'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.albums.browse'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.artists.browse'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.playlists.browse'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.radios.browse'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="[{collapsed: !myLibraryExpanded}, 'collaspable item']" v-if="$store.state.auth.authenticated">
|
||||
<header class="header" @click="myLibraryExpanded = true" tabindex="0" @focus="myLibraryExpanded = true">
|
||||
<translate translate-context="*/*/*/Noun">My Library</translate>
|
||||
<i class="angle right icon" v-if="!myLibraryExpanded"></i>
|
||||
</header>
|
||||
<div class="menu">
|
||||
<router-link class="item" :exact="true" :to="{name: 'library.me'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.albums.me'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.artists.me'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.playlists.me'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'library.radios.me'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
|
||||
<router-link class="item" :to="{name: 'favorites'}"><i class="heart icon"></i><translate translate-context="Sidebar/Favorites/List item.Link/Noun">Favorites</translate></router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<header class="header"><translate translate-context="*/*/*/Noun">Music</translate></header>
|
||||
<header class="header">
|
||||
<translate translate-context="Footer/About/List item.Link">More</translate>
|
||||
</header>
|
||||
<div class="menu">
|
||||
<router-link class="item" :to="{path: '/library'}"><i class="sound icon"></i><translate translate-context="Sidebar/Library/List item.Link/Verb">Browse library</translate></router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i><translate translate-context="Sidebar/Favorites/List item.Link/Noun">Favorites</translate></router-link>
|
||||
<a
|
||||
@click="$store.commit('playlists/chooseTrack', null)"
|
||||
v-if="$store.state.auth.authenticated"
|
||||
class="item">
|
||||
<i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="$store.state.auth.authenticated"
|
||||
class="item" :to="{name: 'content.index'}"><i class="upload icon"></i><translate translate-context="*/Library/*/Verb">Add content</translate></router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']">
|
||||
<header class="header"><translate translate-context="Sidebar/Admin/Title/Noun">Administration</translate></header>
|
||||
<div class="menu">
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['library']"
|
||||
class="item"
|
||||
:to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}">
|
||||
<i class="book icon"></i><translate translate-context="*/*/*/Noun">Library</translate>
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewEdits > 0"
|
||||
:title="labels.pendingReviewEdits"
|
||||
:class="['ui', 'teal', 'label']">
|
||||
{{ $store.state.ui.notifications.pendingReviewEdits }}</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['moderation']"
|
||||
class="item"
|
||||
:to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}">
|
||||
<i class="shield icon"></i><translate translate-context="*/Moderation/*">Moderation</translate>
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewReports > 0"
|
||||
:title="labels.pendingReviewReports"
|
||||
:class="['ui', 'teal', 'label']">{{ $store.state.ui.notifications.pendingReviewReports }}</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['settings']"
|
||||
class="item"
|
||||
:to="{name: 'manage.users.users.list'}">
|
||||
<i class="users icon"></i><translate translate-context="*/*/*/Noun">Users</translate>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="$store.state.auth.availablePermissions['settings']"
|
||||
class="item"
|
||||
:to="{path: '/manage/settings'}">
|
||||
<i class="settings icon"></i><translate translate-context="*/*/*/Noun">Settings</translate>
|
||||
<router-link class="item" to="/about">
|
||||
<i class="info icon"></i><translate translate-context="Sidebar/*/List item.Link">About this pod</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</section>
|
||||
<div v-if="queue.previousQueue " class="ui black icon message">
|
||||
<i class="history icon"></i>
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<translate translate-context="Sidebar/Queue/Message">Do you want to restore your previous queue?</translate>
|
||||
</div>
|
||||
<p>
|
||||
<translate translate-context="*/*/*"
|
||||
translate-plural="%{ count } tracks"
|
||||
:translate-n="queue.previousQueue.tracks.length"
|
||||
:translate-params="{count: queue.previousQueue.tracks.length}">
|
||||
%{ count } track
|
||||
</translate>
|
||||
</p>
|
||||
<div class="ui two buttons">
|
||||
<div @click="queue.restore()" class="ui basic inverted green button"><translate translate-context="*/*/*">Yes</translate></div>
|
||||
<div @click="queue.removePrevious()" class="ui basic inverted red button"><translate translate-context="*/*/*">No</translate></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'queue'}, 'tab']">
|
||||
<table class="ui compact inverted very basic fixed single line unstackable table">
|
||||
<draggable v-model="tracks" tag="tbody" @update="reorder">
|
||||
<tr
|
||||
@click="$store.dispatch('queue/currentIndex', index)"
|
||||
v-for="(track, index) in tracks"
|
||||
:key="index"
|
||||
:class="[{'active': index === queue.currentIndex}]">
|
||||
<td class="right aligned">{{ index + 1}}</td>
|
||||
<td class="center aligned">
|
||||
<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)">
|
||||
<img class="ui mini image" v-else src="../assets/audio/default-cover.png">
|
||||
</td>
|
||||
<td colspan="4">
|
||||
<button class="title reset ellipsis" :title="track.title" :aria-label="labels.selectTrack">
|
||||
<strong>{{ track.title }}</strong><br />
|
||||
<span>
|
||||
{{ track.artist.name }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="$store.getters['favorites/isFavorite'](track.id)">
|
||||
<i class="pink heart icon"></i>
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<button :title="labels.removeFromQueue" @click.stop="cleanTrack(index)" :class="['ui', {'inverted': index != queue.currentIndex}, 'really', 'tiny', 'basic', 'circular', 'icon', 'button']">
|
||||
<i class="trash icon"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</draggable>
|
||||
</table>
|
||||
<div v-if="$store.state.radios.running" class="ui black message">
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<i class="feed icon"></i> <translate translate-context="Sidebar/Player/Title">You have a radio playing</translate>
|
||||
</div>
|
||||
<p><translate translate-context="Sidebar/Player/Paragraph">New tracks will be appended here automatically.</translate></p>
|
||||
<div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate translate-context="*/Player/Button.Label/Short, Verb">Stop radio</translate></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<player @next="scrollToCurrent" @previous="scrollToCurrent"></player>
|
||||
</nav>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions, mapGetters } from "vuex"
|
||||
|
||||
import Player from "@/components/audio/Player"
|
||||
import Logo from "@/components/Logo"
|
||||
import SearchBar from "@/components/audio/SearchBar"
|
||||
import backend from "@/audio/backend"
|
||||
import draggable from "vuedraggable"
|
||||
|
||||
import $ from "jquery"
|
||||
|
||||
export default {
|
||||
name: "sidebar",
|
||||
components: {
|
||||
Player,
|
||||
SearchBar,
|
||||
Logo,
|
||||
draggable
|
||||
Logo
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedTab: "library",
|
||||
backend: backend,
|
||||
tracksChangeBuffer: null,
|
||||
isCollapsed: true,
|
||||
fetchInterval: null
|
||||
fetchInterval: null,
|
||||
exploreExpanded: false,
|
||||
myLibraryExpanded: false,
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
|
@ -218,6 +180,11 @@ export default {
|
|||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
document.getElementById('fake-sidebar').classList.add('loaded')
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
additionalNotifications: "ui/additionalNotifications",
|
||||
|
@ -235,15 +202,10 @@ export default {
|
|||
pendingFollows,
|
||||
mainMenu,
|
||||
selectTrack,
|
||||
pendingReviewEdits
|
||||
}
|
||||
},
|
||||
tracks: {
|
||||
get() {
|
||||
return this.$store.state.queue.tracks
|
||||
},
|
||||
set(value) {
|
||||
this.tracksChangeBuffer = value
|
||||
pendingReviewEdits,
|
||||
addContent: this.$pgettext("*/Library/*/Verb", 'Add content'),
|
||||
notifications: this.$pgettext("*/Notifications/*", 'Notifications'),
|
||||
administration: this.$pgettext("Sidebar/Admin/Title/Noun", 'Administration'),
|
||||
}
|
||||
},
|
||||
logoUrl() {
|
||||
|
@ -252,36 +214,42 @@ export default {
|
|||
} else {
|
||||
return "index"
|
||||
}
|
||||
},
|
||||
focusedMenu () {
|
||||
let mapping = {
|
||||
"library.index": 'exploreExpanded',
|
||||
"library.albums.browse": 'exploreExpanded',
|
||||
"library.albums.detail": 'exploreExpanded',
|
||||
"library.artists.browse": 'exploreExpanded',
|
||||
"library.artists.detail": 'exploreExpanded',
|
||||
"library.tracks.detail": 'exploreExpanded',
|
||||
"library.playlists.browse": 'exploreExpanded',
|
||||
"library.playlists.detail": 'exploreExpanded',
|
||||
"library.radios.browse": 'exploreExpanded',
|
||||
"library.radios.detail": 'exploreExpanded',
|
||||
'library.me': "myLibraryExpanded",
|
||||
'library.albums.me': "myLibraryExpanded",
|
||||
'library.artists.me': "myLibraryExpanded",
|
||||
'library.playlists.me': "myLibraryExpanded",
|
||||
'library.radios.me': "myLibraryExpanded",
|
||||
'favorites': "myLibraryExpanded",
|
||||
}
|
||||
let m = mapping[this.$route.name]
|
||||
if (m) {
|
||||
return m
|
||||
}
|
||||
|
||||
if (this.$store.state.auth.authenticated) {
|
||||
return 'myLibraryExpanded'
|
||||
} else {
|
||||
return 'exploreExpanded'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
cleanTrack: "queue/cleanTrack"
|
||||
}),
|
||||
reorder: function(event) {
|
||||
this.$store.commit("queue/reorder", {
|
||||
tracks: this.tracksChangeBuffer,
|
||||
oldIndex: event.oldIndex,
|
||||
newIndex: event.newIndex
|
||||
})
|
||||
},
|
||||
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
|
||||
},
|
||||
applyContentFilters () {
|
||||
let artistIds = this.$store.getters['moderation/artistFilters']().map((f) => {
|
||||
return f.target.id
|
||||
|
@ -303,26 +271,66 @@ export default {
|
|||
return await self.cleanTrack(realIndex)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
setupDropdown (selector) {
|
||||
let self = this
|
||||
$(self.$el).find(selector).dropdown({
|
||||
selectOnKeydown: false,
|
||||
action: function (text, value, $el) {
|
||||
// used ton ensure focusing the dropdown and clicking via keyboard
|
||||
// works as expected
|
||||
let link = $($el).closest('a')
|
||||
let url = link.attr('href')
|
||||
self.$router.push(url)
|
||||
$(self.$el).find(selector).dropdown('hide')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url: function() {
|
||||
this.isCollapsed = true
|
||||
},
|
||||
selectedTab: function(newValue) {
|
||||
if (newValue === "queue") {
|
||||
this.scrollToCurrent()
|
||||
}
|
||||
},
|
||||
"$store.state.queue.currentIndex": function() {
|
||||
if (this.selectedTab !== "queue") {
|
||||
this.scrollToCurrent()
|
||||
}
|
||||
},
|
||||
"$store.state.moderation.lastUpdate": function () {
|
||||
this.applyContentFilters()
|
||||
}
|
||||
},
|
||||
"$store.state.auth.authenticated": {
|
||||
immediate: true,
|
||||
handler (v) {
|
||||
if (v) {
|
||||
this.$nextTick(() => {
|
||||
this.setupDropdown('.user-dropdown')
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
"$store.state.auth.availablePermissions": {
|
||||
immediate: true,
|
||||
handler (v) {
|
||||
this.$nextTick(() => {
|
||||
this.setupDropdown('.admin-dropdown')
|
||||
})
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
focusedMenu: {
|
||||
immediate: true,
|
||||
handler (n) {
|
||||
if (n) {
|
||||
this[n] = true
|
||||
}
|
||||
}
|
||||
},
|
||||
myLibraryExpanded (v) {
|
||||
if (v) {
|
||||
this.exploreExpanded = false
|
||||
}
|
||||
},
|
||||
exploreExpanded (v) {
|
||||
if (v) {
|
||||
this.myLibraryExpanded = false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -331,16 +339,24 @@ export default {
|
|||
<style scoped lang="scss">
|
||||
@import "../style/vendor/media";
|
||||
|
||||
$sidebar-color: #3d3e3f;
|
||||
$sidebar-color: #2D2F33;
|
||||
|
||||
.sidebar {
|
||||
background: $sidebar-color;
|
||||
@include media(">tablet") {
|
||||
@include media(">desktop") {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 4em;
|
||||
}
|
||||
> nav {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@include media(">desktop") {
|
||||
.menu .item.collapse-button-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
.collapse.button {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -349,9 +365,10 @@ $sidebar-color: #3d3e3f;
|
|||
position: static !important;
|
||||
width: 100% !important;
|
||||
&.collapsed {
|
||||
.menu-area,
|
||||
.player-wrapper,
|
||||
.tabs {
|
||||
.search,
|
||||
.signup.segment,
|
||||
nav.secondary {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -366,23 +383,7 @@ $sidebar-color: #3d3e3f;
|
|||
}
|
||||
}
|
||||
|
||||
.menu-area {
|
||||
.menu .item:not(.active):not(:hover) {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.menu .item {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.menu .item.active {
|
||||
background-color: $sidebar-color;
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
.vertical.menu {
|
||||
.ui.vertical.menu {
|
||||
.item .item {
|
||||
font-size: 1em;
|
||||
> i.icon {
|
||||
|
@ -390,9 +391,29 @@ $sidebar-color: #3d3e3f;
|
|||
margin: 0 0.5em 0 0;
|
||||
}
|
||||
&:not(.active) {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
// color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
}
|
||||
.item.active {
|
||||
border-right: 5px solid #F2711C;
|
||||
border-radius: 0 !important;
|
||||
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
.item.collapsed {
|
||||
&:not(:focus) > .menu {
|
||||
display: none;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.collaspable.item .header {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ui.secondary.menu {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.tabs {
|
||||
flex: 1;
|
||||
|
@ -416,6 +437,10 @@ $sidebar-color: #3d3e3f;
|
|||
width: 55px;
|
||||
}
|
||||
}
|
||||
.item .header .angle.icon {
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
.tab[data-tab="library"] {
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
|
@ -432,8 +457,30 @@ $sidebar-color: #3d3e3f;
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ui.inverted.segment.header-wrapper {
|
||||
.ui.menu .item.inline.admin-dropdown.dropdown > .menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
.ui.segment.header-wrapper {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 4em;
|
||||
nav {
|
||||
> .item, > .menu > .item > .item {
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav.top.title-menu {
|
||||
flex-grow: 1;
|
||||
.item {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
@ -442,20 +489,14 @@ $sidebar-color: #3d3e3f;
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
.ui.search {
|
||||
display: flex;
|
||||
|
||||
.collapse.button,
|
||||
.collapse.button:hover,
|
||||
.collapse.button:active {
|
||||
box-shadow: none !important;
|
||||
margin: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
.collapsed .search-wrapper {
|
||||
@include media("<desktop") {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ui.search {
|
||||
display: flex;
|
||||
}
|
||||
.ui.message.black {
|
||||
background: $sidebar-color;
|
||||
}
|
||||
|
@ -463,10 +504,48 @@ $sidebar-color: #3d3e3f;
|
|||
.ui.mini.image {
|
||||
width: 100%;
|
||||
}
|
||||
nav.top {
|
||||
align-items: self-end;
|
||||
padding: 0.5em 0;
|
||||
> .item, > .right.menu > .item {
|
||||
// color: rgba(255, 255, 255, 0.9) !important;
|
||||
font-size: 1.2em;
|
||||
&:hover, > .dropdown > .icon {
|
||||
// color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
> .label, > .dropdown > .label {
|
||||
font-size: 0.5em;
|
||||
right: 1.7em;
|
||||
bottom: -0.5em;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui.user-dropdown > .text > .label {
|
||||
margin-right: 0;
|
||||
}
|
||||
.logo-wrapper {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
@include media("<desktop") {
|
||||
margin: 0;
|
||||
}
|
||||
img {
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@include media(">tablet") {
|
||||
img {
|
||||
height: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.sidebar {
|
||||
aside.ui.sidebar {
|
||||
overflow-y: visible !important;
|
||||
.ui.search .input {
|
||||
flex: 1;
|
||||
.prompt {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue