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