mirror of
https://code.eliotberriot.com/funkwhale/funkwhale.git
synced 2025-10-06 04:29:55 +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,10 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<main>
|
||||
<div v-if="isLoading" class="ui vertical segment" v-title="">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<template v-if="album">
|
||||
<div :class="['ui', 'head', {'with-background': album.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="album.title">
|
||||
<section :class="['ui', 'head', {'with-background': album.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="album.title">
|
||||
<div class="segment-content">
|
||||
<h2 class="ui center aligned icon header">
|
||||
<i class="circular inverted sound yellow icon"></i>
|
||||
|
@ -38,86 +38,93 @@
|
|||
<translate>View on MusicBrainz</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>Tracks</translate>
|
||||
</h2>
|
||||
<track-table v-if="album" :artist="album.artist" :display-position="true" :tracks="album.tracks"></track-table>
|
||||
</div>
|
||||
<div class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>User libraries</translate>
|
||||
</h2>
|
||||
<library-widget :url="'albums/' + id + '/libraries/'">
|
||||
<translate slot="subtitle">This album is present in the following libraries:</translate>
|
||||
</library-widget>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import logger from '@/logging'
|
||||
import backend from '@/audio/backend'
|
||||
import PlayButton from '@/components/audio/PlayButton'
|
||||
import TrackTable from '@/components/audio/track/Table'
|
||||
import LibraryWidget from '@/components/federation/LibraryWidget'
|
||||
import axios from "axios"
|
||||
import logger from "@/logging"
|
||||
import backend from "@/audio/backend"
|
||||
import PlayButton from "@/components/audio/PlayButton"
|
||||
import TrackTable from "@/components/audio/track/Table"
|
||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||
|
||||
const FETCH_URL = 'albums/'
|
||||
const FETCH_URL = "albums/"
|
||||
|
||||
export default {
|
||||
props: ['id'],
|
||||
props: ["id"],
|
||||
components: {
|
||||
PlayButton,
|
||||
TrackTable,
|
||||
LibraryWidget
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
album: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
fetchData() {
|
||||
var self = this
|
||||
this.isLoading = true
|
||||
let url = FETCH_URL + this.id + '/'
|
||||
let url = FETCH_URL + this.id + "/"
|
||||
logger.default.debug('Fetching album "' + this.id + '"')
|
||||
axios.get(url).then((response) => {
|
||||
axios.get(url).then(response => {
|
||||
self.album = backend.Album.clean(response.data)
|
||||
self.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
labels() {
|
||||
return {
|
||||
title: this.$gettext('Album')
|
||||
title: this.$gettext("Album")
|
||||
}
|
||||
},
|
||||
wikipediaUrl () {
|
||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.album.title + ' ' + this.album.artist.name)
|
||||
wikipediaUrl() {
|
||||
return (
|
||||
"https://en.wikipedia.org/w/index.php?search=" +
|
||||
encodeURI(this.album.title + " " + this.album.artist.name)
|
||||
)
|
||||
},
|
||||
musicbrainzUrl () {
|
||||
musicbrainzUrl() {
|
||||
if (this.album.mbid) {
|
||||
return 'https://musicbrainz.org/release/' + this.album.mbid
|
||||
return "https://musicbrainz.org/release/" + this.album.mbid
|
||||
}
|
||||
},
|
||||
headerStyle () {
|
||||
headerStyle() {
|
||||
if (!this.album.cover.original) {
|
||||
return ''
|
||||
return ""
|
||||
}
|
||||
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.album.cover.original) + ')'
|
||||
return (
|
||||
"background-image: url(" +
|
||||
this.$store.getters["instance/absoluteUrl"](this.album.cover.original) +
|
||||
")"
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
id () {
|
||||
id() {
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
|
@ -126,5 +133,4 @@ export default {
|
|||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div v-title="labels.title">
|
||||
<main v-title="labels.title">
|
||||
<div v-if="isLoading" class="ui vertical segment">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<template v-if="artist">
|
||||
<div :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="artist.name">
|
||||
<section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="artist.name">
|
||||
<div class="segment-content">
|
||||
<h2 class="ui center aligned icon header">
|
||||
<i class="circular inverted users violet icon"></i>
|
||||
|
@ -36,11 +36,11 @@
|
|||
<translate>View on MusicBrainz</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<div v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>Albums by this artist</translate>
|
||||
</h2>
|
||||
|
@ -49,38 +49,38 @@
|
|||
<album-card :mode="'rich'" class="fluid" :album="album"></album-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tracks.length > 0" class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section v-if="tracks.length > 0" class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>Tracks by this artist</translate>
|
||||
</h2>
|
||||
<track-table :display-position="true" :tracks="tracks"></track-table>
|
||||
</div>
|
||||
<div class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>User libraries</translate>
|
||||
</h2>
|
||||
<library-widget :url="'artists/' + id + '/libraries/'">
|
||||
<translate slot="subtitle">This artist is present in the following libraries:</translate>
|
||||
</library-widget>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import axios from 'axios'
|
||||
import logger from '@/logging'
|
||||
import backend from '@/audio/backend'
|
||||
import AlbumCard from '@/components/audio/album/Card'
|
||||
import RadioButton from '@/components/radios/Button'
|
||||
import PlayButton from '@/components/audio/PlayButton'
|
||||
import TrackTable from '@/components/audio/track/Table'
|
||||
import LibraryWidget from '@/components/federation/LibraryWidget'
|
||||
import _ from "lodash"
|
||||
import axios from "axios"
|
||||
import logger from "@/logging"
|
||||
import backend from "@/audio/backend"
|
||||
import AlbumCard from "@/components/audio/album/Card"
|
||||
import RadioButton from "@/components/radios/Button"
|
||||
import PlayButton from "@/components/audio/PlayButton"
|
||||
import TrackTable from "@/components/audio/track/Table"
|
||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||
|
||||
export default {
|
||||
props: ['id'],
|
||||
props: ["id"],
|
||||
components: {
|
||||
AlbumCard,
|
||||
RadioButton,
|
||||
|
@ -88,7 +88,7 @@ export default {
|
|||
TrackTable,
|
||||
LibraryWidget
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
isLoadingAlbums: true,
|
||||
|
@ -99,54 +99,63 @@ export default {
|
|||
tracks: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
fetchData() {
|
||||
var self = this
|
||||
this.isLoading = true
|
||||
logger.default.debug('Fetching artist "' + this.id + '"')
|
||||
axios.get('tracks/', {params: {artist: this.id}}).then((response) => {
|
||||
axios.get("tracks/", { params: { artist: this.id } }).then(response => {
|
||||
self.tracks = response.data.results
|
||||
self.totalTracks = response.data.count
|
||||
})
|
||||
axios.get('artists/' + this.id + '/').then((response) => {
|
||||
axios.get("artists/" + this.id + "/").then(response => {
|
||||
self.artist = response.data
|
||||
self.isLoading = false
|
||||
self.isLoadingAlbums = true
|
||||
axios.get('albums/', {params: {artist: self.id, ordering: '-release_date'}}).then((response) => {
|
||||
self.totalAlbums = response.data.count
|
||||
let parsed = JSON.parse(JSON.stringify(response.data.results))
|
||||
self.albums = parsed.map((album) => {
|
||||
return backend.Album.clean(album)
|
||||
axios
|
||||
.get("albums/", {
|
||||
params: { artist: self.id, ordering: "-release_date" }
|
||||
})
|
||||
.then(response => {
|
||||
self.totalAlbums = response.data.count
|
||||
let parsed = JSON.parse(JSON.stringify(response.data.results))
|
||||
self.albums = parsed.map(album => {
|
||||
return backend.Album.clean(album)
|
||||
})
|
||||
|
||||
self.isLoadingAlbums = false
|
||||
})
|
||||
self.isLoadingAlbums = false
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
labels() {
|
||||
return {
|
||||
title: this.$gettext('Artist')
|
||||
title: this.$gettext("Artist")
|
||||
}
|
||||
},
|
||||
isPlayable () {
|
||||
return this.artist.albums.filter((a) => {
|
||||
return a.is_playable
|
||||
}).length > 0
|
||||
isPlayable() {
|
||||
return (
|
||||
this.artist.albums.filter(a => {
|
||||
return a.is_playable
|
||||
}).length > 0
|
||||
)
|
||||
},
|
||||
wikipediaUrl () {
|
||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.artist.name)
|
||||
wikipediaUrl() {
|
||||
return (
|
||||
"https://en.wikipedia.org/w/index.php?search=" +
|
||||
encodeURI(this.artist.name)
|
||||
)
|
||||
},
|
||||
musicbrainzUrl () {
|
||||
musicbrainzUrl() {
|
||||
if (this.artist.mbid) {
|
||||
return 'https://musicbrainz.org/artist/' + this.artist.mbid
|
||||
return "https://musicbrainz.org/artist/" + this.artist.mbid
|
||||
}
|
||||
},
|
||||
allTracks () {
|
||||
allTracks() {
|
||||
let tracks = []
|
||||
this.albums.forEach(album => {
|
||||
album.tracks.forEach(track => {
|
||||
|
@ -155,22 +164,28 @@ export default {
|
|||
})
|
||||
return tracks
|
||||
},
|
||||
cover () {
|
||||
return this.artist.albums.filter(album => {
|
||||
return album.cover
|
||||
}).map(album => {
|
||||
return album.cover
|
||||
})[0]
|
||||
cover() {
|
||||
return this.artist.albums
|
||||
.filter(album => {
|
||||
return album.cover
|
||||
})
|
||||
.map(album => {
|
||||
return album.cover
|
||||
})[0]
|
||||
},
|
||||
headerStyle () {
|
||||
headerStyle() {
|
||||
if (!this.cover || !this.cover.original) {
|
||||
return ''
|
||||
return ""
|
||||
}
|
||||
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover.original) + ')'
|
||||
return (
|
||||
"background-image: url(" +
|
||||
this.$store.getters["instance/absoluteUrl"](this.cover.original) +
|
||||
")"
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
id () {
|
||||
id() {
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-title="labels.title">
|
||||
<div class="ui vertical stripe segment">
|
||||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
<translate>Browsing artists</translate>
|
||||
</h2>
|
||||
|
@ -64,60 +64,59 @@
|
|||
:total="result.count"
|
||||
></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import axios from "axios"
|
||||
import _ from "lodash"
|
||||
import $ from "jquery"
|
||||
|
||||
import logger from '@/logging'
|
||||
import logger from "@/logging"
|
||||
|
||||
import OrderingMixin from '@/components/mixins/Ordering'
|
||||
import PaginationMixin from '@/components/mixins/Pagination'
|
||||
import TranslationsMixin from '@/components/mixins/Translations'
|
||||
import ArtistCard from '@/components/audio/artist/Card'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import OrderingMixin from "@/components/mixins/Ordering"
|
||||
import PaginationMixin from "@/components/mixins/Pagination"
|
||||
import TranslationsMixin from "@/components/mixins/Translations"
|
||||
import ArtistCard from "@/components/audio/artist/Card"
|
||||
import Pagination from "@/components/Pagination"
|
||||
|
||||
const FETCH_URL = 'artists/'
|
||||
const FETCH_URL = "artists/"
|
||||
|
||||
export default {
|
||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||
props: {
|
||||
defaultQuery: {type: String, required: false, default: ''}
|
||||
defaultQuery: { type: String, required: false, default: "" }
|
||||
},
|
||||
components: {
|
||||
ArtistCard,
|
||||
Pagination
|
||||
},
|
||||
data () {
|
||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
||||
data() {
|
||||
let defaultOrdering = this.getOrderingFromString(
|
||||
this.defaultOrdering || "-creation_date"
|
||||
)
|
||||
return {
|
||||
isLoading: true,
|
||||
result: null,
|
||||
page: parseInt(this.defaultPage),
|
||||
query: this.defaultQuery,
|
||||
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
||||
orderingDirection: defaultOrdering.direction || '+',
|
||||
orderingDirection: defaultOrdering.direction || "+",
|
||||
ordering: defaultOrdering.field,
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['name', 'name']
|
||||
]
|
||||
orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown()
|
||||
mounted() {
|
||||
$(".ui.dropdown").dropdown()
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
let searchPlaceholder = this.$gettext('Enter an artist name...')
|
||||
let title = this.$gettext('Artists')
|
||||
labels() {
|
||||
let searchPlaceholder = this.$gettext("Enter an artist name...")
|
||||
let title = this.$gettext("Artists")
|
||||
return {
|
||||
searchPlaceholder,
|
||||
title
|
||||
|
@ -125,7 +124,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
updateQueryString: _.debounce(function () {
|
||||
updateQueryString: _.debounce(function() {
|
||||
this.$router.replace({
|
||||
query: {
|
||||
query: this.query,
|
||||
|
@ -135,7 +134,7 @@ export default {
|
|||
}
|
||||
})
|
||||
}, 500),
|
||||
fetchData: _.debounce(function () {
|
||||
fetchData: _.debounce(function() {
|
||||
var self = this
|
||||
this.isLoading = true
|
||||
let url = FETCH_URL
|
||||
|
@ -144,36 +143,36 @@ export default {
|
|||
page_size: this.paginateBy,
|
||||
name__icontains: this.query,
|
||||
ordering: this.getOrderingAsString(),
|
||||
playable: 'true'
|
||||
playable: "true"
|
||||
}
|
||||
logger.default.debug('Fetching artists')
|
||||
axios.get(url, {params: params}).then((response) => {
|
||||
logger.default.debug("Fetching artists")
|
||||
axios.get(url, { params: params }).then(response => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
})
|
||||
}, 500),
|
||||
selectPage: function (page) {
|
||||
selectPage: function(page) {
|
||||
this.page = page
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page () {
|
||||
page() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
paginateBy () {
|
||||
paginateBy() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
ordering() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
orderingDirection() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
query () {
|
||||
query() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-title="labels.title">
|
||||
<div class="ui vertical stripe segment">
|
||||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
<div class="ui stackable three column grid">
|
||||
<div class="column">
|
||||
<track-widget :url="'history/listenings/'" :filters="{scope: 'user', ordering: '-creation_date'}">
|
||||
|
@ -26,23 +26,23 @@
|
|||
</album-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import Search from '@/components/audio/Search'
|
||||
import logger from '@/logging'
|
||||
import ArtistCard from '@/components/audio/artist/Card'
|
||||
import TrackWidget from '@/components/audio/track/Widget'
|
||||
import AlbumWidget from '@/components/audio/album/Widget'
|
||||
import PlaylistWidget from '@/components/playlists/Widget'
|
||||
import axios from "axios"
|
||||
import Search from "@/components/audio/Search"
|
||||
import logger from "@/logging"
|
||||
import ArtistCard from "@/components/audio/artist/Card"
|
||||
import TrackWidget from "@/components/audio/track/Widget"
|
||||
import AlbumWidget from "@/components/audio/album/Widget"
|
||||
import PlaylistWidget from "@/components/playlists/Widget"
|
||||
|
||||
const ARTISTS_URL = 'artists/'
|
||||
const ARTISTS_URL = "artists/"
|
||||
|
||||
export default {
|
||||
name: 'library',
|
||||
name: "library",
|
||||
components: {
|
||||
Search,
|
||||
ArtistCard,
|
||||
|
@ -50,35 +50,35 @@ export default {
|
|||
AlbumWidget,
|
||||
PlaylistWidget
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
artists: [],
|
||||
isLoadingArtists: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchArtists()
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
labels() {
|
||||
return {
|
||||
title: this.$gettext('Home')
|
||||
title: this.$gettext("Home")
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchArtists () {
|
||||
fetchArtists() {
|
||||
var self = this
|
||||
this.isLoadingArtists = true
|
||||
let params = {
|
||||
ordering: '-creation_date',
|
||||
ordering: "-creation_date",
|
||||
playable: true
|
||||
}
|
||||
let url = ARTISTS_URL
|
||||
logger.default.time('Loading latest artists')
|
||||
axios.get(url, {params: params}).then((response) => {
|
||||
logger.default.time("Loading latest artists")
|
||||
axios.get(url, { params: params }).then(response => {
|
||||
self.artists = response.data.results
|
||||
logger.default.timeEnd('Loading latest artists')
|
||||
logger.default.timeEnd("Loading latest artists")
|
||||
self.isLoadingArtists = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="main library pusher">
|
||||
<div class="ui secondary pointing menu">
|
||||
<nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
|
||||
<router-link class="ui item" to="/library" exact>
|
||||
<translate>Browse</translate>
|
||||
</router-link>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<router-link class="ui item" to="/library/playlists" exact>
|
||||
<translate>Playlists</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
</nav>
|
||||
<router-view :key="$route.fullPath"></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -21,8 +21,16 @@
|
|||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
showImports () {
|
||||
return this.$store.state.auth.availablePermissions['upload'] || this.$store.state.auth.availablePermissions['library']
|
||||
showImports() {
|
||||
return (
|
||||
this.$store.state.auth.availablePermissions["upload"] ||
|
||||
this.$store.state.auth.availablePermissions["library"]
|
||||
)
|
||||
},
|
||||
labels() {
|
||||
return {
|
||||
secondaryMenu: this.$gettext("Secondary menu")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +38,7 @@ export default {
|
|||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss">
|
||||
@import '../../style/vendor/media';
|
||||
@import "../../style/vendor/media";
|
||||
|
||||
.library {
|
||||
.ui.segment.head {
|
||||
|
@ -46,18 +54,16 @@ export default {
|
|||
}
|
||||
&.with-background {
|
||||
.header {
|
||||
&, .sub {
|
||||
&,
|
||||
.sub {
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
.segment-content {
|
||||
background-color: rgba(0, 0, 0, 0.5)
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-title="labels.title">
|
||||
<div class="ui vertical stripe segment">
|
||||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
<translate>Browsing radios</translate>
|
||||
</h2>
|
||||
|
@ -86,60 +86,59 @@
|
|||
:total="result.count"
|
||||
></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import axios from "axios"
|
||||
import _ from "lodash"
|
||||
import $ from "jquery"
|
||||
|
||||
import logger from '@/logging'
|
||||
import logger from "@/logging"
|
||||
|
||||
import OrderingMixin from '@/components/mixins/Ordering'
|
||||
import PaginationMixin from '@/components/mixins/Pagination'
|
||||
import TranslationsMixin from '@/components/mixins/Translations'
|
||||
import RadioCard from '@/components/radios/Card'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import OrderingMixin from "@/components/mixins/Ordering"
|
||||
import PaginationMixin from "@/components/mixins/Pagination"
|
||||
import TranslationsMixin from "@/components/mixins/Translations"
|
||||
import RadioCard from "@/components/radios/Card"
|
||||
import Pagination from "@/components/Pagination"
|
||||
|
||||
const FETCH_URL = 'radios/radios/'
|
||||
const FETCH_URL = "radios/radios/"
|
||||
|
||||
export default {
|
||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||
props: {
|
||||
defaultQuery: {type: String, required: false, default: ''}
|
||||
defaultQuery: { type: String, required: false, default: "" }
|
||||
},
|
||||
components: {
|
||||
RadioCard,
|
||||
Pagination
|
||||
},
|
||||
data () {
|
||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
||||
data() {
|
||||
let defaultOrdering = this.getOrderingFromString(
|
||||
this.defaultOrdering || "-creation_date"
|
||||
)
|
||||
return {
|
||||
isLoading: true,
|
||||
result: null,
|
||||
page: parseInt(this.defaultPage),
|
||||
query: this.defaultQuery,
|
||||
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
||||
orderingDirection: defaultOrdering.direction || '+',
|
||||
orderingDirection: defaultOrdering.direction || "+",
|
||||
ordering: defaultOrdering.field,
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['name', 'name']
|
||||
]
|
||||
orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown()
|
||||
mounted() {
|
||||
$(".ui.dropdown").dropdown()
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
let searchPlaceholder = this.$gettext('Enter a radio name...')
|
||||
let title = this.$gettext('Radios')
|
||||
labels() {
|
||||
let searchPlaceholder = this.$gettext("Enter a radio name...")
|
||||
let title = this.$gettext("Radios")
|
||||
return {
|
||||
searchPlaceholder,
|
||||
title
|
||||
|
@ -147,7 +146,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
updateQueryString: _.debounce(function () {
|
||||
updateQueryString: _.debounce(function() {
|
||||
this.$router.replace({
|
||||
query: {
|
||||
query: this.query,
|
||||
|
@ -157,7 +156,7 @@ export default {
|
|||
}
|
||||
})
|
||||
}, 500),
|
||||
fetchData: _.debounce(function () {
|
||||
fetchData: _.debounce(function() {
|
||||
var self = this
|
||||
this.isLoading = true
|
||||
let url = FETCH_URL
|
||||
|
@ -167,34 +166,34 @@ export default {
|
|||
name__icontains: this.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}
|
||||
logger.default.debug('Fetching radios')
|
||||
axios.get(url, {params: params}).then((response) => {
|
||||
logger.default.debug("Fetching radios")
|
||||
axios.get(url, { params: params }).then(response => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
})
|
||||
}, 500),
|
||||
selectPage: function (page) {
|
||||
selectPage: function(page) {
|
||||
this.page = page
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page () {
|
||||
page() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
paginateBy () {
|
||||
paginateBy() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
ordering() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
orderingDirection() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
},
|
||||
query () {
|
||||
query() {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<main>
|
||||
<div v-if="isLoadingTrack" class="ui vertical segment" v-title="labels.title">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<template v-if="track">
|
||||
<div :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="track.title">
|
||||
<section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="track.title">
|
||||
<div class="segment-content">
|
||||
<h2 class="ui center aligned icon header">
|
||||
<i class="circular inverted music orange icon"></i>
|
||||
|
@ -49,8 +49,8 @@
|
|||
<translate>Download</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui vertical stripe center aligned segment" v-if="upload">
|
||||
</section>
|
||||
<section class="ui vertical stripe center aligned segment" v-if="upload">
|
||||
<h2 class="ui header"><translate>Track information</translate></h2>
|
||||
<table class="ui very basic collapsing celled center aligned table">
|
||||
<tbody>
|
||||
|
@ -100,8 +100,8 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui vertical stripe center aligned segment">
|
||||
</section>
|
||||
<section class="ui vertical stripe center aligned segment">
|
||||
<h2>
|
||||
<translate>Lyrics</translate>
|
||||
</h2>
|
||||
|
@ -117,41 +117,40 @@
|
|||
<translate>Search on lyrics.wikia.com</translate>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
<div class="ui vertical stripe segment">
|
||||
</section>
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate>User libraries</translate>
|
||||
</h2>
|
||||
<library-widget :url="'tracks/' + id + '/libraries/'">
|
||||
<translate slot="subtitle">This track is present in the following libraries:</translate>
|
||||
</library-widget>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import time from "@/utils/time"
|
||||
import axios from "axios"
|
||||
import url from "@/utils/url"
|
||||
import logger from "@/logging"
|
||||
import PlayButton from "@/components/audio/PlayButton"
|
||||
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
|
||||
import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
|
||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||
|
||||
import time from '@/utils/time'
|
||||
import axios from 'axios'
|
||||
import url from '@/utils/url'
|
||||
import logger from '@/logging'
|
||||
import PlayButton from '@/components/audio/PlayButton'
|
||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||
import LibraryWidget from '@/components/federation/LibraryWidget'
|
||||
|
||||
const FETCH_URL = 'tracks/'
|
||||
const FETCH_URL = "tracks/"
|
||||
|
||||
export default {
|
||||
props: ['id'],
|
||||
props: ["id"],
|
||||
components: {
|
||||
PlayButton,
|
||||
TrackPlaylistIcon,
|
||||
TrackFavoriteIcon,
|
||||
LibraryWidget
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
time,
|
||||
isLoadingTrack: true,
|
||||
|
@ -160,78 +159,94 @@ export default {
|
|||
lyrics: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
this.fetchLyrics()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
fetchData() {
|
||||
var self = this
|
||||
this.isLoadingTrack = true
|
||||
let url = FETCH_URL + this.id + '/'
|
||||
let url = FETCH_URL + this.id + "/"
|
||||
logger.default.debug('Fetching track "' + this.id + '"')
|
||||
axios.get(url).then((response) => {
|
||||
axios.get(url).then(response => {
|
||||
self.track = response.data
|
||||
self.isLoadingTrack = false
|
||||
})
|
||||
},
|
||||
fetchLyrics () {
|
||||
fetchLyrics() {
|
||||
var self = this
|
||||
this.isLoadingLyrics = true
|
||||
let url = FETCH_URL + this.id + '/lyrics/'
|
||||
let url = FETCH_URL + this.id + "/lyrics/"
|
||||
logger.default.debug('Fetching lyrics for track "' + this.id + '"')
|
||||
axios.get(url).then((response) => {
|
||||
self.lyrics = response.data
|
||||
self.isLoadingLyrics = false
|
||||
}, (response) => {
|
||||
console.error('No lyrics available')
|
||||
self.isLoadingLyrics = false
|
||||
})
|
||||
axios.get(url).then(
|
||||
response => {
|
||||
self.lyrics = response.data
|
||||
self.isLoadingLyrics = false
|
||||
},
|
||||
response => {
|
||||
console.error("No lyrics available")
|
||||
self.isLoadingLyrics = false
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
labels() {
|
||||
return {
|
||||
title: this.$gettext('Track')
|
||||
title: this.$gettext("Track")
|
||||
}
|
||||
},
|
||||
upload () {
|
||||
upload() {
|
||||
if (this.track.uploads) {
|
||||
return this.track.uploads[0]
|
||||
}
|
||||
},
|
||||
wikipediaUrl () {
|
||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.track.title + ' ' + this.track.artist.name)
|
||||
wikipediaUrl() {
|
||||
return (
|
||||
"https://en.wikipedia.org/w/index.php?search=" +
|
||||
encodeURI(this.track.title + " " + this.track.artist.name)
|
||||
)
|
||||
},
|
||||
musicbrainzUrl () {
|
||||
musicbrainzUrl() {
|
||||
if (this.track.mbid) {
|
||||
return 'https://musicbrainz.org/recording/' + this.track.mbid
|
||||
return "https://musicbrainz.org/recording/" + this.track.mbid
|
||||
}
|
||||
},
|
||||
downloadUrl () {
|
||||
let u = this.$store.getters['instance/absoluteUrl'](this.upload.listen_url)
|
||||
downloadUrl() {
|
||||
let u = this.$store.getters["instance/absoluteUrl"](
|
||||
this.upload.listen_url
|
||||
)
|
||||
if (this.$store.state.auth.authenticated) {
|
||||
u = url.updateQueryString(u, 'jwt', encodeURI(this.$store.state.auth.token))
|
||||
u = url.updateQueryString(
|
||||
u,
|
||||
"jwt",
|
||||
encodeURI(this.$store.state.auth.token)
|
||||
)
|
||||
}
|
||||
return u
|
||||
},
|
||||
lyricsSearchUrl () {
|
||||
let base = 'http://lyrics.wikia.com/wiki/Special:Search?query='
|
||||
let query = this.track.artist.name + ' ' + this.track.title
|
||||
lyricsSearchUrl() {
|
||||
let base = "http://lyrics.wikia.com/wiki/Special:Search?query="
|
||||
let query = this.track.artist.name + " " + this.track.title
|
||||
return base + encodeURI(query)
|
||||
},
|
||||
cover () {
|
||||
cover() {
|
||||
return null
|
||||
},
|
||||
headerStyle () {
|
||||
headerStyle() {
|
||||
if (!this.cover) {
|
||||
return ''
|
||||
return ""
|
||||
}
|
||||
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover) + ')'
|
||||
return (
|
||||
"background-image: url(" +
|
||||
this.$store.getters["instance/absoluteUrl"](this.cover) +
|
||||
")"
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
id () {
|
||||
id() {
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="ui vertical stripe segment" v-title="labels.title">
|
||||
<div>
|
||||
<div>
|
||||
<section>
|
||||
<h2 class="ui header">
|
||||
<translate>Builder</translate>
|
||||
</h2>
|
||||
|
@ -87,28 +87,28 @@
|
|||
</h3>
|
||||
<track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample"></track-table>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import _ from 'lodash'
|
||||
import BuilderFilter from './Filter'
|
||||
import TrackTable from '@/components/audio/track/Table'
|
||||
import RadioButton from '@/components/radios/Button'
|
||||
import axios from "axios"
|
||||
import $ from "jquery"
|
||||
import _ from "lodash"
|
||||
import BuilderFilter from "./Filter"
|
||||
import TrackTable from "@/components/audio/track/Table"
|
||||
import RadioButton from "@/components/radios/Button"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {required: false}
|
||||
id: { required: false }
|
||||
},
|
||||
components: {
|
||||
BuilderFilter,
|
||||
TrackTable,
|
||||
RadioButton
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
isLoading: false,
|
||||
success: false,
|
||||
|
@ -116,12 +116,12 @@ export default {
|
|||
currentFilterType: null,
|
||||
filters: [],
|
||||
checkResult: null,
|
||||
radioName: '',
|
||||
radioDesc: '',
|
||||
radioName: "",
|
||||
radioDesc: "",
|
||||
isPublic: true
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
created: function() {
|
||||
let self = this
|
||||
this.fetchFilters().then(() => {
|
||||
if (self.id) {
|
||||
|
@ -129,18 +129,18 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown()
|
||||
mounted() {
|
||||
$(".ui.dropdown").dropdown()
|
||||
},
|
||||
methods: {
|
||||
fetchFilters: function () {
|
||||
fetchFilters: function() {
|
||||
let self = this
|
||||
let url = 'radios/radios/filters/'
|
||||
return axios.get(url).then((response) => {
|
||||
let url = "radios/radios/filters/"
|
||||
return axios.get(url).then(response => {
|
||||
self.availableFilters = response.data
|
||||
})
|
||||
},
|
||||
add () {
|
||||
add() {
|
||||
this.filters.push({
|
||||
config: {},
|
||||
filter: this.currentFilter,
|
||||
|
@ -148,23 +148,25 @@ export default {
|
|||
})
|
||||
this.fetchCandidates()
|
||||
},
|
||||
updateConfig (index, field, value) {
|
||||
updateConfig(index, field, value) {
|
||||
this.filters[index].config[field] = value
|
||||
this.fetchCandidates()
|
||||
},
|
||||
deleteFilter (index) {
|
||||
deleteFilter(index) {
|
||||
this.filters.splice(index, 1)
|
||||
this.fetchCandidates()
|
||||
},
|
||||
fetch: function () {
|
||||
fetch: function() {
|
||||
let self = this
|
||||
self.isLoading = true
|
||||
let url = 'radios/radios/' + this.id + '/'
|
||||
axios.get(url).then((response) => {
|
||||
let url = "radios/radios/" + this.id + "/"
|
||||
axios.get(url).then(response => {
|
||||
self.filters = response.data.config.map(f => {
|
||||
return {
|
||||
config: f,
|
||||
filter: this.availableFilters.filter(e => { return e.type === f.type })[0],
|
||||
filter: this.availableFilters.filter(e => {
|
||||
return e.type === f.type
|
||||
})[0],
|
||||
hash: +new Date()
|
||||
}
|
||||
})
|
||||
|
@ -174,24 +176,22 @@ export default {
|
|||
self.isLoading = false
|
||||
})
|
||||
},
|
||||
fetchCandidates: function () {
|
||||
fetchCandidates: function() {
|
||||
let self = this
|
||||
let url = 'radios/radios/validate/'
|
||||
let url = "radios/radios/validate/"
|
||||
let final = this.filters.map(f => {
|
||||
let c = _.clone(f.config)
|
||||
c.type = f.filter.type
|
||||
return c
|
||||
})
|
||||
final = {
|
||||
'filters': [
|
||||
{'type': 'group', filters: final}
|
||||
]
|
||||
filters: [{ type: "group", filters: final }]
|
||||
}
|
||||
axios.post(url, final).then((response) => {
|
||||
axios.post(url, final).then(response => {
|
||||
self.checkResult = response.data.filters[0]
|
||||
})
|
||||
},
|
||||
save: function () {
|
||||
save: function() {
|
||||
let self = this
|
||||
self.success = false
|
||||
self.isLoading = true
|
||||
|
@ -202,24 +202,24 @@ export default {
|
|||
return c
|
||||
})
|
||||
final = {
|
||||
'name': this.radioName,
|
||||
'description': this.radioDesc,
|
||||
'is_public': this.isPublic,
|
||||
'config': final
|
||||
name: this.radioName,
|
||||
description: this.radioDesc,
|
||||
is_public: this.isPublic,
|
||||
config: final
|
||||
}
|
||||
if (this.id) {
|
||||
let url = 'radios/radios/' + this.id + '/'
|
||||
axios.put(url, final).then((response) => {
|
||||
let url = "radios/radios/" + this.id + "/"
|
||||
axios.put(url, final).then(response => {
|
||||
self.isLoading = false
|
||||
self.success = true
|
||||
})
|
||||
} else {
|
||||
let url = 'radios/radios/'
|
||||
axios.post(url, final).then((response) => {
|
||||
let url = "radios/radios/"
|
||||
axios.post(url, final).then(response => {
|
||||
self.success = true
|
||||
self.isLoading = false
|
||||
self.$router.push({
|
||||
name: 'library.radios.detail',
|
||||
name: "library.radios.detail",
|
||||
params: {
|
||||
id: response.data.id
|
||||
}
|
||||
|
@ -229,30 +229,28 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
let title = this.$gettext('Radio Builder')
|
||||
labels() {
|
||||
let title = this.$gettext("Radio Builder")
|
||||
let placeholder = {
|
||||
'name': this.$gettext('My awesome radio'),
|
||||
'description': this.$gettext('My awesome description')
|
||||
name: this.$gettext("My awesome radio"),
|
||||
description: this.$gettext("My awesome description")
|
||||
}
|
||||
return {
|
||||
title,
|
||||
placeholder
|
||||
}
|
||||
},
|
||||
canSave: function () {
|
||||
return (
|
||||
this.radioName.length > 0 && this.checkErrors.length === 0
|
||||
)
|
||||
canSave: function() {
|
||||
return this.radioName.length > 0 && this.checkErrors.length === 0
|
||||
},
|
||||
checkErrors: function () {
|
||||
checkErrors: function() {
|
||||
if (!this.checkResult) {
|
||||
return []
|
||||
}
|
||||
let errors = this.checkResult.errors
|
||||
return errors
|
||||
},
|
||||
currentFilter: function () {
|
||||
currentFilter: function() {
|
||||
let self = this
|
||||
return this.availableFilters.filter(e => {
|
||||
return e.type === self.currentFilterType
|
||||
|
@ -261,7 +259,7 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
filters: {
|
||||
handler: function () {
|
||||
handler: function() {
|
||||
this.fetchCandidates()
|
||||
},
|
||||
deep: true
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue