mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-04 12: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,5 +1,5 @@
|
|||
<template>
|
||||
<div class="ui inverted segment player-wrapper" :style="style">
|
||||
<section class="ui inverted segment player-wrapper" :aria-label="labels.audioPlayer" :style="style">
|
||||
<div class="player">
|
||||
<audio-track
|
||||
ref="currentAudio"
|
||||
|
@ -213,18 +213,18 @@
|
|||
@keydown.s.prevent.exact="shuffle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapGetters, mapActions} from 'vuex'
|
||||
import GlobalEvents from '@/components/utils/global-events'
|
||||
import ColorThief from '@/vendor/color-thief'
|
||||
import {Howl} from 'howler'
|
||||
import { mapState, mapGetters, mapActions } from "vuex"
|
||||
import GlobalEvents from "@/components/utils/global-events"
|
||||
import ColorThief from "@/vendor/color-thief"
|
||||
import { Howl } from "howler"
|
||||
|
||||
import AudioTrack from '@/components/audio/Track'
|
||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||
import AudioTrack from "@/components/audio/Track"
|
||||
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
|
||||
import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -233,8 +233,13 @@ export default {
|
|||
GlobalEvents,
|
||||
AudioTrack
|
||||
},
|
||||
data () {
|
||||
let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
|
||||
data() {
|
||||
let defaultAmbiantColors = [
|
||||
[46, 46, 46],
|
||||
[46, 46, 46],
|
||||
[46, 46, 46],
|
||||
[46, 46, 46]
|
||||
]
|
||||
return {
|
||||
isShuffling: false,
|
||||
sliderVolume: this.volume,
|
||||
|
@ -245,7 +250,7 @@ export default {
|
|||
dummyAudio: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
// we trigger the watcher explicitely it does not work otherwise
|
||||
this.sliderVolume = this.volume
|
||||
// this is needed to unlock audio playing under some browsers,
|
||||
|
@ -254,57 +259,57 @@ export default {
|
|||
this.dummyAudio = new Howl({
|
||||
preload: false,
|
||||
autoplay: false,
|
||||
src: ['noop.webm', 'noop.mp3']
|
||||
src: ["noop.webm", "noop.mp3"]
|
||||
})
|
||||
},
|
||||
destroyed () {
|
||||
destroyed() {
|
||||
this.dummyAudio.unload()
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
togglePlay: 'player/togglePlay',
|
||||
mute: 'player/mute',
|
||||
unmute: 'player/unmute',
|
||||
clean: 'queue/clean',
|
||||
updateProgress: 'player/updateProgress'
|
||||
togglePlay: "player/togglePlay",
|
||||
mute: "player/mute",
|
||||
unmute: "player/unmute",
|
||||
clean: "queue/clean",
|
||||
updateProgress: "player/updateProgress"
|
||||
}),
|
||||
shuffle () {
|
||||
shuffle() {
|
||||
let disabled = this.queue.tracks.length === 0
|
||||
if (this.isShuffling || disabled) {
|
||||
return
|
||||
}
|
||||
let self = this
|
||||
let msg = this.$gettext('Queue shuffled!')
|
||||
let msg = this.$gettext("Queue shuffled!")
|
||||
this.isShuffling = true
|
||||
setTimeout(() => {
|
||||
self.$store.dispatch('queue/shuffle', () => {
|
||||
self.$store.dispatch("queue/shuffle", () => {
|
||||
self.isShuffling = false
|
||||
self.$store.commit('ui/addMessage', {
|
||||
self.$store.commit("ui/addMessage", {
|
||||
content: msg,
|
||||
date: new Date()
|
||||
})
|
||||
})
|
||||
}, 100)
|
||||
},
|
||||
next () {
|
||||
next() {
|
||||
let self = this
|
||||
this.$store.dispatch('queue/next').then(() => {
|
||||
self.$emit('next')
|
||||
this.$store.dispatch("queue/next").then(() => {
|
||||
self.$emit("next")
|
||||
})
|
||||
},
|
||||
previous () {
|
||||
previous() {
|
||||
let self = this
|
||||
this.$store.dispatch('queue/previous').then(() => {
|
||||
self.$emit('previous')
|
||||
this.$store.dispatch("queue/previous").then(() => {
|
||||
self.$emit("previous")
|
||||
})
|
||||
},
|
||||
touchProgress (e) {
|
||||
touchProgress(e) {
|
||||
let time
|
||||
let target = this.$refs.progress
|
||||
time = e.layerX / target.offsetWidth * this.duration
|
||||
time = (e.layerX / target.offsetWidth) * this.duration
|
||||
this.$refs.currentAudio.setCurrentTime(time)
|
||||
},
|
||||
updateBackground () {
|
||||
updateBackground() {
|
||||
if (!this.currentTrack.album.cover) {
|
||||
this.ambiantColors = this.defaultAmbiantColors
|
||||
return
|
||||
|
@ -312,9 +317,9 @@ export default {
|
|||
let image = this.$refs.cover
|
||||
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
|
||||
},
|
||||
handleError ({sound, error}) {
|
||||
this.$store.commit('player/isLoadingAudio', false)
|
||||
this.$store.dispatch('player/trackErrored')
|
||||
handleError({ sound, error }) {
|
||||
this.$store.commit("player/isLoadingAudio", false)
|
||||
this.$store.dispatch("player/trackErrored")
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -330,26 +335,34 @@ export default {
|
|||
queue: state => state.queue
|
||||
}),
|
||||
...mapGetters({
|
||||
currentTrack: 'queue/currentTrack',
|
||||
hasNext: 'queue/hasNext',
|
||||
emptyQueue: 'queue/isEmpty',
|
||||
durationFormatted: 'player/durationFormatted',
|
||||
currentTimeFormatted: 'player/currentTimeFormatted',
|
||||
progress: 'player/progress'
|
||||
currentTrack: "queue/currentTrack",
|
||||
hasNext: "queue/hasNext",
|
||||
emptyQueue: "queue/isEmpty",
|
||||
durationFormatted: "player/durationFormatted",
|
||||
currentTimeFormatted: "player/currentTimeFormatted",
|
||||
progress: "player/progress"
|
||||
}),
|
||||
labels () {
|
||||
let previousTrack = this.$gettext('Previous track')
|
||||
let play = this.$gettext('Play track')
|
||||
let pause = this.$gettext('Pause track')
|
||||
let next = this.$gettext('Next track')
|
||||
let unmute = this.$gettext('Unmute')
|
||||
let mute = this.$gettext('Mute')
|
||||
let loopingDisabled = this.$gettext('Looping disabled. Click to switch to single-track looping.')
|
||||
let loopingSingle = this.$gettext('Looping on a single track. Click to switch to whole queue looping.')
|
||||
let loopingWhole = this.$gettext('Looping on whole queue. Click to disable looping.')
|
||||
let shuffle = this.$gettext('Shuffle your queue')
|
||||
let clear = this.$gettext('Clear your queue')
|
||||
labels() {
|
||||
let audioPlayer = this.$gettext("Media player")
|
||||
let previousTrack = this.$gettext("Previous track")
|
||||
let play = this.$gettext("Play track")
|
||||
let pause = this.$gettext("Pause track")
|
||||
let next = this.$gettext("Next track")
|
||||
let unmute = this.$gettext("Unmute")
|
||||
let mute = this.$gettext("Mute")
|
||||
let loopingDisabled = this.$gettext(
|
||||
"Looping disabled. Click to switch to single-track looping."
|
||||
)
|
||||
let loopingSingle = this.$gettext(
|
||||
"Looping on a single track. Click to switch to whole queue looping."
|
||||
)
|
||||
let loopingWhole = this.$gettext(
|
||||
"Looping on whole queue. Click to disable looping."
|
||||
)
|
||||
let shuffle = this.$gettext("Shuffle your queue")
|
||||
let clear = this.$gettext("Clear your queue")
|
||||
return {
|
||||
audioPlayer,
|
||||
previousTrack,
|
||||
play,
|
||||
pause,
|
||||
|
@ -363,29 +376,35 @@ export default {
|
|||
clear
|
||||
}
|
||||
},
|
||||
style: function () {
|
||||
style: function() {
|
||||
let style = {
|
||||
'background': this.ambiantGradiant
|
||||
background: this.ambiantGradiant
|
||||
}
|
||||
return style
|
||||
},
|
||||
ambiantGradiant: function () {
|
||||
ambiantGradiant: function() {
|
||||
let indexConf = [
|
||||
{orientation: 330, percent: 100, opacity: 0.7},
|
||||
{orientation: 240, percent: 90, opacity: 0.7},
|
||||
{orientation: 150, percent: 80, opacity: 0.7},
|
||||
{orientation: 60, percent: 70, opacity: 0.7}
|
||||
{ orientation: 330, percent: 100, opacity: 0.7 },
|
||||
{ orientation: 240, percent: 90, opacity: 0.7 },
|
||||
{ orientation: 150, percent: 80, opacity: 0.7 },
|
||||
{ orientation: 60, percent: 70, opacity: 0.7 }
|
||||
]
|
||||
let gradients = this.ambiantColors.map((e, i) => {
|
||||
let [r, g, b] = e
|
||||
let conf = indexConf[i]
|
||||
return `linear-gradient(${conf.orientation}deg, rgba(${r}, ${g}, ${b}, ${conf.opacity}) 10%, rgba(255, 255, 255, 0) ${conf.percent}%)`
|
||||
}).join(', ')
|
||||
let gradients = this.ambiantColors
|
||||
.map((e, i) => {
|
||||
let [r, g, b] = e
|
||||
let conf = indexConf[i]
|
||||
return `linear-gradient(${
|
||||
conf.orientation
|
||||
}deg, rgba(${r}, ${g}, ${b}, ${
|
||||
conf.opacity
|
||||
}) 10%, rgba(255, 255, 255, 0) ${conf.percent}%)`
|
||||
})
|
||||
.join(", ")
|
||||
return gradients
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentTrack (newValue, oldValue) {
|
||||
currentTrack(newValue, oldValue) {
|
||||
if (!this.isShuffling && newValue != oldValue) {
|
||||
this.audioKey = String(new Date())
|
||||
}
|
||||
|
@ -393,11 +412,11 @@ export default {
|
|||
this.ambiantColors = this.defaultAmbiantColors
|
||||
}
|
||||
},
|
||||
volume (newValue) {
|
||||
volume(newValue) {
|
||||
this.sliderVolume = newValue
|
||||
},
|
||||
sliderVolume (newValue) {
|
||||
this.$store.commit('player/volume', newValue)
|
||||
sliderVolume(newValue) {
|
||||
this.$store.commit("player/volume", newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +424,6 @@ export default {
|
|||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
|
||||
.ui.progress {
|
||||
margin: 0.5rem 0 1rem;
|
||||
}
|
||||
|
@ -423,18 +441,21 @@ export default {
|
|||
.ui.item {
|
||||
.meta {
|
||||
font-size: 90%;
|
||||
line-height: 1.2
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
.timer.total {
|
||||
text-align: right;
|
||||
text-align: right;
|
||||
}
|
||||
.timer.start {
|
||||
cursor: pointer
|
||||
cursor: pointer;
|
||||
}
|
||||
.track-area {
|
||||
margin-top: 0;
|
||||
.header, .meta, .artist, .album {
|
||||
.header,
|
||||
.meta,
|
||||
.artist,
|
||||
.album {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
@ -468,57 +489,57 @@ export default {
|
|||
left: 25%;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
input[type="range"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 3px;
|
||||
width: 10px;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
input[type="range"]::-moz-range-track {
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
input[type=range]::-moz-focus-outer {
|
||||
input[type="range"]::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
width: 10px;
|
||||
}
|
||||
input[type=range]::-ms-track {
|
||||
input[type="range"]::-ms-track {
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
input[type=range]::-ms-fill-lower {
|
||||
input[type="range"]::-ms-fill-lower {
|
||||
background: white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
input[type=range]::-ms-fill-upper {
|
||||
input[type="range"]::-ms-fill-upper {
|
||||
background: white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
input[type=range]::-ms-thumb {
|
||||
input[type="range"]::-ms-thumb {
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
width: 10px;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
input[type="range"]:focus::-ms-fill-lower {
|
||||
background: white;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
input[type="range"]:focus::-ms-fill-upper {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
@ -545,14 +566,13 @@ export default {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@keyframes MOVE-BG {
|
||||
from {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
to {
|
||||
transform: translateX(46px);
|
||||
}
|
||||
from {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
to {
|
||||
transform: translateX(46px);
|
||||
}
|
||||
}
|
||||
|
||||
.indicating.progress {
|
||||
|
@ -565,7 +585,7 @@ export default {
|
|||
|
||||
.ui.inverted.progress .buffer.bar {
|
||||
position: absolute;
|
||||
background-color:rgba(255, 255, 255, 0.15);
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.indicating.progress .bar {
|
||||
left: -46px;
|
||||
|
@ -576,12 +596,12 @@ export default {
|
|||
grey 1px,
|
||||
grey 10px,
|
||||
transparent 10px,
|
||||
transparent 20px,
|
||||
) !important;
|
||||
transparent 20px
|
||||
) !important;
|
||||
|
||||
animation-name: MOVE-BG;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue