diff --git a/changes/changelog.d/612.enhancement b/changes/changelog.d/612.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..092f3ec5d0a73087448a1a4e745244570c986369 --- /dev/null +++ b/changes/changelog.d/612.enhancement @@ -0,0 +1 @@ +Improved accessibility by using main/section/nav tags and aria-labels in most critical places (#612) diff --git a/front/src/components/About.vue b/front/src/components/About.vue index 438fed67db518c4b99a29b8cf7b3a870d8e5c412..16c58b173bb9d531dfb78c1de0854f60ac06dde1 100644 --- a/front/src/components/About.vue +++ b/front/src/components/About.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical center aligned stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical center aligned stripe segment"> <div class="ui text container"> <h1 class="ui huge header"> <translate v-if="instance.name.value" :translate-params="{instance: instance.name.value}"> @@ -10,8 +10,8 @@ </h1> <stats></stats> </div> - </div> - <div class="ui vertical stripe segment"> + </section> + <section class="ui vertical stripe segment"> <p v-if="!instance.short_description.value && !instance.long_description.value"> <translate>Unfortunately, owners of this instance did not yet take the time to complete this page.</translate> </p> @@ -31,28 +31,28 @@ class="ui middle aligned stackable text container" v-html="$options.filters.markdown(instance.long_description.value)"> </div> - </div> - </div> + </section> + </main> </template> <script> -import {mapState} from 'vuex' -import Stats from '@/components/instance/Stats' +import { mapState } from "vuex" +import Stats from "@/components/instance/Stats" export default { components: { Stats }, - created () { - this.$store.dispatch('instance/fetchSettings') + created() { + this.$store.dispatch("instance/fetchSettings") }, computed: { ...mapState({ instance: state => state.instance.settings.instance }), - labels () { + labels() { return { - title: this.$gettext('About this instance') + title: this.$gettext("About this instance") } } } diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue index 127f8be74a9ef27f361a0d393b0c5049126443ad..3b681d592e7a98d56a74a4e2cd1316286cee7f54 100644 --- a/front/src/components/Footer.vue +++ b/front/src/components/Footer.vue @@ -1,8 +1,8 @@ <template> - <footer id="footer" class="ui vertical footer segment"> + <footer id="footer" role="contentinfo" class="ui vertical footer segment"> <div class="ui container"> <div class="ui stackable equal height stackable grid"> - <div class="four wide column"> + <section class="four wide column"> <h4 v-translate class="ui header"> <translate :translate-params="{instanceName: instanceHostname}" >About %{instanceName}</translate> </h4> @@ -25,24 +25,24 @@ </select> </div> </div> - </div> - <div class="four wide column"> + </section> + <section class="four wide column"> <h4 v-translate class="ui header">Using Funkwhale</h4> <div class="ui link list"> <a href="https://docs.funkwhale.audio" class="item" target="_blank"><translate>Documentation</translate></a> <a href="https://docs.funkwhale.audio/users/apps.html" class="item" target="_blank"><translate>Mobile and desktop apps</translate></a> <div role="button" class="item" @click="$emit('show:shortcuts-modal')"><translate>Keyboard shortcuts</translate></div> </div> - </div> - <div class="four wide column"> + </section> + <section class="four wide column"> <h4 v-translate class="ui header">Getting help</h4> <div class="ui link list"> <a href="https://socialhub.network/c/projects/funkwhale" class="item" target="_blank"><translate>Support forum</translate></a> <a href="https://riot.im/app/#/room/#funkwhale-troubleshooting:matrix.org" class="item" target="_blank"><translate>Chat room</translate></a> <a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank"><translate>Issue tracker</translate></a> </div> - </div> - <div class="four wide column"> + </section> + <section class="four wide column"> <h4 v-translate class="ui header">About Funkwhale</h4> <div class="ui link list"> <a href="https://funkwhale.audio" class="item" target="_blank"><translate>Official website</translate></a> @@ -53,45 +53,51 @@ <p> <translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate> </p> - </div> + </section> </div> </div> </footer> </template> <script> -import {mapState} from 'vuex' - +import { mapState } from "vuex" export default { - props: ['version'], + props: ["version"], methods: { - switchInstance () { - let confirm = window.confirm(this.$gettext('This will erase your local data and disconnect you, do you want to continue?')) + switchInstance() { + let confirm = window.confirm( + this.$gettext( + "This will erase your local data and disconnect you, do you want to continue?" + ) + ) if (confirm) { - this.$store.commit('instance/instanceUrl', null) + this.$store.commit("instance/instanceUrl", null) } - }, + } }, computed: { ...mapState({ messages: state => state.ui.messages }), - instanceHostname () { + instanceHostname() { let url = this.$store.state.instance.instanceUrl - let parser = document.createElement('a'); + let parser = document.createElement("a") parser.href = url return parser.hostname }, - suggestedInstances () { - let instances = [this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio'] + suggestedInstances() { + let instances = [ + this.$store.getters["instance/defaultUrl"](), + "https://demo.funkwhale.audio" + ] return instances - }, + } } } </script> <style scoped> - footer p { - color: grey; - } +footer p { + color: grey; +} </style> diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue index 3448d10df6d7c8faf97e789f9db18daf2e76b686..800f77b9f82a7f887cc32ee35da87b5f00b1f0af 100644 --- a/front/src/components/Home.vue +++ b/front/src/components/Home.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical center aligned stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical center aligned stripe segment"> <div class="ui text container"> <h1 class="ui huge header"> <translate>Welcome on Funkwhale</translate> @@ -15,8 +15,8 @@ <i class="right arrow icon"></i> </router-link> </div> - </div> - <div class="ui vertical stripe segment"> + </section> + <section class="ui vertical stripe segment"> <div class="ui middle aligned stackable text container"> <div class="ui grid"> <div class="row"> @@ -136,22 +136,21 @@ </div> </div> </div> - </div> - </div> + </section> + </main> </template> <script> - export default { - data () { + data() { return { - musicbrainzUrl: 'https://musicbrainz.org/' + musicbrainzUrl: "https://musicbrainz.org/" } }, computed: { - labels () { + labels() { return { - title: this.$gettext('Welcome') + title: this.$gettext("Welcome") } } } diff --git a/front/src/components/PageNotFound.vue b/front/src/components/PageNotFound.vue index 115bc5d023f1745f1399a868bff86c63c8d12c6a..ef4e66f456cb03e63ad4dfcf441a22bcc1536735 100644 --- a/front/src/components/PageNotFound.vue +++ b/front/src/components/PageNotFound.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" :v-title="labels.title"> - <div class="ui vertical stripe segment"> + <main class="main pusher" :v-title="labels.title"> + <section class="ui vertical stripe segment"> <div class="ui text container"> <h1 class="ui huge header"> <i class="warning icon"></i> @@ -16,21 +16,21 @@ <i class="right arrow icon"></i> </router-link> </div> - </div> - </div> + </section> + </main> </template> <script> export default { - data: function () { + data: function() { return { path: window.location.href } }, computed: { - labels () { + labels() { return { - title: this.$gettext('Page Not Found') + title: this.$gettext("Page Not Found") } } } diff --git a/front/src/components/Pagination.vue b/front/src/components/Pagination.vue index bdc20b53d9884f4f1a24e3ac1655c4d6f1bd531f..1064a827e8a7c82aaf4145b2db2540d454b96fc5 100644 --- a/front/src/components/Pagination.vue +++ b/front/src/components/Pagination.vue @@ -1,5 +1,5 @@ <template> - <div class="ui pagination menu"> + <div class="ui pagination menu" role="navigation" :aria-label="labels.pagination"> <a href :disabled="current - 1 < 1" @click.prevent.stop="selectPage(current - 1)" @@ -24,30 +24,42 @@ </template> <script> -import _ from 'lodash' +import _ from "lodash" export default { props: { - current: {type: Number, default: 1}, - paginateBy: {type: Number, default: 25}, - total: {type: Number}, - compact: {type: Boolean, default: false} + current: { type: Number, default: 1 }, + paginateBy: { type: Number, default: 25 }, + total: { type: Number }, + compact: { type: Boolean, default: false } }, computed: { - pages: function () { + labels() { + return { + pagination: this.$gettext("Pagination") + } + }, + pages: function() { let range = 2 let current = this.current let beginning = _.range(1, Math.min(this.maxPage, 1 + range)) - let middle = _.range(Math.max(1, current - range + 1), Math.min(this.maxPage, current + range)) + let middle = _.range( + Math.max(1, current - range + 1), + Math.min(this.maxPage, current + range) + ) let end = _.range(this.maxPage, Math.max(1, this.maxPage - range)) let allowed = beginning.concat(middle, end) allowed = _.uniq(allowed) - allowed = _.sortBy(allowed, [(e) => { return e }]) + allowed = _.sortBy(allowed, [ + e => { + return e + } + ]) let final = [] allowed.forEach(p => { let last = final.slice(-1)[0] let consecutive = true - if (last === 'skip') { + if (last === "skip") { consecutive = false } else { if (!last) { @@ -59,25 +71,25 @@ export default { if (consecutive) { final.push(p) } else { - if (p !== 'skip') { - final.push('skip') + if (p !== "skip") { + final.push("skip") final.push(p) } } }) return final }, - maxPage: function () { + maxPage: function() { return Math.ceil(this.total / this.paginateBy) } }, methods: { - selectPage: function (page) { + selectPage: function(page) { if (page > this.maxPage || page < 1) { return } if (this.current !== page) { - this.$emit('page-changed', page) + this.$emit("page-changed", page) } } } @@ -87,6 +99,6 @@ export default { <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .ui.pagination.menu .item { - cursor: pointer; + cursor: pointer; } </style> diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 44813c6c9f96ba316430bd80a33e93a4fa39f61a..d8029b1bd662ecd1ae75eb30e8e39530254a41e4 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -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; diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 618fcd7164b9c0e7348c388f366254eaa9aa6b47..83751d8d44d7d8578dde4352b22341d1d140c4ac 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -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> diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue index 88caa5afefccf12b0ac115613f45f94dec9046af..30cb15a3669f8ea0baef855cb9ea5a3f89a56354 100644 --- a/front/src/components/auth/Login.vue +++ b/front/src/components/auth/Login.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2><translate>Log in to your Funkwhale account</translate></h2> <form class="ui form" @submit.prevent="submit()"> @@ -43,39 +43,39 @@ </button> </form> </div> - </div> - </div> + </section> + </main> </template> <script> -import PasswordInput from '@/components/forms/PasswordInput' +import PasswordInput from "@/components/forms/PasswordInput" export default { props: { - next: {type: String, default: '/'} + next: { type: String, default: "/" } }, components: { PasswordInput }, - data () { + data() { return { // We need to initialize the component with any // properties that will be used in it credentials: { - username: '', - password: '' + username: "", + password: "" }, - error: '', + error: "", isLoading: false } }, - mounted () { + mounted() { this.$refs.username.focus() }, computed: { - labels () { - let usernamePlaceholder = this.$gettext('Enter your username or email') - let title = this.$gettext('Log In') + labels() { + let usernamePlaceholder = this.$gettext("Enter your username or email") + let title = this.$gettext("Log In") return { usernamePlaceholder, title @@ -83,30 +83,31 @@ export default { } }, methods: { - submit () { + submit() { var self = this self.isLoading = true - this.error = '' + this.error = "" var credentials = { username: this.credentials.username, password: this.credentials.password } - this.$store.dispatch('auth/login', { - credentials, - next: '/library', - onError: error => { - if (error.response.status === 400) { - self.error = 'invalid_credentials' - } else { - self.error = 'unknown_error' + this.$store + .dispatch("auth/login", { + credentials, + next: "/library", + onError: error => { + if (error.response.status === 400) { + self.error = "invalid_credentials" + } else { + self.error = "unknown_error" + } } - } - }).then(e => { - self.isLoading = false - }) + }) + .then(e => { + self.isLoading = false + }) } } - } </script> diff --git a/front/src/components/auth/Logout.vue b/front/src/components/auth/Logout.vue index 5f60a9dbeb8aaee8554dbb5f9d08402a4ba322da..90495f5e0ed17fbb2485da7003a93d97a7bbf5a9 100644 --- a/front/src/components/auth/Logout.vue +++ b/front/src/components/auth/Logout.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2> <translate>Are you sure you want to log out?</translate> @@ -8,16 +8,16 @@ <p v-translate="{username: $store.state.auth.username}">You are currently logged in as %{ username }</p> <button class="ui button" @click="$store.dispatch('auth/logout')"><translate>Yes, log me out!</translate></button> </div> - </div> - </div> + </section> + </main> </template> <script> export default { computed: { - labels () { + labels() { return { - title: this.$gettext('Log Out') + title: this.$gettext("Log Out") } } } diff --git a/front/src/components/auth/Profile.vue b/front/src/components/auth/Profile.vue index b480f7495d72d022f0f1ad82d6fcd226802d89e1..e1bdd416c6f2519361c0643f65010ee375aa25eb 100644 --- a/front/src/components/auth/Profile.vue +++ b/front/src/components/auth/Profile.vue @@ -1,5 +1,5 @@ <template> - <div class="main pusher" v-title="labels.usernameProfile"> + <main class="main pusher" v-title="labels.usernameProfile"> <div v-if="isLoading" class="ui vertical segment"> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> </div> @@ -25,36 +25,37 @@ </a> </div> </template> - </div> + </main> </template> <script> -import {mapState} from 'vuex' +import { mapState } from "vuex" -const dateFormat = require('dateformat') +const dateFormat = require("dateformat") export default { - props: ['username'], - created () { - this.$store.dispatch('auth/fetchProfile') + props: ["username"], + created() { + this.$store.dispatch("auth/fetchProfile") }, computed: { - ...mapState({ profile: state => state.auth.profile }), - labels () { - let msg = this.$gettext('%{ username }\'s profile') - let usernameProfile = this.$gettextInterpolate(msg, {username: this.username}) + labels() { + let msg = this.$gettext("%{ username }'s profile") + let usernameProfile = this.$gettextInterpolate(msg, { + username: this.username + }) return { usernameProfile } }, - signupDate () { + signupDate() { let d = new Date(this.profile.date_joined) - return dateFormat(d, 'longDate') + return dateFormat(d, "longDate") }, - isLoading () { + isLoading() { return !this.profile } } diff --git a/front/src/components/auth/Settings.vue b/front/src/components/auth/Settings.vue index 0c27d9a9aecabdf9d20cc59db473e11096f5a58b..f4e357f28070e21e1e50f48f43c22de1769506d9 100644 --- a/front/src/components/auth/Settings.vue +++ b/front/src/components/auth/Settings.vue @@ -1,7 +1,7 @@ <template> - <div class="main pusher" v-title="labels.title"> + <main class="main pusher" v-title="labels.title"> <div class="ui vertical stripe segment"> - <div class="ui small text container"> + <section class="ui small text container"> <h2 class="ui header"> <translate>Account settings</translate> </h2> @@ -28,9 +28,9 @@ <translate>Update settings</translate> </button> </form> - </div> + </section> <div class="ui hidden divider"></div> - <div class="ui small text container"> + <section class="ui small text container"> <h2 class="ui header"> <translate>Avatar</translate> </h2> @@ -61,9 +61,9 @@ </div> </div> </div> - </div> + </section> <div class="ui hidden divider"></div> - <div class="ui small text container"> + <section class="ui small text container"> <h2 class="ui header"> <translate>Change my password</translate> </h2> @@ -107,18 +107,18 @@ </form> <div class="ui hidden divider" /> <subsonic-token-form /> - </div> + </section> </div> - </div> + </main> </template> <script> -import $ from 'jquery' -import axios from 'axios' -import logger from '@/logging' -import PasswordInput from '@/components/forms/PasswordInput' -import SubsonicTokenForm from '@/components/auth/SubsonicTokenForm' -import TranslationsMixin from '@/components/mixins/Translations' +import $ from "jquery" +import axios from "axios" +import logger from "@/logging" +import PasswordInput from "@/components/forms/PasswordInput" +import SubsonicTokenForm from "@/components/auth/SubsonicTokenForm" +import TranslationsMixin from "@/components/mixins/Translations" export default { mixins: [TranslationsMixin], @@ -126,14 +126,14 @@ export default { PasswordInput, SubsonicTokenForm }, - data () { + data() { let d = { // We need to initialize the component with any // properties that will be used in it - old_password: '', - new_password: '', + old_password: "", + new_password: "", currentAvatar: this.$store.state.auth.profile.avatar, - passwordError: '', + passwordError: "", isLoading: false, isLoadingAvatar: false, avatarErrors: [], @@ -141,12 +141,12 @@ export default { settings: { success: false, errors: [], - order: ['privacy_level'], + order: ["privacy_level"], fields: { - 'privacy_level': { - type: 'dropdown', + privacy_level: { + type: "dropdown", initial: this.$store.state.auth.profile.privacy_level, - choices: ['me', 'instance'] + choices: ["me", "instance"] } } } @@ -157,108 +157,120 @@ export default { }) return d }, - mounted () { - $('select.dropdown').dropdown() + mounted() { + $("select.dropdown").dropdown() }, methods: { - submitSettings () { + submitSettings() { this.settings.success = false this.settings.errors = [] let self = this let payload = this.settingsValues let url = `users/users/${this.$store.state.auth.username}/` - return axios.patch(url, payload).then(response => { - logger.default.info('Updated settings successfully') - self.settings.success = true - return axios.get('users/users/me/').then((response) => { - self.$store.dispatch('auth/updateProfile', response.data) - }) - }, error => { - logger.default.error('Error while updating settings') - self.isLoading = false - self.settings.errors = error.backendErrors - }) + return axios.patch(url, payload).then( + response => { + logger.default.info("Updated settings successfully") + self.settings.success = true + return axios.get("users/users/me/").then(response => { + self.$store.dispatch("auth/updateProfile", response.data) + }) + }, + error => { + logger.default.error("Error while updating settings") + self.isLoading = false + self.settings.errors = error.backendErrors + } + ) }, - submitAvatar () { + submitAvatar() { this.isLoadingAvatar = true this.avatarErrors = [] let self = this this.avatar = this.$refs.avatar.files[0] let formData = new FormData() - formData.append('avatar', this.avatar) - axios.patch( - `users/users/${this.$store.state.auth.username}/`, - formData, - { + formData.append("avatar", this.avatar) + axios + .patch(`users/users/${this.$store.state.auth.username}/`, formData, { headers: { - 'Content-Type': 'multipart/form-data' + "Content-Type": "multipart/form-data" } - } - ).then(response => { - this.isLoadingAvatar = false - self.currentAvatar = response.data.avatar - self.$store.commit('auth/avatar', self.currentAvatar) - }, error => { - self.isLoadingAvatar = false - self.avatarErrors = error.backendErrors - }) + }) + .then( + response => { + this.isLoadingAvatar = false + self.currentAvatar = response.data.avatar + self.$store.commit("auth/avatar", self.currentAvatar) + }, + error => { + self.isLoadingAvatar = false + self.avatarErrors = error.backendErrors + } + ) }, - removeAvatar () { + removeAvatar() { this.isLoadingAvatar = true let self = this this.avatar = null - axios.patch( - `users/users/${this.$store.state.auth.username}/`, - {avatar: null} - ).then(response => { - this.isLoadingAvatar = false - self.currentAvatar = {} - self.$store.commit('auth/avatar', self.currentAvatar) - }, error => { - self.isLoadingAvatar = false - self.avatarErrors = error.backendErrors - }) + axios + .patch(`users/users/${this.$store.state.auth.username}/`, { + avatar: null + }) + .then( + response => { + this.isLoadingAvatar = false + self.currentAvatar = {} + self.$store.commit("auth/avatar", self.currentAvatar) + }, + error => { + self.isLoadingAvatar = false + self.avatarErrors = error.backendErrors + } + ) }, - submitPassword () { + submitPassword() { var self = this self.isLoading = true - this.error = '' + this.error = "" var credentials = { old_password: this.old_password, new_password1: this.new_password, new_password2: this.new_password } - let url = 'auth/registration/change-password/' - return axios.post(url, credentials).then(response => { - logger.default.info('Password successfully changed') - self.$router.push({ - name: 'profile', - params: { - username: self.$store.state.auth.username - }}) - }, error => { - if (error.response.status === 400) { - self.passwordError = 'invalid_credentials' - } else { - self.passwordError = 'unknown_error' + let url = "auth/registration/change-password/" + return axios.post(url, credentials).then( + response => { + logger.default.info("Password successfully changed") + self.$router.push({ + name: "profile", + params: { + username: self.$store.state.auth.username + } + }) + }, + error => { + if (error.response.status === 400) { + self.passwordError = "invalid_credentials" + } else { + self.passwordError = "unknown_error" + } + self.isLoading = false } - self.isLoading = false - }) + ) } }, computed: { - labels () { + labels() { return { - title: this.$gettext('Account Settings') + title: this.$gettext("Account Settings") } }, - orderedSettingsFields () { + orderedSettingsFields() { let self = this return this.settings.order.map(id => { return self.settings.fields[id] }) }, - settingsValues () { + settingsValues() { let self = this let s = {} this.settings.order.forEach(setting => { @@ -268,7 +280,6 @@ export default { return s } } - } </script> diff --git a/front/src/components/auth/Signup.vue b/front/src/components/auth/Signup.vue index 8d2e80470b63d1a651e383bcf546825a1f01cacb..01230a117784dda665edfaa6face83a9aac4c555 100644 --- a/front/src/components/auth/Signup.vue +++ b/front/src/components/auth/Signup.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2><translate>Create a funkwhale account</translate></h2> <form @@ -53,49 +53,51 @@ </button> </form> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' -import logger from '@/logging' +import axios from "axios" +import logger from "@/logging" -import PasswordInput from '@/components/forms/PasswordInput' +import PasswordInput from "@/components/forms/PasswordInput" export default { props: { - defaultInvitation: {type: String, required: false, default: null}, - next: {type: String, default: '/'} + defaultInvitation: { type: String, required: false, default: null }, + next: { type: String, default: "/" } }, components: { PasswordInput }, - data () { + data() { return { - username: '', - email: '', - password: '', + username: "", + email: "", + password: "", isLoadingInstanceSetting: true, errors: [], isLoading: false, invitation: this.defaultInvitation } }, - created () { + created() { let self = this - this.$store.dispatch('instance/fetchSettings', { - callback: function () { + this.$store.dispatch("instance/fetchSettings", { + callback: function() { self.isLoadingInstanceSetting = false } }) }, computed: { - labels () { - let title = this.$gettext('Sign Up') - let placeholder = this.$gettext('Enter your invitation code (case insensitive)') - let usernamePlaceholder = this.$gettext('Enter your username') - let emailPlaceholder = this.$gettext('Enter your email') + labels() { + let title = this.$gettext("Sign Up") + let placeholder = this.$gettext( + "Enter your invitation code (case insensitive)" + ) + let usernamePlaceholder = this.$gettext("Enter your username") + let emailPlaceholder = this.$gettext("Enter your email") return { title, usernamePlaceholder, @@ -105,7 +107,7 @@ export default { } }, methods: { - submit () { + submit() { var self = this self.isLoading = true this.errors = [] @@ -116,17 +118,21 @@ export default { email: this.email, invitation: this.invitation } - return axios.post('auth/registration/', payload).then(response => { - logger.default.info('Successfully created account') - self.$router.push({ - name: 'profile', - params: { - username: this.username - }}) - }, error => { - self.errors = error.backendErrors - self.isLoading = false - }) + return axios.post("auth/registration/", payload).then( + response => { + logger.default.info("Successfully created account") + self.$router.push({ + name: "profile", + params: { + username: this.username + } + }) + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) } } } diff --git a/front/src/components/favorites/List.vue b/front/src/components/favorites/List.vue index f178e41b38523558e17d25317a6d2d9eeb0fc9b6..d3f8fce442c8dc4d1ba196caa9f1f61b63e23c0f 100644 --- a/front/src/components/favorites/List.vue +++ b/front/src/components/favorites/List.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical center aligned stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical center aligned stripe segment"> <div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> <div class="ui text loader"> <translate>Loading your favorites...</translate> @@ -16,8 +16,8 @@ </translate> </h2> <radio-button type="favorites"></radio-button> - </div> - <div class="ui vertical stripe segment"> + </section> + <section class="ui vertical stripe segment"> <div :class="['ui', {'loading': isLoading}, 'form']"> <div class="fields"> <div class="field"> @@ -56,21 +56,21 @@ :total="results.count" ></pagination> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' -import $ from 'jquery' -import logger from '@/logging' -import TrackTable from '@/components/audio/track/Table' -import RadioButton from '@/components/radios/Button' -import Pagination from '@/components/Pagination' -import OrderingMixin from '@/components/mixins/Ordering' -import PaginationMixin from '@/components/mixins/Pagination' -import TranslationsMixin from '@/components/mixins/Translations' -const FAVORITES_URL = 'tracks/' +import axios from "axios" +import $ from "jquery" +import logger from "@/logging" +import TrackTable from "@/components/audio/track/Table" +import RadioButton from "@/components/radios/Button" +import Pagination from "@/components/Pagination" +import OrderingMixin from "@/components/mixins/Ordering" +import PaginationMixin from "@/components/mixins/Pagination" +import TranslationsMixin from "@/components/mixins/Translations" +const FAVORITES_URL = "tracks/" export default { mixins: [OrderingMixin, PaginationMixin, TranslationsMixin], @@ -79,8 +79,10 @@ export default { RadioButton, Pagination }, - data () { - let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + data() { + let defaultOrdering = this.getOrderingFromString( + this.defaultOrdering || "-creation_date" + ) return { results: null, isLoading: false, @@ -88,31 +90,31 @@ export default { previousLink: null, page: parseInt(this.defaultPage), paginateBy: parseInt(this.defaultPaginateBy || 25), - orderingDirection: defaultOrdering.direction || '+', + orderingDirection: defaultOrdering.direction || "+", ordering: defaultOrdering.field, orderingOptions: [ - ['creation_date', 'creation_date'], - ['title', 'track_title'], - ['album__title', 'album_title'], - ['artist__name', 'artist_name'] + ["creation_date", "creation_date"], + ["title", "track_title"], + ["album__title", "album_title"], + ["artist__name", "artist_name"] ] } }, - created () { + created() { this.fetchFavorites(FAVORITES_URL) }, - mounted () { - $('.ui.dropdown').dropdown() + mounted() { + $(".ui.dropdown").dropdown() }, computed: { - labels () { + labels() { return { - title: this.$gettext('Your Favorites') + title: this.$gettext("Your Favorites") } } }, methods: { - updateQueryString: function () { + updateQueryString: function() { this.$router.replace({ query: { page: this.page, @@ -121,42 +123,42 @@ export default { } }) }, - fetchFavorites (url) { + fetchFavorites(url) { var self = this this.isLoading = true let params = { - favorites: 'true', + favorites: "true", page: this.page, page_size: this.paginateBy, ordering: this.getOrderingAsString() } - logger.default.time('Loading user favorites') - axios.get(url, {params: params}).then((response) => { + logger.default.time("Loading user favorites") + axios.get(url, { params: params }).then(response => { self.results = response.data self.nextLink = response.data.next self.previousLink = response.data.previous - self.results.results.forEach((track) => { - self.$store.commit('favorites/track', {id: track.id, value: true}) + self.results.results.forEach(track => { + self.$store.commit("favorites/track", { id: track.id, value: true }) }) - logger.default.timeEnd('Loading user favorites') + logger.default.timeEnd("Loading user favorites") self.isLoading = false }) }, - selectPage: function (page) { + selectPage: function(page) { this.page = page } }, watch: { - page: function () { + page: function() { this.updateQueryString() }, - paginateBy: function () { + paginateBy: function() { this.updateQueryString() }, - orderingDirection: function () { + orderingDirection: function() { this.updateQueryString() }, - ordering: function () { + ordering: function() { this.updateQueryString() } } diff --git a/front/src/components/library/Album.vue b/front/src/components/library/Album.vue index 8ea983369df8e87766153229485cf6a62180514e..943aa9fc92103f950da8b33771b9e4b903cece9c 100644 --- a/front/src/components/library/Album.vue +++ b/front/src/components/library/Album.vue @@ -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> diff --git a/front/src/components/library/Artist.vue b/front/src/components/library/Artist.vue index 953cea28964d4a1098e3e104470962c1d1af351f..e16e6728d50f2f4f597f87bb6f8e2211c4f3bc0e 100644 --- a/front/src/components/library/Artist.vue +++ b/front/src/components/library/Artist.vue @@ -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() } } diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index 379d07e4b8bccd72838effeb99d8d42f0d37c8ac..83dd0e8e263797af0eb3c8caa2f808427eced4d8 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -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() } diff --git a/front/src/components/library/Home.vue b/front/src/components/library/Home.vue index e11127608818e9024ba38f3c85ecef5860a7f7f2..5e81dbdc86acd8f495e1c667d35f8e119b768918 100644 --- a/front/src/components/library/Home.vue +++ b/front/src/components/library/Home.vue @@ -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 }) } diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue index 01a357724990ed107cffd4d3d7f50e06d9fb4e4d..c0371fe58800145856eaf18c30192565184a183f 100644 --- a/front/src/components/library/Library.vue +++ b/front/src/components/library/Library.vue @@ -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> diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue index 4cc1d58df1286509747129b2fe31fde5eb22a291..48e9b1e2f324e8027f97013d04fc353f8eaca042 100644 --- a/front/src/components/library/Radios.vue +++ b/front/src/components/library/Radios.vue @@ -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() } diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue index ddccda397352bc578999608489f153519f332fe8..a66b240f542d796aef4c8290bfe55681cbf96d1f 100644 --- a/front/src/components/library/Track.vue +++ b/front/src/components/library/Track.vue @@ -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() } } diff --git a/front/src/components/library/radios/Builder.vue b/front/src/components/library/radios/Builder.vue index 91ea702464d1768b4ef1d3879cd5612c8f917743..b0157497987c8b6545cfab6eb819e8488194980e 100644 --- a/front/src/components/library/radios/Builder.vue +++ b/front/src/components/library/radios/Builder.vue @@ -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 diff --git a/front/src/components/metadata/ArtistCard.vue b/front/src/components/metadata/ArtistCard.vue index ef2fc616af41523a33fa564d97cdc0905a93024d..98c35337ece4e0c093fc4d77fe0519b835f3b04a 100644 --- a/front/src/components/metadata/ArtistCard.vue +++ b/front/src/components/metadata/ArtistCard.vue @@ -5,9 +5,9 @@ <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> </div> <template v-if="data.id"> - <div class="header"> + <header class="header"> <a :href="getMusicbrainzUrl('artist', data.id)" target="_blank" :title="labels.musicbrainz">{{ data.name }}</a> - </div> + </header> <div class="description"> <table class="ui very basic fixed single line compact table"> <tbody> @@ -32,29 +32,29 @@ </template> <script> -import Vue from 'vue' -import CardMixin from './CardMixin' -import time from '@/utils/time' +import Vue from "vue" +import CardMixin from "./CardMixin" +import time from "@/utils/time" export default Vue.extend({ mixins: [CardMixin], - data () { + data() { return { time } }, computed: { - labels () { + labels() { return { - musicbrainz: this.$gettext('View on MusicBrainz') + musicbrainz: this.$gettext("View on MusicBrainz") } }, - type () { - return 'artist' + type() { + return "artist" }, - releasesGroups () { - return this.data['release-group-list'].filter(r => { - return r.type === 'Album' + releasesGroups() { + return this.data["release-group-list"].filter(r => { + return r.type === "Album" }) } } @@ -64,6 +64,6 @@ export default Vue.extend({ <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="scss"> .ui.card { - width: 100% !important; + width: 100% !important; } </style> diff --git a/front/src/views/Notifications.vue b/front/src/views/Notifications.vue index 758283c779452540478c7e1c640e4d73e3000a27..d8be139965e216a8ea435140037b05db49ef2502 100644 --- a/front/src/views/Notifications.vue +++ b/front/src/views/Notifications.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui vertical aligned stripe segment"> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical aligned stripe segment"> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> <div class="ui text loader"><translate>Loading notifications...</translate></div> </div> @@ -27,19 +27,19 @@ <translate>We don't have any notification to display!</translate> </p> </div> - </div> - </div> + </section> + </main> </template> <script> -import {mapState} from 'vuex' -import axios from 'axios' -import logger from '@/logging' +import { mapState } from "vuex" +import axios from "axios" +import logger from "@/logging" -import NotificationRow from '@/components/notifications/NotificationRow' +import NotificationRow from "@/components/notifications/NotificationRow" export default { - data () { + data() { return { isLoading: false, notifications: null, @@ -51,64 +51,63 @@ export default { components: { NotificationRow }, - created () { + created() { this.fetch(this.filters) - this.$store.commit('ui/addWebsocketEventHandler', { - eventName: 'inbox.item_added', - id: 'notificationPage', + this.$store.commit("ui/addWebsocketEventHandler", { + eventName: "inbox.item_added", + id: "notificationPage", handler: this.handleNewNotification }) }, - destroyed () { - this.$store.commit('ui/removeWebsocketEventHandler', { - eventName: 'inbox.item_added', - id: 'notificationPage', + destroyed() { + this.$store.commit("ui/removeWebsocketEventHandler", { + eventName: "inbox.item_added", + id: "notificationPage" }) }, computed: { ...mapState({ events: state => state.instance.events }), - labels () { + labels() { return { - title: this.$gettext('Notifications'), + title: this.$gettext("Notifications") } } }, methods: { - handleNewNotification (event) { + handleNewNotification(event) { this.notifications.results.unshift(event.item) }, - fetch (params) { + fetch(params) { this.isLoading = true let self = this - axios.get('federation/inbox/', {params: params}).then((response) => { + axios.get("federation/inbox/", { params: params }).then(response => { self.isLoading = false self.notifications = response.data }) }, - markAllAsRead () { + markAllAsRead() { let self = this let before = this.notifications.results[0].id let payload = { - action: 'read', - objects: 'all', + action: "read", + objects: "all", filters: { is_read: false, before } } - axios.post('federation/inbox/action/', payload).then((response) => { - self.$store.commit('ui/notifications', {type: 'inbox', count: 0}) + axios.post("federation/inbox/action/", payload).then(response => { + self.$store.commit("ui/notifications", { type: "inbox", count: 0 }) self.notifications.results.forEach(n => { n.is_read = true }) - }) - }, + } }, watch: { - 'filters.is_read' () { + "filters.is_read"() { this.fetch(this.filters) } } diff --git a/front/src/views/admin/Settings.vue b/front/src/views/admin/Settings.vue index 0aa47a5c216498296fa7279a1a192c822a4c9cb4..890eba9fdfc5b8d8f30362ecb1b2d089c37925a0 100644 --- a/front/src/views/admin/Settings.vue +++ b/front/src/views/admin/Settings.vue @@ -1,5 +1,5 @@ <template> - <div class="main pusher" v-title="labels.settings"> + <main class="main pusher" v-title="labels.settings"> <div class="ui vertical stripe segment"> <div class="ui text container"> <div :class="['ui', {'loading': isLoading}, 'form']"></div> @@ -24,146 +24,140 @@ </div> </div> - </div> + </main> </template> <script> -import axios from 'axios' -import $ from 'jquery' +import axios from "axios" +import $ from "jquery" -import SettingsGroup from '@/components/admin/SettingsGroup' +import SettingsGroup from "@/components/admin/SettingsGroup" export default { components: { SettingsGroup }, - data () { + data() { return { isLoading: false, settingsData: null, current: null } }, - created () { + created() { let self = this this.fetchSettings().then(r => { self.$nextTick(() => { if (self.$store.state.route.hash) { self.scrollTo(self.$store.state.route.hash.substr(1)) } - $('select.dropdown').dropdown() + $("select.dropdown").dropdown() }) }) }, methods: { - scrollTo (id) { + scrollTo(id) { this.current = id document.getElementById(id).scrollIntoView() }, - fetchSettings () { + fetchSettings() { let self = this self.isLoading = true - return axios.get('instance/admin/settings/').then((response) => { + return axios.get("instance/admin/settings/").then(response => { self.settingsData = response.data self.isLoading = false }) } }, computed: { - labels () { + labels() { return { - settings: this.$gettext('Instance settings') + settings: this.$gettext("Instance settings") } }, - groups () { + groups() { // somehow, extraction fails if in the return block directly - let instanceLabel = this.$gettext('Instance information') - let usersLabel = this.$gettext('Users') - let musicLabel = this.$gettext('Music') - let playlistsLabel = this.$gettext('Playlists') - let federationLabel = this.$gettext('Federation') - let subsonicLabel = this.$gettext('Subsonic') - let statisticsLabel = this.$gettext('Statistics') - let errorLabel = this.$gettext('Error reporting') + let instanceLabel = this.$gettext("Instance information") + let usersLabel = this.$gettext("Users") + let musicLabel = this.$gettext("Music") + let playlistsLabel = this.$gettext("Playlists") + let federationLabel = this.$gettext("Federation") + let subsonicLabel = this.$gettext("Subsonic") + let statisticsLabel = this.$gettext("Statistics") + let errorLabel = this.$gettext("Error reporting") return [ { label: instanceLabel, - id: 'instance', + id: "instance", settings: [ - 'instance__name', - 'instance__short_description', - 'instance__long_description' + "instance__name", + "instance__short_description", + "instance__long_description" ] }, { label: usersLabel, - id: 'users', + id: "users", settings: [ - 'users__registration_enabled', - 'common__api_authentication_required', - 'users__default_permissions', - 'users__upload_quota' + "users__registration_enabled", + "common__api_authentication_required", + "users__default_permissions", + "users__upload_quota" ] }, { label: musicLabel, - id: 'music', + id: "music", settings: [ - 'music__transcoding_enabled', - 'music__transcoding_cache_duration', + "music__transcoding_enabled", + "music__transcoding_cache_duration" ] }, { label: playlistsLabel, - id: 'playlists', - settings: [ - 'playlists__max_tracks' - ] + id: "playlists", + settings: ["playlists__max_tracks"] }, { label: federationLabel, - id: 'federation', + id: "federation", settings: [ - 'federation__enabled', - 'federation__music_needs_approval', - 'federation__collection_page_size', - 'federation__music_cache_duration', - 'federation__actor_fetch_delay' + "federation__enabled", + "federation__music_needs_approval", + "federation__collection_page_size", + "federation__music_cache_duration", + "federation__actor_fetch_delay" ] }, { label: subsonicLabel, - id: 'subsonic', - settings: [ - 'subsonic__enabled' - ] + id: "subsonic", + settings: ["subsonic__enabled"] }, { label: statisticsLabel, - id: 'statistics', + id: "statistics", settings: [ - 'instance__nodeinfo_enabled', - 'instance__nodeinfo_stats_enabled', - 'instance__nodeinfo_private' + "instance__nodeinfo_enabled", + "instance__nodeinfo_stats_enabled", + "instance__nodeinfo_private" ] }, { label: errorLabel, - id: 'reporting', - settings: [ - 'raven__front_enabled', - 'raven__front_dsn' - - ] + id: "reporting", + settings: ["raven__front_enabled", "raven__front_dsn"] } ] } }, watch: { - settingsData () { + settingsData() { let self = this this.$nextTick(() => { - $(self.$el).find('.sticky').sticky({context: '#settings-grid'}) + $(self.$el) + .find(".sticky") + .sticky({ context: "#settings-grid" }) }) } } diff --git a/front/src/views/admin/library/Base.vue b/front/src/views/admin/library/Base.vue index 22f20452f76ed5a2c244a47774a7709d0c65bcc3..45d257606b403769d865560749cf31a0700e0d09 100644 --- a/front/src/views/admin/library/Base.vue +++ b/front/src/views/admin/library/Base.vue @@ -1,10 +1,10 @@ <template> <div class="main pusher" v-title="labels.title"> - <div class="ui secondary pointing menu"> + <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" :to="{name: 'manage.library.files'}"><translate>Files</translate></router-link> - </div> + </nav> <router-view :key="$route.fullPath"></router-view> </div> </template> @@ -12,10 +12,12 @@ <script> export default { computed: { - labels () { - let title = this.$gettext('Manage library') + labels() { + let title = this.$gettext("Manage library") + let secondaryMenu = this.$gettext("Secondary menu") return { - title + title, + secondaryMenu } } } @@ -23,10 +25,8 @@ export default { </script> <style scoped> - .ui.menu .item > .label { position: absolute; right: -2em; } - </style> diff --git a/front/src/views/admin/library/FilesList.vue b/front/src/views/admin/library/FilesList.vue index 1c5216c8facf0c1c902f7b6bab0b8eb0ad19a16c..3557879bb29acb14a704a6c5113cf243ca0a4edc 100644 --- a/front/src/views/admin/library/FilesList.vue +++ b/front/src/views/admin/library/FilesList.vue @@ -1,24 +1,24 @@ <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>Library files</translate></h2> <div class="ui hidden divider"></div> <library-files-table :show-library="true"></library-files-table> - </div> - </div> + </section> + </main> </template> <script> -import LibraryFilesTable from '@/components/manage/library/FilesTable' +import LibraryFilesTable from "@/components/manage/library/FilesTable" export default { components: { LibraryFilesTable }, computed: { - labels () { + labels() { return { - title: this.$gettext('Files') + title: this.$gettext("Files") } } } diff --git a/front/src/views/admin/users/Base.vue b/front/src/views/admin/users/Base.vue index 7486d02d742254cc7d2fcb214435ffdaafd1c44f..41110fb3c39c4ab5459182377f185fb9b6506f30 100644 --- a/front/src/views/admin/users/Base.vue +++ b/front/src/views/admin/users/Base.vue @@ -1,13 +1,13 @@ <template> <div class="main pusher" v-title="labels.manageUsers"> - <div class="ui secondary pointing menu"> + <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" :to="{name: 'manage.users.users.list'}"><translate>Users</translate></router-link> <router-link class="ui item" :to="{name: 'manage.users.invitations.list'}"><translate>Invitations</translate></router-link> - </div> + </nav> <router-view :key="$route.fullPath"></router-view> </div> </template> @@ -15,9 +15,10 @@ <script> export default { computed: { - labels () { + labels() { return { - manageUsers: this.$gettext('Manage users') + manageUsers: this.$gettext("Manage users"), + secondaryMenu: this.$gettext("Secondary menu") } } } diff --git a/front/src/views/admin/users/InvitationsList.vue b/front/src/views/admin/users/InvitationsList.vue index b2dd8f0374aa21bf3eea2eba1c3cf52e86e457ad..04da76f8588af789a63fc7f8deacf2e0ff069bbc 100644 --- a/front/src/views/admin/users/InvitationsList.vue +++ b/front/src/views/admin/users/InvitationsList.vue @@ -1,17 +1,17 @@ <template> - <div v-title="labels.invitations"> - <div class="ui vertical stripe segment"> + <main v-title="labels.invitations"> + <section class="ui vertical stripe segment"> <h2 class="ui header"><translate>Invitations</translate></h2> <invitation-form></invitation-form> <div class="ui hidden divider"></div> <invitations-table></invitations-table> - </div> - </div> + </section> + </main> </template> <script> -import InvitationForm from '@/components/manage/users/InvitationForm' -import InvitationsTable from '@/components/manage/users/InvitationsTable' +import InvitationForm from "@/components/manage/users/InvitationForm" +import InvitationsTable from "@/components/manage/users/InvitationsTable" export default { components: { @@ -19,9 +19,9 @@ export default { InvitationsTable }, computed: { - labels () { + labels() { return { - invitations: this.$gettext('Invitations') + invitations: this.$gettext("Invitations") } } } diff --git a/front/src/views/admin/users/UsersDetail.vue b/front/src/views/admin/users/UsersDetail.vue index 21e7b9811fdae29b1be9e05d6060343c1485e979..b347f814a20a419eb2f6224be5f62a1ba45e3709 100644 --- a/front/src/views/admin/users/UsersDetail.vue +++ b/front/src/views/admin/users/UsersDetail.vue @@ -1,10 +1,10 @@ <template> - <div> + <main> <div v-if="isLoading" class="ui vertical segment"> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> </div> <template v-if="object"> - <div :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']" v-title="object.username"> + <section :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']" v-title="object.username"> <div class="segment-content"> <h2 class="ui center aligned icon header"> <i class="circular inverted user red icon"></i> @@ -102,35 +102,34 @@ </div> <div class="ui hidden divider"></div> <button @click="fetchData" class="ui basic button"><translate>Refresh</translate></button> - </div> + </section> </template> - </div> + </main> </template> <script> - -import $ from 'jquery' -import axios from 'axios' -import logger from '@/logging' +import $ from "jquery" +import axios from "axios" +import logger from "@/logging" export default { - props: ['id'], - data () { + props: ["id"], + data() { return { isLoading: true, object: null, permissions: [] } }, - created () { + created() { this.fetchData() }, methods: { - fetchData () { + fetchData() { var self = this this.isLoading = true - let url = 'manage/users/users/' + this.id + '/' - axios.get(url).then((response) => { + let url = "manage/users/users/" + this.id + "/" + axios.get(url).then(response => { self.object = response.data self.permissions = [] self.allPermissions.forEach(p => { @@ -141,60 +140,72 @@ export default { self.isLoading = false }) }, - update (attr, toNull) { + update(attr, toNull) { let newValue = this.object[attr] if (toNull && !newValue) { newValue = null } - console.log(newValue, typeof(newValue)) + console.log(newValue, typeof newValue) let params = {} - if (attr === 'permissions') { - params['permissions'] = {} + if (attr === "permissions") { + params["permissions"] = {} this.allPermissions.forEach(p => { - params['permissions'][p.code] = this.permissions.indexOf(p.code) > -1 + params["permissions"][p.code] = this.permissions.indexOf(p.code) > -1 }) } else { params[attr] = newValue } - axios.patch('manage/users/users/' + this.id + '/', params).then((response) => { - logger.default.info(`${attr} was updated succcessfully to ${newValue}`) - }, (error) => { - logger.default.error(`Error while setting ${attr} to ${newValue}`, error) - }) + axios.patch("manage/users/users/" + this.id + "/", params).then( + response => { + logger.default.info( + `${attr} was updated succcessfully to ${newValue}` + ) + }, + error => { + logger.default.error( + `Error while setting ${attr} to ${newValue}`, + error + ) + } + ) } }, computed: { - labels () { + labels() { return { - inactive: this.$gettext('Determine if the user account is active or not. Inactive users cannot login or use the service.'), - uploadQuota: this.$gettext('Determine how much content the user can upload. Leave empty to use the default value of the instance.') + inactive: this.$gettext( + "Determine if the user account is active or not. Inactive users cannot login or use the service." + ), + uploadQuota: this.$gettext( + "Determine how much content the user can upload. Leave empty to use the default value of the instance." + ) } }, - allPermissions () { + allPermissions() { return [ { - 'code': 'upload', - 'label': this.$gettext('Upload') + code: "upload", + label: this.$gettext("Upload") }, { - 'code': 'library', - 'label': this.$gettext('Library') + code: "library", + label: this.$gettext("Library") }, { - 'code': 'federation', - 'label': this.$gettext('Federation') + code: "federation", + label: this.$gettext("Federation") }, { - 'code': 'settings', - 'label': this.$gettext('Settings') + code: "settings", + label: this.$gettext("Settings") } ] } }, watch: { - object () { + object() { this.$nextTick(() => { - $('select.dropdown').dropdown() + $("select.dropdown").dropdown() }) } } diff --git a/front/src/views/admin/users/UsersList.vue b/front/src/views/admin/users/UsersList.vue index ef4d60961e89ba2566d484bdd42ae800796beded..c0ee4166cd1fd976738a1315b4bce9aa35e512c1 100644 --- a/front/src/views/admin/users/UsersList.vue +++ b/front/src/views/admin/users/UsersList.vue @@ -1,24 +1,24 @@ <template> - <div v-title="labels.users"> - <div class="ui vertical stripe segment"> + <main v-title="labels.users"> + <section class="ui vertical stripe segment"> <h2 class="ui header"><translate>Users</translate></h2> <div class="ui hidden divider"></div> <users-table></users-table> - </div> - </div> + </section> + </main> </template> <script> -import UsersTable from '@/components/manage/users/UsersTable' +import UsersTable from "@/components/manage/users/UsersTable" export default { components: { UsersTable }, computed: { - labels () { + labels() { return { - users: this.$gettext('Users') + users: this.$gettext("Users") } } } diff --git a/front/src/views/auth/EmailConfirm.vue b/front/src/views/auth/EmailConfirm.vue index 7b982504506781119fc27efbffd53ff9c61b3854..c2d83326e1b1e1086208d77a22b125df9db890d3 100644 --- a/front/src/views/auth/EmailConfirm.vue +++ b/front/src/views/auth/EmailConfirm.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.confirm"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.confirm"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2><translate>Confirm your email</translate></h2> <form v-if="!success" class="ui form" @submit.prevent="submit()"> @@ -28,16 +28,16 @@ </router-link> </div> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' +import axios from "axios" export default { - props: ['defaultKey'], - data () { + props: ["defaultKey"], + data() { return { isLoading: false, errors: [], @@ -46,30 +46,32 @@ export default { } }, computed: { - labels () { + labels() { return { - confirm: this.$gettext('Confirm your email') + confirm: this.$gettext("Confirm your email") } } }, methods: { - submit () { + submit() { let self = this self.isLoading = true self.errors = [] let payload = { key: this.key } - return axios.post('auth/registration/verify-email/', payload).then(response => { - self.isLoading = false - self.success = true - }, error => { - self.errors = error.backendErrors - self.isLoading = false - }) + return axios.post("auth/registration/verify-email/", payload).then( + response => { + self.isLoading = false + self.success = true + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) } } - } </script> diff --git a/front/src/views/auth/PasswordReset.vue b/front/src/views/auth/PasswordReset.vue index 52787a516ff52e22813f83579973bf6de8170387..e7759113c33b80676983ce64d51acfbaf519855e 100644 --- a/front/src/views/auth/PasswordReset.vue +++ b/front/src/views/auth/PasswordReset.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.reset"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.reset"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2><translate>Reset your password</translate></h2> <form class="ui form" @submit.prevent="submit()"> @@ -28,29 +28,31 @@ <translate>Ask for a password reset</translate></button> </form> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' +import axios from "axios" export default { - props: ['defaultEmail'], - data () { + props: ["defaultEmail"], + data() { return { email: this.defaultEmail, isLoading: false, errors: [] } }, - mounted () { + mounted() { this.$refs.email.focus() }, computed: { - labels () { - let reset = this.$gettext('Reset your password') - let placeholder = this.$gettext('Input the email address binded to your account') + labels() { + let reset = this.$gettext("Reset your password") + let placeholder = this.$gettext( + "Input the email address binded to your account" + ) return { reset, placeholder @@ -58,25 +60,27 @@ export default { } }, methods: { - submit () { + submit() { let self = this self.isLoading = true self.errors = [] let payload = { email: this.email } - return axios.post('auth/password/reset/', payload).then(response => { - self.isLoading = false - self.$router.push({ - name: 'auth.password-reset-confirm' - }) - }, error => { - self.errors = error.backendErrors - self.isLoading = false - }) + return axios.post("auth/password/reset/", payload).then( + response => { + self.isLoading = false + self.$router.push({ + name: "auth.password-reset-confirm" + }) + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) } } - } </script> diff --git a/front/src/views/auth/PasswordResetConfirm.vue b/front/src/views/auth/PasswordResetConfirm.vue index b6e4f23224242d160b0c2121b176ed5fb07a8287..df0589beb8d13d909bcefaefe4268fef91bacbaf 100644 --- a/front/src/views/auth/PasswordResetConfirm.vue +++ b/front/src/views/auth/PasswordResetConfirm.vue @@ -1,6 +1,6 @@ <template> - <div class="main pusher" v-title="labels.changePassword"> - <div class="ui vertical stripe segment"> + <main class="main pusher" v-title="labels.changePassword"> + <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2><translate>Change your password</translate></h2> <form v-if="!success" class="ui form" @submit.prevent="submit()"> @@ -33,22 +33,22 @@ </router-link> </div> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' -import PasswordInput from '@/components/forms/PasswordInput' +import axios from "axios" +import PasswordInput from "@/components/forms/PasswordInput" export default { - props: ['defaultToken', 'defaultUid'], + props: ["defaultToken", "defaultUid"], components: { PasswordInput }, - data () { + data() { return { - newPassword: '', + newPassword: "", isLoading: false, errors: [], token: this.defaultToken, @@ -57,14 +57,14 @@ export default { } }, computed: { - labels () { + labels() { return { - changePassword: this.$gettext('Change your password') + changePassword: this.$gettext("Change your password") } } }, methods: { - submit () { + submit() { let self = this self.isLoading = true self.errors = [] @@ -74,16 +74,18 @@ export default { new_password1: this.newPassword, new_password2: this.newPassword } - return axios.post('auth/password/reset/confirm/', payload).then(response => { - self.isLoading = false - self.success = true - }, error => { - self.errors = error.backendErrors - self.isLoading = false - }) + return axios.post("auth/password/reset/confirm/", payload).then( + response => { + self.isLoading = false + self.success = true + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) } } - } </script> diff --git a/front/src/views/content/Base.vue b/front/src/views/content/Base.vue index cfdd204281bb2086700e1abf744fda1121a8ad51..039e21adcd311edd99badb5443ac5f8a16f0f478 100644 --- a/front/src/views/content/Base.vue +++ b/front/src/views/content/Base.vue @@ -1,24 +1,25 @@ <template> - <div class="main pusher" v-title="labels.title"> - <div class="ui secondary pointing menu"> + <main class="main pusher" v-title="labels.title"> + <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" :to="{name: 'content.libraries.index'}"><translate>Libraries</translate></router-link> <router-link class="ui item" :to="{name: 'content.libraries.files'}"><translate>Tracks</translate></router-link> - </div> + </nav> <router-view :key="$route.fullPath"></router-view> - </div> + </main> </template> <script> - export default { computed: { - labels () { - let title = this.$gettext('Add content') + labels() { + let title = this.$gettext("Add content") + let secondaryMenu = this.$gettext("Secondary menu") return { - title + title, + secondaryMenu } } } diff --git a/front/src/views/content/Home.vue b/front/src/views/content/Home.vue index 91fe11824d694ff613c99065213e8b4916e975a0..d75a4a89f139605cd3bbc4e2cb8adaac47c9b0bd 100644 --- a/front/src/views/content/Home.vue +++ b/front/src/views/content/Home.vue @@ -1,5 +1,5 @@ <template> - <div class="ui vertical aligned stripe segment" v-title="labels.title"> + <section class="ui vertical aligned stripe segment" v-title="labels.title"> <div class="ui text container"> <h1>{{ labels.title }}</h1> <p><translate>We offer various way to grab new content and make it available here.</translate></p> @@ -22,21 +22,24 @@ </div> </div> - </div> + </section> </template> <script> -import {humanSize} from '@/filters' +import { humanSize } from "@/filters" export default { computed: { - labels () { + labels() { return { - title: this.$gettext('Add and manage content') + title: this.$gettext("Add and manage content") } }, - defaultQuota () { - let quota = this.$store.state.instance.settings.users.upload_quota.value * 1000 * 1000 + defaultQuota() { + let quota = + this.$store.state.instance.settings.users.upload_quota.value * + 1000 * + 1000 return humanSize(quota) } } diff --git a/front/src/views/content/libraries/Detail.vue b/front/src/views/content/libraries/Detail.vue index eed851e5e551b48ec9b4cc09485e7da407b6f47b..324a16c7f642789ae10fbe0393262b4ecae1c58b 100644 --- a/front/src/views/content/libraries/Detail.vue +++ b/front/src/views/content/libraries/Detail.vue @@ -1,5 +1,5 @@ <template> - <div class="ui vertical aligned stripe segment"> + <section class="ui vertical aligned stripe segment"> <div v-if="isLoadingLibrary" :class="['ui', {'active': isLoadingLibrary}, 'inverted', 'dimmer']"> <div class="ui text loader"><translate>Loading library data...</translate></div> </div> @@ -64,15 +64,15 @@ <library-form :library="library" @updated="libraryUpdated" @deleted="libraryDeleted" /> </div> </detail-area> - </div> + </section> </template> <script> -import axios from 'axios' -import DetailMixin from './DetailMixin' -import DetailArea from './DetailArea' -import LibraryForm from './Form' -import LibraryFilesTable from './FilesTable' +import axios from "axios" +import DetailMixin from "./DetailMixin" +import DetailArea from "./DetailArea" +import LibraryForm from "./Form" +import LibraryFilesTable from "./FilesTable" export default { mixins: [DetailMixin], @@ -81,46 +81,48 @@ export default { LibraryForm, LibraryFilesTable }, - data () { + data() { return { - currentTab: 'follows', + currentTab: "follows", isLoadingFollows: false, follows: null } }, - created () { + created() { this.fetchFollows() }, methods: { - libraryUpdated () { + libraryUpdated() { this.hiddenForm = true this.fetch() }, - libraryDeleted () { + libraryDeleted() { this.$router.push({ - name: 'content.libraries.index' + name: "content.libraries.index" }) }, - fetchFollows () { + fetchFollows() { let self = this self.isLoadingLibrary = true - axios.get(`libraries/${this.id}/follows/`).then((response) => { + axios.get(`libraries/${this.id}/follows/`).then(response => { self.follows = response.data self.isLoadingFollows = false }) }, - updateApproved (follow, value) { + updateApproved(follow, value) { let self = this let action if (value) { - action = 'accept' + action = "accept" } else { - action = 'reject' + action = "reject" } - axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => { - follow.isLoading = false - follow.approved = value - }) + axios + .post(`federation/follows/library/${follow.uuid}/${action}/`) + .then(response => { + follow.isLoading = false + follow.approved = value + }) } } } diff --git a/front/src/views/content/libraries/Files.vue b/front/src/views/content/libraries/Files.vue index 752dcd77699056c6eac175d2faf571f37cb1a3e1..0184df3864d0080900e1fa8aeb7f7243acf2c70a 100644 --- a/front/src/views/content/libraries/Files.vue +++ b/front/src/views/content/libraries/Files.vue @@ -1,14 +1,14 @@ <template> - <div class="ui vertical aligned stripe segment"> + <section class="ui vertical aligned stripe segment"> <library-files-table :default-query="query"></library-files-table> - </div> + </section> </template> <script> -import LibraryFilesTable from './FilesTable' +import LibraryFilesTable from "./FilesTable" export default { - props: ['query'], + props: ["query"], components: { LibraryFilesTable } diff --git a/front/src/views/content/libraries/Home.vue b/front/src/views/content/libraries/Home.vue index 98dc2dde3e6fd6fc820427491d86533d1155c372..4162cd72c4d07922b06dc75d11fdbe0b1bd49032 100644 --- a/front/src/views/content/libraries/Home.vue +++ b/front/src/views/content/libraries/Home.vue @@ -1,5 +1,5 @@ <template> - <div class="ui vertical aligned stripe segment"> + <section class="ui vertical aligned stripe segment"> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> <div class="ui text loader"><translate>Loading Libraries...</translate></div> </div> @@ -24,24 +24,24 @@ </div> </div> </div> - </div> + </section> </template> <script> -import axios from 'axios' -import LibraryForm from './Form' -import LibraryCard from './Card' -import Quota from './Quota' +import axios from "axios" +import LibraryForm from "./Form" +import LibraryCard from "./Card" +import Quota from "./Quota" export default { - data () { + data() { return { isLoading: false, hiddenForm: true, libraries: [] } }, - created () { + created() { this.fetch() }, components: { @@ -50,10 +50,10 @@ export default { Quota }, methods: { - fetch () { + fetch() { this.isLoading = true let self = this - axios.get('libraries/').then((response) => { + axios.get("libraries/").then(response => { self.isLoading = false self.libraries = response.data.results if (self.libraries.length === 0) { @@ -61,7 +61,7 @@ export default { } }) }, - libraryCreated (library) { + libraryCreated(library) { this.hiddenForm = true this.libraries.unshift(library) } diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue index c1a08a7d1c51e5995c495e08cd48d495fbab8a50..9a548106d2a5035fcc8bfb4a5d869a5a324dbe2d 100644 --- a/front/src/views/playlists/Detail.vue +++ b/front/src/views/playlists/Detail.vue @@ -1,9 +1,9 @@ <template> - <div> + <main> <div v-if="isLoading" class="ui vertical segment" v-title="labels.playlist"> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> </div> - <div v-if="!isLoading && playlist" class="ui head vertical center aligned stripe segment" v-title="playlist.name"> + <section v-if="!isLoading && playlist" class="ui head vertical center aligned stripe segment" v-title="playlist.name"> <div class="segment-content"> <h2 class="ui center aligned icon header"> <i class="circular inverted list yellow icon"></i> @@ -39,8 +39,8 @@ <p slot="modal-confirm"><translate>Delete playlist</translate></p> </dangerous-button> </div> - </div> - <div class="ui vertical stripe segment"> + </section> + <section class="ui vertical stripe segment"> <template v-if="edit"> <playlist-editor @playlist-updated="playlist = $event" @@ -51,20 +51,20 @@ <h2><translate>Tracks</translate></h2> <track-table :display-position="true" :tracks="tracks"></track-table> </template> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' -import TrackTable from '@/components/audio/track/Table' -import RadioButton from '@/components/radios/Button' -import PlayButton from '@/components/audio/PlayButton' -import PlaylistEditor from '@/components/playlists/Editor' +import axios from "axios" +import TrackTable from "@/components/audio/track/Table" +import RadioButton from "@/components/radios/Button" +import PlayButton from "@/components/audio/PlayButton" +import PlaylistEditor from "@/components/playlists/Editor" export default { props: { - id: {required: true}, - defaultEdit: {type: Boolean, default: false} + id: { required: true }, + defaultEdit: { type: Boolean, default: false } }, components: { PlaylistEditor, @@ -72,7 +72,7 @@ export default { PlayButton, RadioButton }, - data: function () { + data: function() { return { edit: this.defaultEdit, isLoading: false, @@ -81,18 +81,18 @@ export default { playlistTracks: [] } }, - created: function () { + created: function() { this.fetch() }, computed: { - labels () { + labels() { return { - playlist: this.$gettext('Playlist') + playlist: this.$gettext("Playlist") } } }, methods: { - updatePlts (v) { + updatePlts(v) { this.playlistTracks = v this.tracks = v.map((e, i) => { let track = e.track @@ -100,26 +100,29 @@ export default { return track }) }, - fetch: function () { + fetch: function() { let self = this self.isLoading = true - let url = 'playlists/' + this.id + '/' - axios.get(url).then((response) => { + let url = "playlists/" + this.id + "/" + axios.get(url).then(response => { self.playlist = response.data - axios.get(url + 'tracks/').then((response) => { - self.updatePlts(response.data.results) - }).then(() => { - self.isLoading = false - }) + axios + .get(url + "tracks/") + .then(response => { + self.updatePlts(response.data.results) + }) + .then(() => { + self.isLoading = false + }) }) }, - deletePlaylist () { + deletePlaylist() { let self = this - let url = 'playlists/' + this.id + '/' - axios.delete(url).then((response) => { - self.$store.dispatch('playlists/fetchOwn') + let url = "playlists/" + this.id + "/" + axios.delete(url).then(response => { + self.$store.dispatch("playlists/fetchOwn") self.$router.push({ - path: '/library' + path: "/library" }) }) } diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue index 47035da7fc6df7ce15fe993559071d9dbf8b2069..de9ca78ac74d19ec05d4b3b0446eae06bcd36973 100644 --- a/front/src/views/playlists/List.vue +++ b/front/src/views/playlists/List.vue @@ -1,6 +1,6 @@ <template> - <div v-title="labels.playlists"> - <div class="ui vertical stripe segment"> + <main v-title="labels.playlists"> + <section class="ui vertical stripe segment"> <h2 class="ui header"><translate>Browsing playlists</translate></h2> <div :class="['ui', {'loading': isLoading}, 'form']"> <template v-if="$store.state.auth.authenticated"> @@ -50,59 +50,61 @@ :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 OrderingMixin from '@/components/mixins/Ordering' -import PaginationMixin from '@/components/mixins/Pagination' -import TranslationsMixin from '@/components/mixins/Translations' -import PlaylistCardList from '@/components/playlists/CardList' -import Pagination from '@/components/Pagination' +import OrderingMixin from "@/components/mixins/Ordering" +import PaginationMixin from "@/components/mixins/Pagination" +import TranslationsMixin from "@/components/mixins/Translations" +import PlaylistCardList from "@/components/playlists/CardList" +import Pagination from "@/components/Pagination" -const FETCH_URL = 'playlists/' +const FETCH_URL = "playlists/" export default { mixins: [OrderingMixin, PaginationMixin, TranslationsMixin], props: { - defaultQuery: {type: String, required: false, default: ''} + defaultQuery: { type: String, required: false, default: "" } }, components: { PlaylistCardList, 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'], - ['modification_date', 'modification_date'], - ['name', 'name'] + ["creation_date", "creation_date"], + ["modification_date", "modification_date"], + ["name", "name"] ] } }, - created () { + created() { this.fetchData() }, - mounted () { - $('.ui.dropdown').dropdown() + mounted() { + $(".ui.dropdown").dropdown() }, computed: { - labels () { - let playlists = this.$gettext('Playlists') - let searchPlaceholder = this.$gettext('Enter an playlist name...') + labels() { + let playlists = this.$gettext("Playlists") + let searchPlaceholder = this.$gettext("Enter an playlist name...") return { playlists, searchPlaceholder @@ -110,7 +112,7 @@ export default { } }, methods: { - updateQueryString: _.debounce(function () { + updateQueryString: _.debounce(function() { this.$router.replace({ query: { query: this.query, @@ -120,7 +122,7 @@ export default { } }) }, 250), - fetchData: _.debounce(function () { + fetchData: _.debounce(function() { var self = this this.isLoading = true let url = FETCH_URL @@ -130,33 +132,33 @@ export default { q: this.query, ordering: this.getOrderingAsString() } - axios.get(url, {params: params}).then((response) => { + 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() } diff --git a/front/src/views/radios/Detail.vue b/front/src/views/radios/Detail.vue index e269bf284e9c08fbd52e00061070d4f3fac02bb3..0c46385a6bab53f50f96d65acd491ac6c287e4c3 100644 --- a/front/src/views/radios/Detail.vue +++ b/front/src/views/radios/Detail.vue @@ -1,9 +1,9 @@ <template> - <div> + <main> <div v-if="isLoading" class="ui vertical segment" v-title="labels.title"> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> </div> - <div v-if="!isLoading && radio" class="ui head vertical center aligned stripe segment" v-title="radio.name"> + <section v-if="!isLoading && radio" class="ui head vertical center aligned stripe segment" v-title="radio.name"> <div class="segment-content"> <h2 class="ui center aligned icon header"> <i class="circular inverted feed blue icon"></i> @@ -30,8 +30,8 @@ </dangerous-button> </template> </div> - </div> - <div class="ui vertical stripe segment"> + </section> + <section class="ui vertical stripe segment"> <h2><translate>Tracks</translate></h2> <track-table :tracks="tracks"></track-table> <div class="ui center aligned basic segment"> @@ -43,26 +43,26 @@ :total="totalTracks" ></pagination> </div> - </div> - </div> + </section> + </main> </template> <script> -import axios from 'axios' -import TrackTable from '@/components/audio/track/Table' -import RadioButton from '@/components/radios/Button' -import Pagination from '@/components/Pagination' +import axios from "axios" +import TrackTable from "@/components/audio/track/Table" +import RadioButton from "@/components/radios/Button" +import Pagination from "@/components/Pagination" export default { props: { - id: {required: true} + id: { required: true } }, components: { TrackTable, RadioButton, Pagination }, - data: function () { + data: function() { return { isLoading: false, radio: null, @@ -71,46 +71,49 @@ export default { page: 1 } }, - created: function () { + created: function() { this.fetch() }, computed: { - labels () { + labels() { return { - title: this.$gettext('Radio') + title: this.$gettext("Radio") } } }, methods: { - selectPage: function (page) { + selectPage: function(page) { this.page = page }, - 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.radio = response.data - axios.get(url + 'tracks/', {params: {page: this.page}}).then((response) => { - this.totalTracks = response.data.count - this.tracks = response.data.results - }).then(() => { - self.isLoading = false - }) + axios + .get(url + "tracks/", { params: { page: this.page } }) + .then(response => { + this.totalTracks = response.data.count + this.tracks = response.data.results + }) + .then(() => { + self.isLoading = false + }) }) }, - deleteRadio () { + deleteRadio() { let self = this - let url = 'radios/radios/' + this.id + '/' - axios.delete(url).then((response) => { + let url = "radios/radios/" + this.id + "/" + axios.delete(url).then(response => { self.$router.push({ - path: '/library' + path: "/library" }) }) } }, watch: { - page: function () { + page: function() { this.fetch() } }