Fix #612: Improved accessibility by using main/section/nav tags and aria-labels in most critical places

This commit is contained in:
Eliot Berriot 2018-11-19 23:33:22 +01:00
parent 9005ebbd6d
commit 29171853b3
No known key found for this signature in database
GPG key ID: DD6965E2476E5C27
42 changed files with 1266 additions and 1122 deletions

View file

@ -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>&nbsp;
<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;