Commit e15d8066 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Resolve "Redesign the sidebar/navigation to simplify the UI"

parent cdd6f3d7
Brand new navigation, queue and player redesign (#594)
......@@ -6,6 +6,13 @@ Next release notes
Those release notes refer to the current development branch and are reset
after each release.
Redesigned navigation, player and queue
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This release includes a full redesign of our navigation, player and queue. Overall, it should provide
a better, less confusing experience, especially on mobile devices. This redesign was suggested
14 months ago, and took a while, but thanks to the involvement and feedback of many people, we got it done!
Improved search performance
^^^^^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -25,7 +25,7 @@
"qs": "^6.7.0",
"sanitize-html": "^1.20.1",
"showdown": "^1.8.6",
"vue": "^2.5.17",
"vue": "^2.6.10",
"vue-gettext": "^2.1.0",
"vue-lazyload": "^1.2.6",
"vue-masonry": "^0.11.5",
......@@ -50,6 +50,7 @@
"mocha": "^5.2.0",
"moxios": "^0.4.0",
"node-sass": "^4.9.3",
"preload-webpack-plugin": "^3.0.0-beta.4",
"purgecss-webpack-plugin": "^1.6.0",
"sass-loader": "^7.1.0",
"sinon": "^6.1.5",
......
......@@ -7,13 +7,85 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<title>Funkwhale</title>
<style>
#fake-app {
width: 100vw;
height: 100vh;
z-index: -1;
position: fixed;
top: 0;
left: 0;
display: flex;
font-family: sans-serif;
}
#fake-sidebar {
width: 275px;
height: 100vh;
background-color: #2D2F33;
}
#fake-sidebar.loaded, #fake-content.loaded {
display: none;
}
#orange-square {
width: 56px;
height: 56px;
background-color: #f2711c
}
#fake-content {
height: 100vh;
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#fake-content h1 {
margin-bottom: 2em;
}
#fake-content .placeholder {
width: 20em;
max-width: 95%;
}
@media only screen and (max-width: 768px) {
#fake-app {
flex-direction: column;
}
#fake-sidebar {
width: 100%;
height: 56px;
}
}
</style>
</head>
<body class="theme-light" id="body">
<noscript>
<strong>We're sorry but Funkwhale doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<div id="fake-app">
<div id="fake-sidebar">
<div id="orange-square"></div>
</div>
<div id="fake-content">
<noscript>
<strong>We're sorry but Funkwhale doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<h1>Loading Funkwhale…</h1>
<div class="ui placeholder">
<div class="image header">
<div class="full line"></div>
<div class="line"></div>
</div>
<div class="image header">
<div class="line"></div>
<div class="full line"></div>
</div>
<div class="image header">
<div class="medium line"></div>
<div class="full line"></div>
</div>
</div>
</div>
</div>
<div id="app">
</div>
<!-- built files will be auto injected -->
</body>
......
<template>
<div id="app" :key="String($store.state.instance.instanceUrl)">
<div id="app" :key="String($store.state.instance.instanceUrl)" :class="[$store.state.ui.queueFocused ? 'queue-focused' : '', {'has-bottom-player': $store.state.queue.tracks.length > 0}]">
<!-- here, we display custom stylesheets, if any -->
<link
v-for="url in customStylesheets"
......@@ -12,9 +12,13 @@
<sidebar></sidebar>
<set-instance-modal @update:show="showSetInstanceModal = $event" :show="showSetInstanceModal"></set-instance-modal>
<service-messages v-if="messages.length > 0"/>
<router-view :key="$route.fullPath"></router-view>
<div class="ui fitted divider"></div>
<transition name="queue">
<queue @touch-progress="$refs.player.setCurrentTime($event)" v-if="$store.state.ui.queueFocused"></queue>
</transition>
<router-view :class="{hidden: $store.state.ui.queueFocused}" :key="$route.fullPath"></router-view>
<player ref="player"></player>
<app-footer
:class="{hidden: $store.state.ui.queueFocused}"
:version="version"
@show:shortcuts-modal="showShortcutsModal = !showShortcutsModal"
@show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal"
......@@ -32,39 +36,33 @@
import Vue from 'vue'
import axios from 'axios'
import _ from '@/lodash'
import {mapState, mapGetters} from 'vuex'
import {mapState, mapGetters, mapActions} from 'vuex'
import { WebSocketBridge } from 'django-channels'
import GlobalEvents from '@/components/utils/global-events'
import Sidebar from '@/components/Sidebar'
import AppFooter from '@/components/Footer'
import ServiceMessages from '@/components/ServiceMessages'
import moment from 'moment'
import locales from './locales'
import PlaylistModal from '@/components/playlists/PlaylistModal'
import FilterModal from '@/components/moderation/FilterModal'
import ReportModal from '@/components/moderation/ReportModal'
import ShortcutsModal from '@/components/ShortcutsModal'
import SetInstanceModal from '@/components/SetInstanceModal'
export default {
name: 'app',
components: {
Sidebar,
AppFooter,
FilterModal,
ReportModal,
PlaylistModal,
ShortcutsModal,
Player: () => import(/* webpackChunkName: "audio" */ "@/components/audio/Player"),
Queue: () => import(/* webpackChunkName: "audio" */ "@/components/Queue"),
PlaylistModal: () => import(/* webpackChunkName: "auth-audio" */ "@/components/playlists/PlaylistModal"),
Sidebar: () => import(/* webpackChunkName: "core" */ "@/components/Sidebar"),
AppFooter: () => import(/* webpackChunkName: "core" */ "@/components/Footer"),
ServiceMessages: () => import(/* webpackChunkName: "core" */ "@/components/ServiceMessages"),
SetInstanceModal: () => import(/* webpackChunkName: "core" */ "@/components/SetInstanceModal"),
ShortcutsModal: () => import(/* webpackChunkName: "core" */ "@/components/ShortcutsModal"),
FilterModal: () => import(/* webpackChunkName: "moderation" */ "@/components/moderation/FilterModal"),
ReportModal: () => import(/* webpackChunkName: "moderation" */ "@/components/moderation/ReportModal"),
GlobalEvents,
ServiceMessages,
SetInstanceModal,
},
data () {
return {
bridge: null,
instanceUrl: null,
showShortcutsModal: false,
showSetInstanceModal: false,
showSetInstanceModal: false
}
},
async created () {
......@@ -82,6 +80,10 @@ export default {
if (serverUrl) {
this.$store.commit('instance/instanceUrl', serverUrl)
}
const url = urlParams.get('_url')
if (url) {
this.$router.replace(url)
}
else if (!this.$store.state.instance.instanceUrl) {
// we have several way to guess the API server url. By order of precedence:
// 1. use the url provided in settings.json, if any
......@@ -127,6 +129,9 @@ export default {
self.$router.push(event.target.getAttribute('href'))
event.preventDefault();
}, false);
this.$nextTick(() => {
document.getElementById('fake-content').classList.add('loaded')
})
},
destroyed () {
......@@ -238,10 +243,27 @@ export default {
...mapState({
messages: state => state.ui.messages,
nodeinfo: state => state.instance.nodeinfo,
playing: state => state.player.playing,
bufferProgress: state => state.player.bufferProgress,
isLoadingAudio: state => state.player.isLoadingAudio,
}),
...mapGetters({
currentTrack: 'queue/currentTrack'
hasNext: "queue/hasNext",
currentTrack: 'queue/currentTrack',
progress: "player/progress",
}),
labels() {
let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play track")
let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause track")
let next = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Next track")
let expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Expand queue")
return {
play,
pause,
next,
expandQueue,
}
},
suggestedInstances () {
let instances = this.$store.state.instance.knownInstances.slice(0)
if (this.$store.state.instance.frontSettings.defaultServerUrl) {
......@@ -264,7 +286,7 @@ export default {
if (this.$store.state.instance.frontSettings) {
return this.$store.state.instance.frontSettings.additionalStylesheets || []
}
}
},
},
watch: {
'$store.state.instance.instanceUrl' () {
......@@ -290,7 +312,7 @@ export default {
immediate: true,
handler(newValue) {
let self = this
import(`./translations/${newValue}.json`).then((response) =>{
import(/* webpackChunkName: "locale-[request]" */ `./translations/${newValue}.json`).then((response) =>{
Vue.$translations[newValue] = response.default[newValue]
}).finally(() => {
// set current language twice, otherwise we seem to have a cache somewhere
......@@ -302,12 +324,12 @@ export default {
return self.$store.commit('ui/momentLocale', 'en')
}
let momentLocale = newValue.replace('_', '-').toLowerCase()
import(`moment/locale/${momentLocale}.js`).then(() => {
import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${momentLocale}.js`).then(() => {
self.$store.commit('ui/momentLocale', momentLocale)
}).catch(() => {
console.log('No momentjs locale available for', momentLocale)
let shortLocale = momentLocale.split('-')[0]
import(`moment/locale/${shortLocale}.js`).then(() => {
import(/* webpackChunkName: "moment-locale-[request]" */ `moment/locale/${shortLocale}.js`).then(() => {
self.$store.commit('ui/momentLocale', shortLocale)
}).catch(() => {
console.log('No momentjs locale available for', shortLocale)
......@@ -333,4 +355,185 @@ export default {
<style lang="scss">
@import "style/_main";
.ui.bottom-player {
z-index: 999999;
width: 100%;
width: 100vw;
}
#app.queue-focused {
.queue-not-focused {
@include media("<desktop") {
display: none;
}
}
}
.when-queue-focused {
.group {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.1em;
> * {
margin-left: 0.5em;
}
}
@include media("<desktop") {
width: 100%;
justify-content: space-between !important;
}
}
#app:not(.queue-focused) {
.when-queue-focused {
@include media("<desktop") {
display: none;
}
}
}
.ui.bottom-player > .segment.fixed-controls {
width: 100%;
width: 100vw;
border-radius: 0;
padding: 0em;
position: fixed;
bottom: 0;
left: 0;
margin: 0;
z-index: 1001;
height: $bottom-player-height;
.controls-row {
height: $bottom-player-height;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
@include media(">desktop") {
padding: 0 1em;
justify-content: space-around;
}
}
cursor: pointer;
.indicating.progress {
overflow: hidden;
}
.ui.progress .bar {
transition: none;
}
.ui.progress .buffer.bar {
position: absolute;
}
@keyframes MOVE-BG {
from {
transform: translateX(0px);
}
to {
transform: translateX(46px);
}
}
.discrete.link {
color: inherit;
}
.indicating.progress .bar {
left: -46px;
width: 200% !important;
color: grey;
background: repeating-linear-gradient(
-55deg,
grey 1px,
grey 10px,
transparent 10px,
transparent 20px
) !important;
animation-name: MOVE-BG;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.ui.progress:not([data-percent]):not(.indeterminate)
.bar.position:not(.buffer) {
background: #ff851b;
min-width: 0;
}
.track-controls {
display: flex;
align-items: center;
justify-content: start;
flex-grow: 1;
.image {
padding: 0.5em;
width: auto;
margin-right: 0.5em;
> img {
max-height: 3.7em;
max-width: 4.7em;
}
}
}
.controls {
min-width: 8em;
font-size: 1.1em;
@include media(">desktop") {
&:not(.fluid) {
width: 20%;
}
&.queue-controls {
width: 32.5%;
}
&.progress-controls {
width: 10%;
}
&.player-controls {
width: 15%;
}
}
&.small, .small {
@include media(">desktop") {
font-size: 0.9em;
}
}
.icon {
font-size: 1.1em;
}
.icon.large {
font-size: 1.4em;
}
&:not(.track-controls) {
@include media(">desktop") {
line-height: 1em;
}
justify-content: center;
align-items: center;
&.align-right {
justify-content: flex-end;
}
&.align-left {
justify-content: flex-start;
}
> * {
margin: 0 0.5em;
}
}
&.player-controls {
.icon {
margin: 0;
}
}
}
}
.queue-enter-active, .queue-leave-active {
transition: all 0.2s ease-in-out;
.current-track, .queue-column {
opacity: 0;
}
}
.queue-enter, .queue-leave-to {
transform: translateY(100vh);
opacity: 0;
}
</style>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="206.66678mm"
height="28.491329mm"
viewBox="0 0 206.66678 28.491329"
version="1.1"
id="svg4600"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="text-white.svg">
<defs
id="defs4594" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="135.70772"
inkscape:cy="-23.988564"
inkscape:document-units="mm"
inkscape:current-layer="g5240"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1044"
inkscape:window-x="1920"
inkscape:window-y="36"
inkscape:window-maximized="1" />
<metadata
id="metadata4597">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(34.652951,-134.48185)">
<g
id="g5240">
<g
transform="translate(-66.52381,12.019644)"
id="g5221"
style="fill:#ffffff;fill-opacity:0.95454544">
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 32.845914,132.89252 c 0,-6.69443 2.603389,-9.29781 10.413554,-9.29781 1.636415,0 3.719126,0.14876 4.834864,0.37191 0.59506,0.14876 1.115738,0.59506 1.115738,1.11574 v 2.00832 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -0.669443 c -0.818208,0 -1.48765,-0.29753 -2.529006,-0.29753 -4.834864,0 -5.801837,0.96698 -5.801837,4.98363 v 0.29753 h 6.620045 c 0.59506,0 1.115738,0.4463 1.115738,1.11574 v 2.15709 c 0,0.66945 -0.446295,1.11574 -1.115738,1.11574 h -6.620045 v 11.30614 c 0,0.59506 -0.446295,1.11574 -1.115737,1.11574 h -4.016657 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 z"
id="path5166" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 57.020235,141.59528 c 0,3.04968 1.413268,4.31418 3.495978,4.31418 1.785181,0 3.495979,-1.2645 4.834864,-2.60339 v -12.12435 c 0,-0.59506 0.520678,-1.11573 1.115738,-1.11573 h 4.091039 c 0.59506,0 1.115738,0.52067 1.115738,1.11573 v 17.70304 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -4.091039 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 v -1.19012 c -1.710798,1.48765 -3.570361,2.67777 -6.322514,2.67777 -4.834864,0 -8.25646,-2.529 -8.25646,-8.70275 v -10.41355 c 0,-0.59506 0.446295,-1.11574 1.115738,-1.11574 h 4.091038 c 0.595061,0 1.115738,0.52068 1.115738,1.11574 v 10.33917 z"
id="path5168" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 90.715518,138.47121 c 0,-3.04968 -1.413268,-4.31419 -3.495979,-4.31419 -1.78518,0 -3.570361,1.26451 -4.909246,2.60339 v 12.19874 c 0,0.59506 -0.446295,1.11573 -1.115738,1.11573 h -4.091039 c -0.669442,0 -1.115738,-0.52067 -1.115738,-1.11573 v -17.77743 c 0,-0.59506 0.446296,-1.11573 1.115738,-1.11573 h 4.165422 c 0.59506,0 1.115737,0.52067 1.115737,1.11573 v 1.19012 c 1.710798,-1.48765 3.570362,-2.67777 6.396897,-2.67777 4.834865,0 8.256461,2.52901 8.256461,8.70276 v 10.41355 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -4.091039 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 z"
id="path5170" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 107.67473,137.95053 c 2.1571,0 3.57036,-0.89259 4.31419,-2.45462 l 1.78518,-3.71913 c 0.4463,-1.04135 1.56203,-1.71079 2.60339,-1.71079 h 3.42159 c 0.96698,0 1.11574,0.74382 0.59507,1.71079 l -2.38025,4.98363 c -0.74382,1.63642 -2.23147,2.90092 -3.86789,3.27283 1.48765,0.4463 2.75216,1.48765 3.86789,3.27283 l 3.12407,4.98363 c 0.59506,0.96698 0.29753,1.7108 -0.59506,1.7108 h -3.4216 c -1.19012,0 -2.15709,-0.74382 -2.75215,-1.7108 l -2.30586,-3.71912 c -0.96697,-1.63642 -2.67777,-2.38024 -4.31418,-2.38024 v 6.76881 c 0,0.59506 -0.4463,1.11573 -1.11574,1.11573 h -4.09104 c -0.59506,0 -1.11574,-0.52067 -1.11574,-1.11573 v -23.80241 c 0,-0.59506 0.4463,-1.11574 1.11574,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 12.79379 z"
id="path5172" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 140.1799,130.06599 c 1.04135,0 1.78518,0.59506 2.08271,1.56203 l 2.9753,9.81849 2.9753,-9.81849 c 0.29753,-1.04136 1.33888,-1.56203 2.45462,-1.56203 h 3.34722 c 0.89259,0 1.04135,0.66944 0.74382,1.56203 l -5.50431,16.73607 c -0.29753,0.96697 -1.33888,1.56203 -2.30585,1.56203 h -2.60339 c -0.89259,0 -2.00833,-0.59506 -2.30586,-1.56203 l -3.49598,-10.78547 -3.49598,10.85985 c -0.29753,0.96697 -1.41327,1.56203 -2.30586,1.56203 h -2.60338 c -0.96698,0 -1.93395,-0.59506 -2.30586,-1.56203 l -5.50431,-16.73607 c -0.29753,-0.89259 -0.0744,-1.56203 0.74383,-1.56203 h 3.34721 c 1.11574,0 2.08271,0.59506 2.45462,1.56203 l 2.9753,9.81849 2.9753,-9.81849 c 0.29753,-0.96697 1.04136,-1.56203 2.08272,-1.56203 h 3.27283 z"
id="path5174" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 172.09,138.47121 c 0,-3.04968 -1.41327,-4.31419 -3.4216,-4.31419 -1.78518,0 -3.57036,1.26451 -4.90924,2.60339 v 12.19874 c 0,0.59506 -0.4463,1.11573 -1.11574,1.11573 h -4.09104 c -0.59506,0 -1.11574,-0.52067 -1.11574,-1.11573 v -23.80241 c 0,-0.59506 0.52068,-1.11574 1.11574,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 7.2151 c 1.71079,-1.48765 3.57036,-2.67777 6.39689,-2.67777 4.83487,0 8.25646,2.52901 8.25646,8.70276 v 10.41355 c 0,0.59506 -0.52067,1.11574 -1.11573,1.11574 h -4.09104 c -0.59506,0 -1.11574,-0.52068 -1.11574,-1.11574 z"
id="path5176" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 189.04921,135.04961 c -0.44629,0.59506 -1.19012,0.96698 -2.08271,0.96698 h -2.67777 c -0.59506,0 -1.11573,-0.4463 -1.11573,-1.11574 0,-3.86789 3.86789,-5.20678 9.89287,-5.20678 5.35554,0 9.59535,2.23148 9.59535,7.88455 v 11.23176 c 0,0.59506 -0.52068,1.11574 -1.11574,1.11574 h -3.49598 c -0.59506,0 -1.11574,-0.52068 -1.11574,-1.11574 v -0.59506 c -1.7108,1.19012 -3.71912,2.08271 -6.62004,2.08271 -4.83487,0 -8.55399,-2.15709 -8.55399,-6.39689 0,-4.23981 3.71912,-6.32252 8.55399,-6.32252 h 6.02498 c 0,-2.90092 -1.19012,-3.79351 -3.71912,-3.79351 -1.56204,0.0744 -2.9753,0.52068 -3.57037,1.2645 z m 7.28949,9.52097 v -2.82654 h -5.57869 c -1.78518,0 -2.75215,0.96697 -2.75215,2.23148 0,1.2645 0.96697,2.23147 2.9753,2.23147 2.15709,0 4.01666,-0.8182 5.35554,-1.63641 z"
id="path5178" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 208.16552,150.0005 c -0.59506,0 -1.11573,-0.52068 -1.11573,-1.11574 v -23.8024 c 0,-0.59506 0.52067,-1.11574 1.11573,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 23.8024 c 0,0.59506 -0.44629,1.11574 -1.11574,1.11574 z"
id="path5180" />
<path
style="fill:#ffffff;fill-opacity:0.95454544;stroke-width:0.74382526"
inkscape:connector-curvature="0"
d="m 223.04203,141.96719 c 0.22315,2.9753 1.56203,4.2398 4.61171,4.2398 1.56204,0 2.97531,-0.44629 3.57037,-1.19012 0.52067,-0.59506 1.19012,-0.96697 2.08271,-0.96697 h 2.67777 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 0,3.86789 -3.94228,5.20677 -9.89288,5.20677 -6.62004,0 -10.6367,-3.57036 -10.6367,-10.26478 0,-6.69443 4.01666,-10.33917 10.6367,-10.33917 6.62004,0 10.56232,3.57036 10.56232,10.11602 v 1.04135 c 0,0.59506 -0.4463,1.11574 -1.11574,1.11574 h -13.612 z m 0,-3.86789 h 8.47961 c -0.14877,-2.75216 -1.48765,-4.23981 -4.23981,-4.23981 -2.67777,0 -4.09104,1.48765 -4.2398,4.23981 z"
id="path5182" />
</g>
</g>
</g>
<style
id="style2"
type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#009FE3;}
.st2{fill:#3C3C3B;}
</style>
</svg>
<template>
<section class="main with-background" :aria-label="labels.queue">
<div :class="['ui vertical stripe queue segment', playerFocused ? 'player-focused' : '']">
<div class="ui fluid container">
<div class="ui stackable grid" id="queue-grid">
<div class="ui six wide column current-track">
<div class="ui basic segment" id="player">
<template v-if="currentTrack">
<img class="ui image" v-if="currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.square_crop)">
<img class="ui image" v-else src="../assets/audio/default-cover.png">
<h1 class="ui header">
<div class="content">
<router-link class="small header discrete link track" :title="currentTrack.title" :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}">
{{ currentTrack.title | truncate(35) }}
</router-link>
<div class="sub header">
<router-link class="discrete link artist" :title="currentTrack.artist.name" :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}">
{{ currentTrack.artist.name | truncate(35) }}</router-link> /<router-link class="discrete link album" :title="currentTrack.album.title" :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}">
{{ currentTrack.album.title | truncate(35) }}
</router-link>
</div>
</div>
</h1>
<div class="ui small warning message" v-if="currentTrack && errored">
<div class="header">
<translate translate-context="Sidebar/Player/Error message.Title">The track cannot be loaded</translate>
</div>
<p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors">
<translate translate-context="Sidebar/Player/Error message.Paragraph">The next track will play automatically in a few seconds…</translate>
<i class="loading spinner icon"></i>
</p>
<p>
<translate translate-context="Sidebar/Player/Error message.Paragraph">You may have a connectivity issue.</translate>
</p>
</div>
<div class="additional-controls">
<track-favorite-icon
class="tablet-and-below"