mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-04 22:09:23 +02:00
Fix #612: Improved accessibility by using main/section/nav tags and aria-labels in most critical places
This commit is contained in:
parent
9005ebbd6d
commit
29171853b3
42 changed files with 1266 additions and 1122 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar',]">
|
||||
<div class="ui inverted segment header-wrapper">
|
||||
<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">
|
||||
|
@ -12,12 +12,12 @@
|
|||
:class="['ui', 'basic', 'big', {'inverted': isCollapsed}, 'orange', 'icon', 'collapse', 'button']">
|
||||
<i class="sidebar icon"></i></span>
|
||||
</search-bar>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="menu-area">
|
||||
<div class="ui compact fluid two item inverted menu">
|
||||
<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">
|
||||
<a class="active item" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a>
|
||||
<a class="item" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
|
||||
<translate>Queue</translate>
|
||||
<template v-if="queue.tracks.length === 0">
|
||||
<translate>(empty)</translate>
|
||||
|
@ -29,10 +29,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<div class="ui bottom attached active tab" data-tab="library">
|
||||
<div class="ui inverted vertical large fluid menu">
|
||||
<section class="ui bottom attached active tab" data-tab="library" :aria-label="labels.mainMenu">
|
||||
<nav class="ui inverted vertical large fluid menu" role="navigation" :aria-label="labels.mainMenu">
|
||||
<div class="item">
|
||||
<div class="header"><translate>My account</translate></div>
|
||||
<header class="header"><translate>My account</translate></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>
|
||||
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="header"><translate>Music</translate></div>
|
||||
<header class="header"><translate>Music</translate></header>
|
||||
<div class="menu">
|
||||
<router-link class="item" :to="{path: '/library'}"><i class="sound icon"></i><translate>Browse library</translate></router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i><translate>Favorites</translate></router-link>
|
||||
|
@ -77,7 +77,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="item" v-if="$store.state.auth.availablePermissions['settings']">
|
||||
<div class="header"><translate>Administration</translate></div>
|
||||
<header class="header"><translate>Administration</translate></header>
|
||||
<div class="menu">
|
||||
<router-link
|
||||
class="item"
|
||||
|
@ -91,8 +91,8 @@
|
|||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</section>
|
||||
<div v-if="queue.previousQueue " class="ui black icon message">
|
||||
<i class="history icon"></i>
|
||||
<div class="content">
|
||||
|
@ -113,17 +113,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom attached tab" data-tab="queue">
|
||||
<section class="ui bottom attached tab" data-tab="queue">
|
||||
<table class="ui compact inverted very basic fixed single line unstackable table">
|
||||
<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}]">
|
||||
<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">
|
||||
<button class="title reset ellipsis" :aria-label="labels.selectTrack">
|
||||
<strong>{{ track.title }}</strong><br />
|
||||
{{ track.artist.name }}
|
||||
</button>
|
||||
|
@ -134,7 +138,7 @@
|
|||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<button @click.stop="cleanTrack(index)" :class="['ui', {'inverted': index != queue.currentIndex}, 'really', 'tiny', 'basic', 'circular', 'icon', 'button']">
|
||||
<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>
|
||||
|
@ -150,44 +154,46 @@
|
|||
<div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate>Stop radio</translate></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<player @next="scrollToCurrent" @previous="scrollToCurrent"></player>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapActions} from 'vuex'
|
||||
import { mapState, mapActions } 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 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'
|
||||
import $ from "jquery"
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
name: "sidebar",
|
||||
components: {
|
||||
Player,
|
||||
SearchBar,
|
||||
Logo,
|
||||
draggable
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
selectedTab: 'library',
|
||||
selectedTab: "library",
|
||||
backend: backend,
|
||||
tracksChangeBuffer: null,
|
||||
isCollapsed: true,
|
||||
fetchInterval: null,
|
||||
fetchInterval: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
$(this.$el).find('.menu .item').tab()
|
||||
mounted() {
|
||||
$(this.$el)
|
||||
.find(".menu .item")
|
||||
.tab()
|
||||
},
|
||||
destroy () {
|
||||
destroy() {
|
||||
if (this.fetchInterval) {
|
||||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
|
@ -197,82 +203,92 @@ export default {
|
|||
queue: state => state.queue,
|
||||
url: state => state.route.path
|
||||
}),
|
||||
labels () {
|
||||
let pendingRequests = this.$gettext('Pending import requests')
|
||||
let pendingFollows = this.$gettext('Pending follow requests')
|
||||
labels() {
|
||||
let mainMenu = this.$gettext("Main menu")
|
||||
let selectTrack = this.$gettext("Play this track")
|
||||
let pendingRequests = this.$gettext("Pending import requests")
|
||||
let pendingFollows = this.$gettext("Pending follow requests")
|
||||
return {
|
||||
pendingRequests,
|
||||
pendingFollows
|
||||
pendingFollows,
|
||||
mainMenu,
|
||||
selectTrack
|
||||
}
|
||||
},
|
||||
tracks: {
|
||||
get () {
|
||||
get() {
|
||||
return this.$store.state.queue.tracks
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.tracksChangeBuffer = value
|
||||
}
|
||||
},
|
||||
logoUrl () {
|
||||
logoUrl() {
|
||||
if (this.$store.state.auth.authenticated) {
|
||||
return 'library.index'
|
||||
return "library.index"
|
||||
} else {
|
||||
return 'index'
|
||||
return "index"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
cleanTrack: 'queue/cleanTrack'
|
||||
cleanTrack: "queue/cleanTrack"
|
||||
}),
|
||||
reorder: function (event) {
|
||||
this.$store.commit('queue/reorder', {
|
||||
tracks: this.tracksChangeBuffer, oldIndex: event.oldIndex, newIndex: event.newIndex})
|
||||
reorder: function(event) {
|
||||
this.$store.commit("queue/reorder", {
|
||||
tracks: this.tracksChangeBuffer,
|
||||
oldIndex: event.oldIndex,
|
||||
newIndex: event.newIndex
|
||||
})
|
||||
},
|
||||
scrollToCurrent () {
|
||||
scrollToCurrent() {
|
||||
let current = $(this.$el).find('[data-tab="queue"] .active')[0]
|
||||
if (!current) {
|
||||
return
|
||||
}
|
||||
let container = $(this.$el).find('.tabs')[0]
|
||||
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
|
||||
var scrollBack =
|
||||
container.scrollHeight - container.scrollTop <= container.clientHeight
|
||||
? 0
|
||||
: container.clientHeight / 2
|
||||
container.scrollTop = container.scrollTop - scrollBack
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url: function () {
|
||||
url: function() {
|
||||
this.isCollapsed = true
|
||||
},
|
||||
selectedTab: function (newValue) {
|
||||
if (newValue === 'queue') {
|
||||
selectedTab: function(newValue) {
|
||||
if (newValue === "queue") {
|
||||
this.scrollToCurrent()
|
||||
}
|
||||
},
|
||||
'$store.state.queue.currentIndex': function () {
|
||||
if (this.selectedTab !== 'queue') {
|
||||
"$store.state.queue.currentIndex": function() {
|
||||
if (this.selectedTab !== "queue") {
|
||||
this.scrollToCurrent()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
@import '../style/vendor/media';
|
||||
@import "../style/vendor/media";
|
||||
|
||||
$sidebar-color: #3d3e3f;
|
||||
|
||||
.sidebar {
|
||||
background: $sidebar-color;
|
||||
background: $sidebar-color;
|
||||
@include media(">tablet") {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@include media(">desktop") {
|
||||
|
@ -284,7 +300,9 @@ $sidebar-color: #3d3e3f;
|
|||
position: static !important;
|
||||
width: 100% !important;
|
||||
&.collapsed {
|
||||
.menu-area, .player-wrapper, .tabs {
|
||||
.menu-area,
|
||||
.player-wrapper,
|
||||
.tabs {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -378,7 +396,9 @@ $sidebar-color: #3d3e3f;
|
|||
.ui.search {
|
||||
display: flex;
|
||||
|
||||
.collapse.button, .collapse.button:hover, .collapse.button:active {
|
||||
.collapse.button,
|
||||
.collapse.button:hover,
|
||||
.collapse.button:active {
|
||||
box-shadow: none !important;
|
||||
margin: 0px;
|
||||
display: flex;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue