Fix #612: Improved accessibility by using main/section/nav tags and aria-labels in most critical places
This commit is contained in:
parent
9005ebbd6d
commit
29171853b3
|
@ -0,0 +1 @@
|
||||||
|
Improved accessibility by using main/section/nav tags and aria-labels in most critical places (#612)
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical center aligned stripe segment">
|
<section class="ui vertical center aligned stripe segment">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
<h1 class="ui huge header">
|
<h1 class="ui huge header">
|
||||||
<translate v-if="instance.name.value" :translate-params="{instance: instance.name.value}">
|
<translate v-if="instance.name.value" :translate-params="{instance: instance.name.value}">
|
||||||
|
@ -10,8 +10,8 @@
|
||||||
</h1>
|
</h1>
|
||||||
<stats></stats>
|
<stats></stats>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<p v-if="!instance.short_description.value && !instance.long_description.value">
|
<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>
|
<translate>Unfortunately, owners of this instance did not yet take the time to complete this page.</translate>
|
||||||
</p>
|
</p>
|
||||||
|
@ -31,28 +31,28 @@
|
||||||
class="ui middle aligned stackable text container"
|
class="ui middle aligned stackable text container"
|
||||||
v-html="$options.filters.markdown(instance.long_description.value)">
|
v-html="$options.filters.markdown(instance.long_description.value)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import { mapState } from "vuex"
|
||||||
import Stats from '@/components/instance/Stats'
|
import Stats from "@/components/instance/Stats"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Stats
|
Stats
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.$store.dispatch('instance/fetchSettings')
|
this.$store.dispatch("instance/fetchSettings")
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
instance: state => state.instance.settings.instance
|
instance: state => state.instance.settings.instance
|
||||||
}),
|
}),
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('About this instance')
|
title: this.$gettext("About this instance")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<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 container">
|
||||||
<div class="ui stackable equal height stackable grid">
|
<div class="ui stackable equal height stackable grid">
|
||||||
<div class="four wide column">
|
<section class="four wide column">
|
||||||
<h4 v-translate class="ui header">
|
<h4 v-translate class="ui header">
|
||||||
<translate :translate-params="{instanceName: instanceHostname}" >About %{instanceName}</translate>
|
<translate :translate-params="{instanceName: instanceHostname}" >About %{instanceName}</translate>
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -25,24 +25,24 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="four wide column">
|
<section class="four wide column">
|
||||||
<h4 v-translate class="ui header">Using Funkwhale</h4>
|
<h4 v-translate class="ui header">Using Funkwhale</h4>
|
||||||
<div class="ui link list">
|
<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" 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>
|
<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 role="button" class="item" @click="$emit('show:shortcuts-modal')"><translate>Keyboard shortcuts</translate></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="four wide column">
|
<section class="four wide column">
|
||||||
<h4 v-translate class="ui header">Getting help</h4>
|
<h4 v-translate class="ui header">Getting help</h4>
|
||||||
<div class="ui link list">
|
<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://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://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>
|
<a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank"><translate>Issue tracker</translate></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="four wide column">
|
<section class="four wide column">
|
||||||
<h4 v-translate class="ui header">About Funkwhale</h4>
|
<h4 v-translate class="ui header">About Funkwhale</h4>
|
||||||
<div class="ui link list">
|
<div class="ui link list">
|
||||||
<a href="https://funkwhale.audio" class="item" target="_blank"><translate>Official website</translate></a>
|
<a href="https://funkwhale.audio" class="item" target="_blank"><translate>Official website</translate></a>
|
||||||
|
@ -53,45 +53,51 @@
|
||||||
<p>
|
<p>
|
||||||
<translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate>
|
<translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import { mapState } from "vuex"
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['version'],
|
props: ["version"],
|
||||||
methods: {
|
methods: {
|
||||||
switchInstance () {
|
switchInstance() {
|
||||||
let confirm = window.confirm(this.$gettext('This will erase your local data and disconnect you, do you want to continue?'))
|
let confirm = window.confirm(
|
||||||
|
this.$gettext(
|
||||||
|
"This will erase your local data and disconnect you, do you want to continue?"
|
||||||
|
)
|
||||||
|
)
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
this.$store.commit('instance/instanceUrl', null)
|
this.$store.commit("instance/instanceUrl", null)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
messages: state => state.ui.messages
|
messages: state => state.ui.messages
|
||||||
}),
|
}),
|
||||||
instanceHostname () {
|
instanceHostname() {
|
||||||
let url = this.$store.state.instance.instanceUrl
|
let url = this.$store.state.instance.instanceUrl
|
||||||
let parser = document.createElement('a');
|
let parser = document.createElement("a")
|
||||||
parser.href = url
|
parser.href = url
|
||||||
return parser.hostname
|
return parser.hostname
|
||||||
},
|
},
|
||||||
suggestedInstances () {
|
suggestedInstances() {
|
||||||
let instances = [this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio']
|
let instances = [
|
||||||
|
this.$store.getters["instance/defaultUrl"](),
|
||||||
|
"https://demo.funkwhale.audio"
|
||||||
|
]
|
||||||
return instances
|
return instances
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
footer p {
|
footer p {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical center aligned stripe segment">
|
<section class="ui vertical center aligned stripe segment">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
<h1 class="ui huge header">
|
<h1 class="ui huge header">
|
||||||
<translate>Welcome on Funkwhale</translate>
|
<translate>Welcome on Funkwhale</translate>
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
<i class="right arrow icon"></i>
|
<i class="right arrow icon"></i>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui middle aligned stackable text container">
|
<div class="ui middle aligned stackable text container">
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -136,22 +136,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
musicbrainzUrl: 'https://musicbrainz.org/'
|
musicbrainzUrl: "https://musicbrainz.org/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Welcome')
|
title: this.$gettext("Welcome")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" :v-title="labels.title">
|
<main class="main pusher" :v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
<h1 class="ui huge header">
|
<h1 class="ui huge header">
|
||||||
<i class="warning icon"></i>
|
<i class="warning icon"></i>
|
||||||
|
@ -16,21 +16,21 @@
|
||||||
<i class="right arrow icon"></i>
|
<i class="right arrow icon"></i>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
path: window.location.href
|
path: window.location.href
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Page Not Found')
|
title: this.$gettext("Page Not Found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ui pagination menu">
|
<div class="ui pagination menu" role="navigation" :aria-label="labels.pagination">
|
||||||
<a href
|
<a href
|
||||||
:disabled="current - 1 < 1"
|
:disabled="current - 1 < 1"
|
||||||
@click.prevent.stop="selectPage(current - 1)"
|
@click.prevent.stop="selectPage(current - 1)"
|
||||||
|
@ -24,30 +24,42 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
current: {type: Number, default: 1},
|
current: { type: Number, default: 1 },
|
||||||
paginateBy: {type: Number, default: 25},
|
paginateBy: { type: Number, default: 25 },
|
||||||
total: {type: Number},
|
total: { type: Number },
|
||||||
compact: {type: Boolean, default: false}
|
compact: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pages: function () {
|
labels() {
|
||||||
|
return {
|
||||||
|
pagination: this.$gettext("Pagination")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pages: function() {
|
||||||
let range = 2
|
let range = 2
|
||||||
let current = this.current
|
let current = this.current
|
||||||
let beginning = _.range(1, Math.min(this.maxPage, 1 + range))
|
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 end = _.range(this.maxPage, Math.max(1, this.maxPage - range))
|
||||||
let allowed = beginning.concat(middle, end)
|
let allowed = beginning.concat(middle, end)
|
||||||
allowed = _.uniq(allowed)
|
allowed = _.uniq(allowed)
|
||||||
allowed = _.sortBy(allowed, [(e) => { return e }])
|
allowed = _.sortBy(allowed, [
|
||||||
|
e => {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
])
|
||||||
let final = []
|
let final = []
|
||||||
allowed.forEach(p => {
|
allowed.forEach(p => {
|
||||||
let last = final.slice(-1)[0]
|
let last = final.slice(-1)[0]
|
||||||
let consecutive = true
|
let consecutive = true
|
||||||
if (last === 'skip') {
|
if (last === "skip") {
|
||||||
consecutive = false
|
consecutive = false
|
||||||
} else {
|
} else {
|
||||||
if (!last) {
|
if (!last) {
|
||||||
|
@ -59,25 +71,25 @@ export default {
|
||||||
if (consecutive) {
|
if (consecutive) {
|
||||||
final.push(p)
|
final.push(p)
|
||||||
} else {
|
} else {
|
||||||
if (p !== 'skip') {
|
if (p !== "skip") {
|
||||||
final.push('skip')
|
final.push("skip")
|
||||||
final.push(p)
|
final.push(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return final
|
return final
|
||||||
},
|
},
|
||||||
maxPage: function () {
|
maxPage: function() {
|
||||||
return Math.ceil(this.total / this.paginateBy)
|
return Math.ceil(this.total / this.paginateBy)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
if (page > this.maxPage || page < 1) {
|
if (page > this.maxPage || page < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.current !== page) {
|
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 -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ui.pagination.menu .item {
|
.ui.pagination.menu .item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar',]">
|
<aside :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar',]">
|
||||||
<div class="ui inverted segment header-wrapper">
|
<header class="ui inverted segment header-wrapper">
|
||||||
<search-bar @search="isCollapsed = false">
|
<search-bar @search="isCollapsed = false">
|
||||||
<router-link :title="'Funkwhale'" :to="{name: logoUrl}">
|
<router-link :title="'Funkwhale'" :to="{name: logoUrl}">
|
||||||
<i class="logo bordered inverted orange big icon">
|
<i class="logo bordered inverted orange big icon">
|
||||||
|
@ -12,12 +12,12 @@
|
||||||
:class="['ui', 'basic', 'big', {'inverted': isCollapsed}, 'orange', 'icon', 'collapse', 'button']">
|
:class="['ui', 'basic', 'big', {'inverted': isCollapsed}, 'orange', 'icon', 'collapse', 'button']">
|
||||||
<i class="sidebar icon"></i></span>
|
<i class="sidebar icon"></i></span>
|
||||||
</search-bar>
|
</search-bar>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<div class="menu-area">
|
<div class="menu-area">
|
||||||
<div class="ui compact fluid two item inverted menu">
|
<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="active item" role="button" @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="item" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
|
||||||
<translate>Queue</translate>
|
<translate>Queue</translate>
|
||||||
<template v-if="queue.tracks.length === 0">
|
<template v-if="queue.tracks.length === 0">
|
||||||
<translate>(empty)</translate>
|
<translate>(empty)</translate>
|
||||||
|
@ -29,10 +29,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<div class="ui bottom attached active tab" data-tab="library">
|
<section class="ui bottom attached active tab" data-tab="library" :aria-label="labels.mainMenu">
|
||||||
<div class="ui inverted vertical large fluid menu">
|
<nav class="ui inverted vertical large fluid menu" role="navigation" :aria-label="labels.mainMenu">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="header"><translate>My account</translate></div>
|
<header class="header"><translate>My account</translate></header>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}">
|
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}">
|
||||||
<i class="user icon"></i>
|
<i class="user icon"></i>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="header"><translate>Music</translate></div>
|
<header class="header"><translate>Music</translate></header>
|
||||||
<div class="menu">
|
<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" :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>
|
<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>
|
</div>
|
||||||
<div class="item" v-if="$store.state.auth.availablePermissions['settings']">
|
<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">
|
<div class="menu">
|
||||||
<router-link
|
<router-link
|
||||||
class="item"
|
class="item"
|
||||||
|
@ -91,8 +91,8 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
</section>
|
||||||
<div v-if="queue.previousQueue " class="ui black icon message">
|
<div v-if="queue.previousQueue " class="ui black icon message">
|
||||||
<i class="history icon"></i>
|
<i class="history icon"></i>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -113,17 +113,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<table class="ui compact inverted very basic fixed single line unstackable table">
|
||||||
<draggable v-model="tracks" element="tbody" @update="reorder">
|
<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="right aligned">{{ index + 1}}</td>
|
||||||
<td class="center aligned">
|
<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-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">
|
<img class="ui mini image" v-else src="../assets/audio/default-cover.png">
|
||||||
</td>
|
</td>
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
<button class="title reset ellipsis">
|
<button class="title reset ellipsis" :aria-label="labels.selectTrack">
|
||||||
<strong>{{ track.title }}</strong><br />
|
<strong>{{ track.title }}</strong><br />
|
||||||
{{ track.artist.name }}
|
{{ track.artist.name }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -134,7 +138,7 @@
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<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>
|
<i class="trash icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -150,44 +154,46 @@
|
||||||
<div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate>Stop radio</translate></div>
|
<div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate>Stop radio</translate></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<player @next="scrollToCurrent" @previous="scrollToCurrent"></player>
|
<player @next="scrollToCurrent" @previous="scrollToCurrent"></player>
|
||||||
</div>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState, mapActions} from 'vuex'
|
import { mapState, mapActions } from "vuex"
|
||||||
|
|
||||||
import Player from '@/components/audio/Player'
|
import Player from "@/components/audio/Player"
|
||||||
import Logo from '@/components/Logo'
|
import Logo from "@/components/Logo"
|
||||||
import SearchBar from '@/components/audio/SearchBar'
|
import SearchBar from "@/components/audio/SearchBar"
|
||||||
import backend from '@/audio/backend'
|
import backend from "@/audio/backend"
|
||||||
import draggable from 'vuedraggable'
|
import draggable from "vuedraggable"
|
||||||
|
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'sidebar',
|
name: "sidebar",
|
||||||
components: {
|
components: {
|
||||||
Player,
|
Player,
|
||||||
SearchBar,
|
SearchBar,
|
||||||
Logo,
|
Logo,
|
||||||
draggable
|
draggable
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTab: 'library',
|
selectedTab: "library",
|
||||||
backend: backend,
|
backend: backend,
|
||||||
tracksChangeBuffer: null,
|
tracksChangeBuffer: null,
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
fetchInterval: null,
|
fetchInterval: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$(this.$el).find('.menu .item').tab()
|
$(this.$el)
|
||||||
|
.find(".menu .item")
|
||||||
|
.tab()
|
||||||
},
|
},
|
||||||
destroy () {
|
destroy() {
|
||||||
if (this.fetchInterval) {
|
if (this.fetchInterval) {
|
||||||
clearInterval(this.fetchInterval)
|
clearInterval(this.fetchInterval)
|
||||||
}
|
}
|
||||||
|
@ -197,82 +203,92 @@ export default {
|
||||||
queue: state => state.queue,
|
queue: state => state.queue,
|
||||||
url: state => state.route.path
|
url: state => state.route.path
|
||||||
}),
|
}),
|
||||||
labels () {
|
labels() {
|
||||||
let pendingRequests = this.$gettext('Pending import requests')
|
let mainMenu = this.$gettext("Main menu")
|
||||||
let pendingFollows = this.$gettext('Pending follow requests')
|
let selectTrack = this.$gettext("Play this track")
|
||||||
|
let pendingRequests = this.$gettext("Pending import requests")
|
||||||
|
let pendingFollows = this.$gettext("Pending follow requests")
|
||||||
return {
|
return {
|
||||||
pendingRequests,
|
pendingRequests,
|
||||||
pendingFollows
|
pendingFollows,
|
||||||
|
mainMenu,
|
||||||
|
selectTrack
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tracks: {
|
tracks: {
|
||||||
get () {
|
get() {
|
||||||
return this.$store.state.queue.tracks
|
return this.$store.state.queue.tracks
|
||||||
},
|
},
|
||||||
set (value) {
|
set(value) {
|
||||||
this.tracksChangeBuffer = value
|
this.tracksChangeBuffer = value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logoUrl () {
|
logoUrl() {
|
||||||
if (this.$store.state.auth.authenticated) {
|
if (this.$store.state.auth.authenticated) {
|
||||||
return 'library.index'
|
return "library.index"
|
||||||
} else {
|
} else {
|
||||||
return 'index'
|
return "index"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
cleanTrack: 'queue/cleanTrack'
|
cleanTrack: "queue/cleanTrack"
|
||||||
}),
|
}),
|
||||||
reorder: function (event) {
|
reorder: function(event) {
|
||||||
this.$store.commit('queue/reorder', {
|
this.$store.commit("queue/reorder", {
|
||||||
tracks: this.tracksChangeBuffer, oldIndex: event.oldIndex, newIndex: event.newIndex})
|
tracks: this.tracksChangeBuffer,
|
||||||
|
oldIndex: event.oldIndex,
|
||||||
|
newIndex: event.newIndex
|
||||||
|
})
|
||||||
},
|
},
|
||||||
scrollToCurrent () {
|
scrollToCurrent() {
|
||||||
let current = $(this.$el).find('[data-tab="queue"] .active')[0]
|
let current = $(this.$el).find('[data-tab="queue"] .active')[0]
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return
|
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
|
// Position container at the top line then scroll current into view
|
||||||
container.scrollTop = 0
|
container.scrollTop = 0
|
||||||
current.scrollIntoView(true)
|
current.scrollIntoView(true)
|
||||||
// Scroll back nothing if element is at bottom of container else do it
|
// Scroll back nothing if element is at bottom of container else do it
|
||||||
// for half the height of the containers display area
|
// 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
|
container.scrollTop = container.scrollTop - scrollBack
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
url: function () {
|
url: function() {
|
||||||
this.isCollapsed = true
|
this.isCollapsed = true
|
||||||
},
|
},
|
||||||
selectedTab: function (newValue) {
|
selectedTab: function(newValue) {
|
||||||
if (newValue === 'queue') {
|
if (newValue === "queue") {
|
||||||
this.scrollToCurrent()
|
this.scrollToCurrent()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'$store.state.queue.currentIndex': function () {
|
"$store.state.queue.currentIndex": function() {
|
||||||
if (this.selectedTab !== 'queue') {
|
if (this.selectedTab !== "queue") {
|
||||||
this.scrollToCurrent()
|
this.scrollToCurrent()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '../style/vendor/media';
|
@import "../style/vendor/media";
|
||||||
|
|
||||||
$sidebar-color: #3d3e3f;
|
$sidebar-color: #3d3e3f;
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background: $sidebar-color;
|
background: $sidebar-color;
|
||||||
@include media(">tablet") {
|
@include media(">tablet") {
|
||||||
display:flex;
|
display: flex;
|
||||||
flex-direction:column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
@include media(">desktop") {
|
@include media(">desktop") {
|
||||||
|
@ -284,7 +300,9 @@ $sidebar-color: #3d3e3f;
|
||||||
position: static !important;
|
position: static !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.menu-area, .player-wrapper, .tabs {
|
.menu-area,
|
||||||
|
.player-wrapper,
|
||||||
|
.tabs {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,7 +396,9 @@ $sidebar-color: #3d3e3f;
|
||||||
.ui.search {
|
.ui.search {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.collapse.button, .collapse.button:hover, .collapse.button:active {
|
.collapse.button,
|
||||||
|
.collapse.button:hover,
|
||||||
|
.collapse.button:active {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">
|
<div class="player">
|
||||||
<audio-track
|
<audio-track
|
||||||
ref="currentAudio"
|
ref="currentAudio"
|
||||||
|
@ -213,18 +213,18 @@
|
||||||
@keydown.s.prevent.exact="shuffle"
|
@keydown.s.prevent.exact="shuffle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState, mapGetters, mapActions} from 'vuex'
|
import { mapState, mapGetters, mapActions } from "vuex"
|
||||||
import GlobalEvents from '@/components/utils/global-events'
|
import GlobalEvents from "@/components/utils/global-events"
|
||||||
import ColorThief from '@/vendor/color-thief'
|
import ColorThief from "@/vendor/color-thief"
|
||||||
import {Howl} from 'howler'
|
import { Howl } from "howler"
|
||||||
|
|
||||||
import AudioTrack from '@/components/audio/Track'
|
import AudioTrack from "@/components/audio/Track"
|
||||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
|
||||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -233,8 +233,13 @@ export default {
|
||||||
GlobalEvents,
|
GlobalEvents,
|
||||||
AudioTrack
|
AudioTrack
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
|
let defaultAmbiantColors = [
|
||||||
|
[46, 46, 46],
|
||||||
|
[46, 46, 46],
|
||||||
|
[46, 46, 46],
|
||||||
|
[46, 46, 46]
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
isShuffling: false,
|
isShuffling: false,
|
||||||
sliderVolume: this.volume,
|
sliderVolume: this.volume,
|
||||||
|
@ -245,7 +250,7 @@ export default {
|
||||||
dummyAudio: null
|
dummyAudio: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
// we trigger the watcher explicitely it does not work otherwise
|
// we trigger the watcher explicitely it does not work otherwise
|
||||||
this.sliderVolume = this.volume
|
this.sliderVolume = this.volume
|
||||||
// this is needed to unlock audio playing under some browsers,
|
// this is needed to unlock audio playing under some browsers,
|
||||||
|
@ -254,57 +259,57 @@ export default {
|
||||||
this.dummyAudio = new Howl({
|
this.dummyAudio = new Howl({
|
||||||
preload: false,
|
preload: false,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
src: ['noop.webm', 'noop.mp3']
|
src: ["noop.webm", "noop.mp3"]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed() {
|
||||||
this.dummyAudio.unload()
|
this.dummyAudio.unload()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
togglePlay: 'player/togglePlay',
|
togglePlay: "player/togglePlay",
|
||||||
mute: 'player/mute',
|
mute: "player/mute",
|
||||||
unmute: 'player/unmute',
|
unmute: "player/unmute",
|
||||||
clean: 'queue/clean',
|
clean: "queue/clean",
|
||||||
updateProgress: 'player/updateProgress'
|
updateProgress: "player/updateProgress"
|
||||||
}),
|
}),
|
||||||
shuffle () {
|
shuffle() {
|
||||||
let disabled = this.queue.tracks.length === 0
|
let disabled = this.queue.tracks.length === 0
|
||||||
if (this.isShuffling || disabled) {
|
if (this.isShuffling || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let self = this
|
let self = this
|
||||||
let msg = this.$gettext('Queue shuffled!')
|
let msg = this.$gettext("Queue shuffled!")
|
||||||
this.isShuffling = true
|
this.isShuffling = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
self.$store.dispatch('queue/shuffle', () => {
|
self.$store.dispatch("queue/shuffle", () => {
|
||||||
self.isShuffling = false
|
self.isShuffling = false
|
||||||
self.$store.commit('ui/addMessage', {
|
self.$store.commit("ui/addMessage", {
|
||||||
content: msg,
|
content: msg,
|
||||||
date: new Date()
|
date: new Date()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
next () {
|
next() {
|
||||||
let self = this
|
let self = this
|
||||||
this.$store.dispatch('queue/next').then(() => {
|
this.$store.dispatch("queue/next").then(() => {
|
||||||
self.$emit('next')
|
self.$emit("next")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
previous () {
|
previous() {
|
||||||
let self = this
|
let self = this
|
||||||
this.$store.dispatch('queue/previous').then(() => {
|
this.$store.dispatch("queue/previous").then(() => {
|
||||||
self.$emit('previous')
|
self.$emit("previous")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
touchProgress (e) {
|
touchProgress(e) {
|
||||||
let time
|
let time
|
||||||
let target = this.$refs.progress
|
let target = this.$refs.progress
|
||||||
time = e.layerX / target.offsetWidth * this.duration
|
time = (e.layerX / target.offsetWidth) * this.duration
|
||||||
this.$refs.currentAudio.setCurrentTime(time)
|
this.$refs.currentAudio.setCurrentTime(time)
|
||||||
},
|
},
|
||||||
updateBackground () {
|
updateBackground() {
|
||||||
if (!this.currentTrack.album.cover) {
|
if (!this.currentTrack.album.cover) {
|
||||||
this.ambiantColors = this.defaultAmbiantColors
|
this.ambiantColors = this.defaultAmbiantColors
|
||||||
return
|
return
|
||||||
|
@ -312,9 +317,9 @@ export default {
|
||||||
let image = this.$refs.cover
|
let image = this.$refs.cover
|
||||||
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
|
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
|
||||||
},
|
},
|
||||||
handleError ({sound, error}) {
|
handleError({ sound, error }) {
|
||||||
this.$store.commit('player/isLoadingAudio', false)
|
this.$store.commit("player/isLoadingAudio", false)
|
||||||
this.$store.dispatch('player/trackErrored')
|
this.$store.dispatch("player/trackErrored")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -330,26 +335,34 @@ export default {
|
||||||
queue: state => state.queue
|
queue: state => state.queue
|
||||||
}),
|
}),
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentTrack: 'queue/currentTrack',
|
currentTrack: "queue/currentTrack",
|
||||||
hasNext: 'queue/hasNext',
|
hasNext: "queue/hasNext",
|
||||||
emptyQueue: 'queue/isEmpty',
|
emptyQueue: "queue/isEmpty",
|
||||||
durationFormatted: 'player/durationFormatted',
|
durationFormatted: "player/durationFormatted",
|
||||||
currentTimeFormatted: 'player/currentTimeFormatted',
|
currentTimeFormatted: "player/currentTimeFormatted",
|
||||||
progress: 'player/progress'
|
progress: "player/progress"
|
||||||
}),
|
}),
|
||||||
labels () {
|
labels() {
|
||||||
let previousTrack = this.$gettext('Previous track')
|
let audioPlayer = this.$gettext("Media player")
|
||||||
let play = this.$gettext('Play track')
|
let previousTrack = this.$gettext("Previous track")
|
||||||
let pause = this.$gettext('Pause track')
|
let play = this.$gettext("Play track")
|
||||||
let next = this.$gettext('Next track')
|
let pause = this.$gettext("Pause track")
|
||||||
let unmute = this.$gettext('Unmute')
|
let next = this.$gettext("Next track")
|
||||||
let mute = this.$gettext('Mute')
|
let unmute = this.$gettext("Unmute")
|
||||||
let loopingDisabled = this.$gettext('Looping disabled. Click to switch to single-track looping.')
|
let mute = this.$gettext("Mute")
|
||||||
let loopingSingle = this.$gettext('Looping on a single track. Click to switch to whole queue looping.')
|
let loopingDisabled = this.$gettext(
|
||||||
let loopingWhole = this.$gettext('Looping on whole queue. Click to disable looping.')
|
"Looping disabled. Click to switch to single-track looping."
|
||||||
let shuffle = this.$gettext('Shuffle your queue')
|
)
|
||||||
let clear = this.$gettext('Clear your queue')
|
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 {
|
return {
|
||||||
|
audioPlayer,
|
||||||
previousTrack,
|
previousTrack,
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
|
@ -363,29 +376,35 @@ export default {
|
||||||
clear
|
clear
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: function () {
|
style: function() {
|
||||||
let style = {
|
let style = {
|
||||||
'background': this.ambiantGradiant
|
background: this.ambiantGradiant
|
||||||
}
|
}
|
||||||
return style
|
return style
|
||||||
},
|
},
|
||||||
ambiantGradiant: function () {
|
ambiantGradiant: function() {
|
||||||
let indexConf = [
|
let indexConf = [
|
||||||
{orientation: 330, percent: 100, opacity: 0.7},
|
{ orientation: 330, percent: 100, opacity: 0.7 },
|
||||||
{orientation: 240, percent: 90, opacity: 0.7},
|
{ orientation: 240, percent: 90, opacity: 0.7 },
|
||||||
{orientation: 150, percent: 80, opacity: 0.7},
|
{ orientation: 150, percent: 80, opacity: 0.7 },
|
||||||
{orientation: 60, percent: 70, opacity: 0.7}
|
{ orientation: 60, percent: 70, opacity: 0.7 }
|
||||||
]
|
]
|
||||||
let gradients = this.ambiantColors.map((e, i) => {
|
let gradients = this.ambiantColors
|
||||||
let [r, g, b] = e
|
.map((e, i) => {
|
||||||
let conf = indexConf[i]
|
let [r, g, b] = e
|
||||||
return `linear-gradient(${conf.orientation}deg, rgba(${r}, ${g}, ${b}, ${conf.opacity}) 10%, rgba(255, 255, 255, 0) ${conf.percent}%)`
|
let conf = indexConf[i]
|
||||||
}).join(', ')
|
return `linear-gradient(${
|
||||||
|
conf.orientation
|
||||||
|
}deg, rgba(${r}, ${g}, ${b}, ${
|
||||||
|
conf.opacity
|
||||||
|
}) 10%, rgba(255, 255, 255, 0) ${conf.percent}%)`
|
||||||
|
})
|
||||||
|
.join(", ")
|
||||||
return gradients
|
return gradients
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentTrack (newValue, oldValue) {
|
currentTrack(newValue, oldValue) {
|
||||||
if (!this.isShuffling && newValue != oldValue) {
|
if (!this.isShuffling && newValue != oldValue) {
|
||||||
this.audioKey = String(new Date())
|
this.audioKey = String(new Date())
|
||||||
}
|
}
|
||||||
|
@ -393,11 +412,11 @@ export default {
|
||||||
this.ambiantColors = this.defaultAmbiantColors
|
this.ambiantColors = this.defaultAmbiantColors
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
volume (newValue) {
|
volume(newValue) {
|
||||||
this.sliderVolume = newValue
|
this.sliderVolume = newValue
|
||||||
},
|
},
|
||||||
sliderVolume (newValue) {
|
sliderVolume(newValue) {
|
||||||
this.$store.commit('player/volume', newValue)
|
this.$store.commit("player/volume", newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,7 +424,6 @@ export default {
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
.ui.progress {
|
.ui.progress {
|
||||||
margin: 0.5rem 0 1rem;
|
margin: 0.5rem 0 1rem;
|
||||||
}
|
}
|
||||||
|
@ -423,18 +441,21 @@ export default {
|
||||||
.ui.item {
|
.ui.item {
|
||||||
.meta {
|
.meta {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
line-height: 1.2
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.timer.total {
|
.timer.total {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.timer.start {
|
.timer.start {
|
||||||
cursor: pointer
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.track-area {
|
.track-area {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
.header, .meta, .artist, .album {
|
.header,
|
||||||
|
.meta,
|
||||||
|
.artist,
|
||||||
|
.album {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,57 +489,57 @@ export default {
|
||||||
left: 25%;
|
left: 25%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
input[type=range]:focus {
|
input[type="range"]:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
input[type=range]::-webkit-slider-runnable-track {
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
input[type=range]::-webkit-slider-thumb {
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
input[type=range]::-moz-range-track {
|
input[type="range"]::-moz-range-track {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: white;
|
background: white;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
input[type=range]::-moz-focus-outer {
|
input[type="range"]::-moz-focus-outer {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
input[type=range]::-moz-range-thumb {
|
input[type="range"]::-moz-range-thumb {
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
input[type=range]::-ms-track {
|
input[type="range"]::-ms-track {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
input[type=range]::-ms-fill-lower {
|
input[type="range"]::-ms-fill-lower {
|
||||||
background: white;
|
background: white;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
input[type=range]::-ms-fill-upper {
|
input[type="range"]::-ms-fill-upper {
|
||||||
background: white;
|
background: white;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
input[type=range]::-ms-thumb {
|
input[type="range"]::-ms-thumb {
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
input[type=range]:focus::-ms-fill-lower {
|
input[type="range"]:focus::-ms-fill-lower {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
input[type=range]:focus::-ms-fill-upper {
|
input[type="range"]:focus::-ms-fill-upper {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -545,14 +566,13 @@ export default {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes MOVE-BG {
|
@keyframes MOVE-BG {
|
||||||
from {
|
from {
|
||||||
transform: translateX(0px);
|
transform: translateX(0px);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
transform: translateX(46px);
|
transform: translateX(46px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicating.progress {
|
.indicating.progress {
|
||||||
|
@ -565,7 +585,7 @@ export default {
|
||||||
|
|
||||||
.ui.inverted.progress .buffer.bar {
|
.ui.inverted.progress .buffer.bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color:rgba(255, 255, 255, 0.15);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
.indicating.progress .bar {
|
.indicating.progress .bar {
|
||||||
left: -46px;
|
left: -46px;
|
||||||
|
@ -576,12 +596,12 @@ export default {
|
||||||
grey 1px,
|
grey 1px,
|
||||||
grey 10px,
|
grey 10px,
|
||||||
transparent 10px,
|
transparent 10px,
|
||||||
transparent 20px,
|
transparent 20px
|
||||||
) !important;
|
) !important;
|
||||||
|
|
||||||
animation-name: MOVE-BG;
|
animation-name: MOVE-BG;
|
||||||
animation-duration: 2s;
|
animation-duration: 2s;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2><translate>Log in to your Funkwhale account</translate></h2>
|
<h2><translate>Log in to your Funkwhale account</translate></h2>
|
||||||
<form class="ui form" @submit.prevent="submit()">
|
<form class="ui form" @submit.prevent="submit()">
|
||||||
|
@ -43,39 +43,39 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PasswordInput from '@/components/forms/PasswordInput'
|
import PasswordInput from "@/components/forms/PasswordInput"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
next: {type: String, default: '/'}
|
next: { type: String, default: "/" }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PasswordInput
|
PasswordInput
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
// We need to initialize the component with any
|
// We need to initialize the component with any
|
||||||
// properties that will be used in it
|
// properties that will be used in it
|
||||||
credentials: {
|
credentials: {
|
||||||
username: '',
|
username: "",
|
||||||
password: ''
|
password: ""
|
||||||
},
|
},
|
||||||
error: '',
|
error: "",
|
||||||
isLoading: false
|
isLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
this.$refs.username.focus()
|
this.$refs.username.focus()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let usernamePlaceholder = this.$gettext('Enter your username or email')
|
let usernamePlaceholder = this.$gettext("Enter your username or email")
|
||||||
let title = this.$gettext('Log In')
|
let title = this.$gettext("Log In")
|
||||||
return {
|
return {
|
||||||
usernamePlaceholder,
|
usernamePlaceholder,
|
||||||
title
|
title
|
||||||
|
@ -83,30 +83,31 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
submit() {
|
||||||
var self = this
|
var self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
this.error = ''
|
this.error = ""
|
||||||
var credentials = {
|
var credentials = {
|
||||||
username: this.credentials.username,
|
username: this.credentials.username,
|
||||||
password: this.credentials.password
|
password: this.credentials.password
|
||||||
}
|
}
|
||||||
this.$store.dispatch('auth/login', {
|
this.$store
|
||||||
credentials,
|
.dispatch("auth/login", {
|
||||||
next: '/library',
|
credentials,
|
||||||
onError: error => {
|
next: "/library",
|
||||||
if (error.response.status === 400) {
|
onError: error => {
|
||||||
self.error = 'invalid_credentials'
|
if (error.response.status === 400) {
|
||||||
} else {
|
self.error = "invalid_credentials"
|
||||||
self.error = 'unknown_error'
|
} else {
|
||||||
|
self.error = "unknown_error"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}).then(e => {
|
.then(e => {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Are you sure you want to log out?</translate>
|
<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>
|
<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>
|
<button class="ui button" @click="$store.dispatch('auth/logout')"><translate>Yes, log me out!</translate></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Log Out')
|
title: this.$gettext("Log Out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 v-if="isLoading" class="ui vertical segment">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,36 +25,37 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import { mapState } from "vuex"
|
||||||
|
|
||||||
const dateFormat = require('dateformat')
|
const dateFormat = require("dateformat")
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['username'],
|
props: ["username"],
|
||||||
created () {
|
created() {
|
||||||
this.$store.dispatch('auth/fetchProfile')
|
this.$store.dispatch("auth/fetchProfile")
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
...mapState({
|
...mapState({
|
||||||
profile: state => state.auth.profile
|
profile: state => state.auth.profile
|
||||||
}),
|
}),
|
||||||
labels () {
|
labels() {
|
||||||
let msg = this.$gettext('%{ username }\'s profile')
|
let msg = this.$gettext("%{ username }'s profile")
|
||||||
let usernameProfile = this.$gettextInterpolate(msg, {username: this.username})
|
let usernameProfile = this.$gettextInterpolate(msg, {
|
||||||
|
username: this.username
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
usernameProfile
|
usernameProfile
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
signupDate () {
|
signupDate() {
|
||||||
let d = new Date(this.profile.date_joined)
|
let d = new Date(this.profile.date_joined)
|
||||||
return dateFormat(d, 'longDate')
|
return dateFormat(d, "longDate")
|
||||||
},
|
},
|
||||||
isLoading () {
|
isLoading() {
|
||||||
return !this.profile
|
return !this.profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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 vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<section class="ui small text container">
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Account settings</translate>
|
<translate>Account settings</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -28,9 +28,9 @@
|
||||||
<translate>Update settings</translate>
|
<translate>Update settings</translate>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<div class="ui small text container">
|
<section class="ui small text container">
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Avatar</translate>
|
<translate>Avatar</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -61,9 +61,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<div class="ui small text container">
|
<section class="ui small text container">
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Change my password</translate>
|
<translate>Change my password</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -107,18 +107,18 @@
|
||||||
</form>
|
</form>
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<subsonic-token-form />
|
<subsonic-token-form />
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
import PasswordInput from '@/components/forms/PasswordInput'
|
import PasswordInput from "@/components/forms/PasswordInput"
|
||||||
import SubsonicTokenForm from '@/components/auth/SubsonicTokenForm'
|
import SubsonicTokenForm from "@/components/auth/SubsonicTokenForm"
|
||||||
import TranslationsMixin from '@/components/mixins/Translations'
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [TranslationsMixin],
|
mixins: [TranslationsMixin],
|
||||||
|
@ -126,14 +126,14 @@ export default {
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
SubsonicTokenForm
|
SubsonicTokenForm
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let d = {
|
let d = {
|
||||||
// We need to initialize the component with any
|
// We need to initialize the component with any
|
||||||
// properties that will be used in it
|
// properties that will be used in it
|
||||||
old_password: '',
|
old_password: "",
|
||||||
new_password: '',
|
new_password: "",
|
||||||
currentAvatar: this.$store.state.auth.profile.avatar,
|
currentAvatar: this.$store.state.auth.profile.avatar,
|
||||||
passwordError: '',
|
passwordError: "",
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoadingAvatar: false,
|
isLoadingAvatar: false,
|
||||||
avatarErrors: [],
|
avatarErrors: [],
|
||||||
|
@ -141,12 +141,12 @@ export default {
|
||||||
settings: {
|
settings: {
|
||||||
success: false,
|
success: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
order: ['privacy_level'],
|
order: ["privacy_level"],
|
||||||
fields: {
|
fields: {
|
||||||
'privacy_level': {
|
privacy_level: {
|
||||||
type: 'dropdown',
|
type: "dropdown",
|
||||||
initial: this.$store.state.auth.profile.privacy_level,
|
initial: this.$store.state.auth.profile.privacy_level,
|
||||||
choices: ['me', 'instance']
|
choices: ["me", "instance"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,108 +157,120 @@ export default {
|
||||||
})
|
})
|
||||||
return d
|
return d
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('select.dropdown').dropdown()
|
$("select.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submitSettings () {
|
submitSettings() {
|
||||||
this.settings.success = false
|
this.settings.success = false
|
||||||
this.settings.errors = []
|
this.settings.errors = []
|
||||||
let self = this
|
let self = this
|
||||||
let payload = this.settingsValues
|
let payload = this.settingsValues
|
||||||
let url = `users/users/${this.$store.state.auth.username}/`
|
let url = `users/users/${this.$store.state.auth.username}/`
|
||||||
return axios.patch(url, payload).then(response => {
|
return axios.patch(url, payload).then(
|
||||||
logger.default.info('Updated settings successfully')
|
response => {
|
||||||
self.settings.success = true
|
logger.default.info("Updated settings successfully")
|
||||||
return axios.get('users/users/me/').then((response) => {
|
self.settings.success = true
|
||||||
self.$store.dispatch('auth/updateProfile', response.data)
|
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
|
error => {
|
||||||
self.settings.errors = error.backendErrors
|
logger.default.error("Error while updating settings")
|
||||||
})
|
self.isLoading = false
|
||||||
|
self.settings.errors = error.backendErrors
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
submitAvatar () {
|
submitAvatar() {
|
||||||
this.isLoadingAvatar = true
|
this.isLoadingAvatar = true
|
||||||
this.avatarErrors = []
|
this.avatarErrors = []
|
||||||
let self = this
|
let self = this
|
||||||
this.avatar = this.$refs.avatar.files[0]
|
this.avatar = this.$refs.avatar.files[0]
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.append('avatar', this.avatar)
|
formData.append("avatar", this.avatar)
|
||||||
axios.patch(
|
axios
|
||||||
`users/users/${this.$store.state.auth.username}/`,
|
.patch(`users/users/${this.$store.state.auth.username}/`, formData, {
|
||||||
formData,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
"Content-Type": "multipart/form-data"
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
).then(response => {
|
.then(
|
||||||
this.isLoadingAvatar = false
|
response => {
|
||||||
self.currentAvatar = response.data.avatar
|
this.isLoadingAvatar = false
|
||||||
self.$store.commit('auth/avatar', self.currentAvatar)
|
self.currentAvatar = response.data.avatar
|
||||||
}, error => {
|
self.$store.commit("auth/avatar", self.currentAvatar)
|
||||||
self.isLoadingAvatar = false
|
},
|
||||||
self.avatarErrors = error.backendErrors
|
error => {
|
||||||
})
|
self.isLoadingAvatar = false
|
||||||
|
self.avatarErrors = error.backendErrors
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
removeAvatar () {
|
removeAvatar() {
|
||||||
this.isLoadingAvatar = true
|
this.isLoadingAvatar = true
|
||||||
let self = this
|
let self = this
|
||||||
this.avatar = null
|
this.avatar = null
|
||||||
axios.patch(
|
axios
|
||||||
`users/users/${this.$store.state.auth.username}/`,
|
.patch(`users/users/${this.$store.state.auth.username}/`, {
|
||||||
{avatar: null}
|
avatar: null
|
||||||
).then(response => {
|
})
|
||||||
this.isLoadingAvatar = false
|
.then(
|
||||||
self.currentAvatar = {}
|
response => {
|
||||||
self.$store.commit('auth/avatar', self.currentAvatar)
|
this.isLoadingAvatar = false
|
||||||
}, error => {
|
self.currentAvatar = {}
|
||||||
self.isLoadingAvatar = false
|
self.$store.commit("auth/avatar", self.currentAvatar)
|
||||||
self.avatarErrors = error.backendErrors
|
},
|
||||||
})
|
error => {
|
||||||
|
self.isLoadingAvatar = false
|
||||||
|
self.avatarErrors = error.backendErrors
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
submitPassword () {
|
submitPassword() {
|
||||||
var self = this
|
var self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
this.error = ''
|
this.error = ""
|
||||||
var credentials = {
|
var credentials = {
|
||||||
old_password: this.old_password,
|
old_password: this.old_password,
|
||||||
new_password1: this.new_password,
|
new_password1: this.new_password,
|
||||||
new_password2: this.new_password
|
new_password2: this.new_password
|
||||||
}
|
}
|
||||||
let url = 'auth/registration/change-password/'
|
let url = "auth/registration/change-password/"
|
||||||
return axios.post(url, credentials).then(response => {
|
return axios.post(url, credentials).then(
|
||||||
logger.default.info('Password successfully changed')
|
response => {
|
||||||
self.$router.push({
|
logger.default.info("Password successfully changed")
|
||||||
name: 'profile',
|
self.$router.push({
|
||||||
params: {
|
name: "profile",
|
||||||
username: self.$store.state.auth.username
|
params: {
|
||||||
}})
|
username: self.$store.state.auth.username
|
||||||
}, error => {
|
}
|
||||||
if (error.response.status === 400) {
|
})
|
||||||
self.passwordError = 'invalid_credentials'
|
},
|
||||||
} else {
|
error => {
|
||||||
self.passwordError = 'unknown_error'
|
if (error.response.status === 400) {
|
||||||
|
self.passwordError = "invalid_credentials"
|
||||||
|
} else {
|
||||||
|
self.passwordError = "unknown_error"
|
||||||
|
}
|
||||||
|
self.isLoading = false
|
||||||
}
|
}
|
||||||
self.isLoading = false
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Account Settings')
|
title: this.$gettext("Account Settings")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
orderedSettingsFields () {
|
orderedSettingsFields() {
|
||||||
let self = this
|
let self = this
|
||||||
return this.settings.order.map(id => {
|
return this.settings.order.map(id => {
|
||||||
return self.settings.fields[id]
|
return self.settings.fields[id]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
settingsValues () {
|
settingsValues() {
|
||||||
let self = this
|
let self = this
|
||||||
let s = {}
|
let s = {}
|
||||||
this.settings.order.forEach(setting => {
|
this.settings.order.forEach(setting => {
|
||||||
|
@ -268,7 +280,6 @@ export default {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2><translate>Create a funkwhale account</translate></h2>
|
<h2><translate>Create a funkwhale account</translate></h2>
|
||||||
<form
|
<form
|
||||||
|
@ -53,49 +53,51 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
|
|
||||||
import PasswordInput from '@/components/forms/PasswordInput'
|
import PasswordInput from "@/components/forms/PasswordInput"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
defaultInvitation: {type: String, required: false, default: null},
|
defaultInvitation: { type: String, required: false, default: null },
|
||||||
next: {type: String, default: '/'}
|
next: { type: String, default: "/" }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PasswordInput
|
PasswordInput
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: '',
|
username: "",
|
||||||
email: '',
|
email: "",
|
||||||
password: '',
|
password: "",
|
||||||
isLoadingInstanceSetting: true,
|
isLoadingInstanceSetting: true,
|
||||||
errors: [],
|
errors: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
invitation: this.defaultInvitation
|
invitation: this.defaultInvitation
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
let self = this
|
let self = this
|
||||||
this.$store.dispatch('instance/fetchSettings', {
|
this.$store.dispatch("instance/fetchSettings", {
|
||||||
callback: function () {
|
callback: function() {
|
||||||
self.isLoadingInstanceSetting = false
|
self.isLoadingInstanceSetting = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let title = this.$gettext('Sign Up')
|
let title = this.$gettext("Sign Up")
|
||||||
let placeholder = this.$gettext('Enter your invitation code (case insensitive)')
|
let placeholder = this.$gettext(
|
||||||
let usernamePlaceholder = this.$gettext('Enter your username')
|
"Enter your invitation code (case insensitive)"
|
||||||
let emailPlaceholder = this.$gettext('Enter your email')
|
)
|
||||||
|
let usernamePlaceholder = this.$gettext("Enter your username")
|
||||||
|
let emailPlaceholder = this.$gettext("Enter your email")
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
usernamePlaceholder,
|
usernamePlaceholder,
|
||||||
|
@ -105,7 +107,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
submit() {
|
||||||
var self = this
|
var self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
this.errors = []
|
this.errors = []
|
||||||
|
@ -116,17 +118,21 @@ export default {
|
||||||
email: this.email,
|
email: this.email,
|
||||||
invitation: this.invitation
|
invitation: this.invitation
|
||||||
}
|
}
|
||||||
return axios.post('auth/registration/', payload).then(response => {
|
return axios.post("auth/registration/", payload).then(
|
||||||
logger.default.info('Successfully created account')
|
response => {
|
||||||
self.$router.push({
|
logger.default.info("Successfully created account")
|
||||||
name: 'profile',
|
self.$router.push({
|
||||||
params: {
|
name: "profile",
|
||||||
username: this.username
|
params: {
|
||||||
}})
|
username: this.username
|
||||||
}, error => {
|
}
|
||||||
self.errors = error.backendErrors
|
})
|
||||||
self.isLoading = false
|
},
|
||||||
})
|
error => {
|
||||||
|
self.errors = error.backendErrors
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<div class="ui vertical center aligned stripe segment">
|
<section class="ui vertical center aligned stripe segment">
|
||||||
<div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
|
<div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
|
||||||
<div class="ui text loader">
|
<div class="ui text loader">
|
||||||
<translate>Loading your favorites...</translate>
|
<translate>Loading your favorites...</translate>
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
</translate>
|
</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<radio-button type="favorites"></radio-button>
|
<radio-button type="favorites"></radio-button>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div :class="['ui', {'loading': isLoading}, 'form']">
|
<div :class="['ui', {'loading': isLoading}, 'form']">
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -56,21 +56,21 @@
|
||||||
:total="results.count"
|
:total="results.count"
|
||||||
></pagination>
|
></pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import RadioButton from '@/components/radios/Button'
|
import RadioButton from "@/components/radios/Button"
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from "@/components/Pagination"
|
||||||
import OrderingMixin from '@/components/mixins/Ordering'
|
import OrderingMixin from "@/components/mixins/Ordering"
|
||||||
import PaginationMixin from '@/components/mixins/Pagination'
|
import PaginationMixin from "@/components/mixins/Pagination"
|
||||||
import TranslationsMixin from '@/components/mixins/Translations'
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
const FAVORITES_URL = 'tracks/'
|
const FAVORITES_URL = "tracks/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||||
|
@ -79,8 +79,10 @@ export default {
|
||||||
RadioButton,
|
RadioButton,
|
||||||
Pagination
|
Pagination
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
let defaultOrdering = this.getOrderingFromString(
|
||||||
|
this.defaultOrdering || "-creation_date"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
results: null,
|
results: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
@ -88,31 +90,31 @@ export default {
|
||||||
previousLink: null,
|
previousLink: null,
|
||||||
page: parseInt(this.defaultPage),
|
page: parseInt(this.defaultPage),
|
||||||
paginateBy: parseInt(this.defaultPaginateBy || 25),
|
paginateBy: parseInt(this.defaultPaginateBy || 25),
|
||||||
orderingDirection: defaultOrdering.direction || '+',
|
orderingDirection: defaultOrdering.direction || "+",
|
||||||
ordering: defaultOrdering.field,
|
ordering: defaultOrdering.field,
|
||||||
orderingOptions: [
|
orderingOptions: [
|
||||||
['creation_date', 'creation_date'],
|
["creation_date", "creation_date"],
|
||||||
['title', 'track_title'],
|
["title", "track_title"],
|
||||||
['album__title', 'album_title'],
|
["album__title", "album_title"],
|
||||||
['artist__name', 'artist_name']
|
["artist__name", "artist_name"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchFavorites(FAVORITES_URL)
|
this.fetchFavorites(FAVORITES_URL)
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('.ui.dropdown').dropdown()
|
$(".ui.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Your Favorites')
|
title: this.$gettext("Your Favorites")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateQueryString: function () {
|
updateQueryString: function() {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
query: {
|
query: {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
|
@ -121,42 +123,42 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchFavorites (url) {
|
fetchFavorites(url) {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let params = {
|
let params = {
|
||||||
favorites: 'true',
|
favorites: "true",
|
||||||
page: this.page,
|
page: this.page,
|
||||||
page_size: this.paginateBy,
|
page_size: this.paginateBy,
|
||||||
ordering: this.getOrderingAsString()
|
ordering: this.getOrderingAsString()
|
||||||
}
|
}
|
||||||
logger.default.time('Loading user favorites')
|
logger.default.time("Loading user favorites")
|
||||||
axios.get(url, {params: params}).then((response) => {
|
axios.get(url, { params: params }).then(response => {
|
||||||
self.results = response.data
|
self.results = response.data
|
||||||
self.nextLink = response.data.next
|
self.nextLink = response.data.next
|
||||||
self.previousLink = response.data.previous
|
self.previousLink = response.data.previous
|
||||||
self.results.results.forEach((track) => {
|
self.results.results.forEach(track => {
|
||||||
self.$store.commit('favorites/track', {id: track.id, value: true})
|
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
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page: function () {
|
page: function() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
},
|
},
|
||||||
paginateBy: function () {
|
paginateBy: function() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
},
|
},
|
||||||
orderingDirection: function () {
|
orderingDirection: function() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
},
|
},
|
||||||
ordering: function () {
|
ordering: function() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
<div v-if="isLoading" class="ui vertical segment" v-title="">
|
<div v-if="isLoading" class="ui vertical segment" v-title="">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="album">
|
<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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted sound yellow icon"></i>
|
<i class="circular inverted sound yellow icon"></i>
|
||||||
|
@ -38,86 +38,93 @@
|
||||||
<translate>View on MusicBrainz</translate>
|
<translate>View on MusicBrainz</translate>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Tracks</translate>
|
<translate>Tracks</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<track-table v-if="album" :artist="album.artist" :display-position="true" :tracks="album.tracks"></track-table>
|
<track-table v-if="album" :artist="album.artist" :display-position="true" :tracks="album.tracks"></track-table>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>User libraries</translate>
|
<translate>User libraries</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<library-widget :url="'albums/' + id + '/libraries/'">
|
<library-widget :url="'albums/' + id + '/libraries/'">
|
||||||
<translate slot="subtitle">This album is present in the following libraries:</translate>
|
<translate slot="subtitle">This album is present in the following libraries:</translate>
|
||||||
</library-widget>
|
</library-widget>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
import backend from '@/audio/backend'
|
import backend from "@/audio/backend"
|
||||||
import PlayButton from '@/components/audio/PlayButton'
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import LibraryWidget from '@/components/federation/LibraryWidget'
|
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||||
|
|
||||||
const FETCH_URL = 'albums/'
|
const FETCH_URL = "albums/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['id'],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackTable,
|
TrackTable,
|
||||||
LibraryWidget
|
LibraryWidget
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
album: null
|
album: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
fetchData() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let url = FETCH_URL + this.id + '/'
|
let url = FETCH_URL + this.id + "/"
|
||||||
logger.default.debug('Fetching album "' + 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.album = backend.Album.clean(response.data)
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Album')
|
title: this.$gettext("Album")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
wikipediaUrl () {
|
wikipediaUrl() {
|
||||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.album.title + ' ' + this.album.artist.name)
|
return (
|
||||||
|
"https://en.wikipedia.org/w/index.php?search=" +
|
||||||
|
encodeURI(this.album.title + " " + this.album.artist.name)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
musicbrainzUrl () {
|
musicbrainzUrl() {
|
||||||
if (this.album.mbid) {
|
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) {
|
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: {
|
watch: {
|
||||||
id () {
|
id() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,5 +133,4 @@ export default {
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.title">
|
<main v-title="labels.title">
|
||||||
<div v-if="isLoading" class="ui vertical segment">
|
<div v-if="isLoading" class="ui vertical segment">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="artist">
|
<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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted users violet icon"></i>
|
<i class="circular inverted users violet icon"></i>
|
||||||
|
@ -36,11 +36,11 @@
|
||||||
<translate>View on MusicBrainz</translate>
|
<translate>View on MusicBrainz</translate>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
<section v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</section>
|
||||||
<div v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
<section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Albums by this artist</translate>
|
<translate>Albums by this artist</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -49,38 +49,38 @@
|
||||||
<album-card :mode="'rich'" class="fluid" :album="album"></album-card>
|
<album-card :mode="'rich'" class="fluid" :album="album"></album-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div v-if="tracks.length > 0" class="ui vertical stripe segment">
|
<section v-if="tracks.length > 0" class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Tracks by this artist</translate>
|
<translate>Tracks by this artist</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<track-table :display-position="true" :tracks="tracks"></track-table>
|
<track-table :display-position="true" :tracks="tracks"></track-table>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>User libraries</translate>
|
<translate>User libraries</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<library-widget :url="'artists/' + id + '/libraries/'">
|
<library-widget :url="'artists/' + id + '/libraries/'">
|
||||||
<translate slot="subtitle">This artist is present in the following libraries:</translate>
|
<translate slot="subtitle">This artist is present in the following libraries:</translate>
|
||||||
</library-widget>
|
</library-widget>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
import backend from '@/audio/backend'
|
import backend from "@/audio/backend"
|
||||||
import AlbumCard from '@/components/audio/album/Card'
|
import AlbumCard from "@/components/audio/album/Card"
|
||||||
import RadioButton from '@/components/radios/Button'
|
import RadioButton from "@/components/radios/Button"
|
||||||
import PlayButton from '@/components/audio/PlayButton'
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import LibraryWidget from '@/components/federation/LibraryWidget'
|
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['id'],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
AlbumCard,
|
AlbumCard,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
|
@ -88,7 +88,7 @@ export default {
|
||||||
TrackTable,
|
TrackTable,
|
||||||
LibraryWidget
|
LibraryWidget
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isLoadingAlbums: true,
|
isLoadingAlbums: true,
|
||||||
|
@ -99,54 +99,63 @@ export default {
|
||||||
tracks: []
|
tracks: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
fetchData() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
logger.default.debug('Fetching artist "' + this.id + '"')
|
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.tracks = response.data.results
|
||||||
self.totalTracks = response.data.count
|
self.totalTracks = response.data.count
|
||||||
})
|
})
|
||||||
axios.get('artists/' + this.id + '/').then((response) => {
|
axios.get("artists/" + this.id + "/").then(response => {
|
||||||
self.artist = response.data
|
self.artist = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.isLoadingAlbums = true
|
self.isLoadingAlbums = true
|
||||||
axios.get('albums/', {params: {artist: self.id, ordering: '-release_date'}}).then((response) => {
|
axios
|
||||||
self.totalAlbums = response.data.count
|
.get("albums/", {
|
||||||
let parsed = JSON.parse(JSON.stringify(response.data.results))
|
params: { artist: self.id, ordering: "-release_date" }
|
||||||
self.albums = parsed.map((album) => {
|
|
||||||
return backend.Album.clean(album)
|
|
||||||
})
|
})
|
||||||
|
.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: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Artist')
|
title: this.$gettext("Artist")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isPlayable () {
|
isPlayable() {
|
||||||
return this.artist.albums.filter((a) => {
|
return (
|
||||||
return a.is_playable
|
this.artist.albums.filter(a => {
|
||||||
}).length > 0
|
return a.is_playable
|
||||||
|
}).length > 0
|
||||||
|
)
|
||||||
},
|
},
|
||||||
wikipediaUrl () {
|
wikipediaUrl() {
|
||||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.artist.name)
|
return (
|
||||||
|
"https://en.wikipedia.org/w/index.php?search=" +
|
||||||
|
encodeURI(this.artist.name)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
musicbrainzUrl () {
|
musicbrainzUrl() {
|
||||||
if (this.artist.mbid) {
|
if (this.artist.mbid) {
|
||||||
return 'https://musicbrainz.org/artist/' + this.artist.mbid
|
return "https://musicbrainz.org/artist/" + this.artist.mbid
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allTracks () {
|
allTracks() {
|
||||||
let tracks = []
|
let tracks = []
|
||||||
this.albums.forEach(album => {
|
this.albums.forEach(album => {
|
||||||
album.tracks.forEach(track => {
|
album.tracks.forEach(track => {
|
||||||
|
@ -155,22 +164,28 @@ export default {
|
||||||
})
|
})
|
||||||
return tracks
|
return tracks
|
||||||
},
|
},
|
||||||
cover () {
|
cover() {
|
||||||
return this.artist.albums.filter(album => {
|
return this.artist.albums
|
||||||
return album.cover
|
.filter(album => {
|
||||||
}).map(album => {
|
return album.cover
|
||||||
return album.cover
|
})
|
||||||
})[0]
|
.map(album => {
|
||||||
|
return album.cover
|
||||||
|
})[0]
|
||||||
},
|
},
|
||||||
headerStyle () {
|
headerStyle() {
|
||||||
if (!this.cover || !this.cover.original) {
|
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: {
|
watch: {
|
||||||
id () {
|
id() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.title">
|
<main v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Browsing artists</translate>
|
<translate>Browsing artists</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -64,60 +64,59 @@
|
||||||
:total="result.count"
|
:total="result.count"
|
||||||
></pagination>
|
></pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
|
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
|
|
||||||
import OrderingMixin from '@/components/mixins/Ordering'
|
import OrderingMixin from "@/components/mixins/Ordering"
|
||||||
import PaginationMixin from '@/components/mixins/Pagination'
|
import PaginationMixin from "@/components/mixins/Pagination"
|
||||||
import TranslationsMixin from '@/components/mixins/Translations'
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
import ArtistCard from '@/components/audio/artist/Card'
|
import ArtistCard from "@/components/audio/artist/Card"
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from "@/components/Pagination"
|
||||||
|
|
||||||
const FETCH_URL = 'artists/'
|
const FETCH_URL = "artists/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||||
props: {
|
props: {
|
||||||
defaultQuery: {type: String, required: false, default: ''}
|
defaultQuery: { type: String, required: false, default: "" }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ArtistCard,
|
ArtistCard,
|
||||||
Pagination
|
Pagination
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
let defaultOrdering = this.getOrderingFromString(
|
||||||
|
this.defaultOrdering || "-creation_date"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
result: null,
|
result: null,
|
||||||
page: parseInt(this.defaultPage),
|
page: parseInt(this.defaultPage),
|
||||||
query: this.defaultQuery,
|
query: this.defaultQuery,
|
||||||
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
||||||
orderingDirection: defaultOrdering.direction || '+',
|
orderingDirection: defaultOrdering.direction || "+",
|
||||||
ordering: defaultOrdering.field,
|
ordering: defaultOrdering.field,
|
||||||
orderingOptions: [
|
orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
|
||||||
['creation_date', 'creation_date'],
|
|
||||||
['name', 'name']
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('.ui.dropdown').dropdown()
|
$(".ui.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let searchPlaceholder = this.$gettext('Enter an artist name...')
|
let searchPlaceholder = this.$gettext("Enter an artist name...")
|
||||||
let title = this.$gettext('Artists')
|
let title = this.$gettext("Artists")
|
||||||
return {
|
return {
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
title
|
title
|
||||||
|
@ -125,7 +124,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateQueryString: _.debounce(function () {
|
updateQueryString: _.debounce(function() {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
query: {
|
query: {
|
||||||
query: this.query,
|
query: this.query,
|
||||||
|
@ -135,7 +134,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 500),
|
}, 500),
|
||||||
fetchData: _.debounce(function () {
|
fetchData: _.debounce(function() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let url = FETCH_URL
|
let url = FETCH_URL
|
||||||
|
@ -144,36 +143,36 @@ export default {
|
||||||
page_size: this.paginateBy,
|
page_size: this.paginateBy,
|
||||||
name__icontains: this.query,
|
name__icontains: this.query,
|
||||||
ordering: this.getOrderingAsString(),
|
ordering: this.getOrderingAsString(),
|
||||||
playable: 'true'
|
playable: "true"
|
||||||
}
|
}
|
||||||
logger.default.debug('Fetching artists')
|
logger.default.debug("Fetching artists")
|
||||||
axios.get(url, {params: params}).then((response) => {
|
axios.get(url, { params: params }).then(response => {
|
||||||
self.result = response.data
|
self.result = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}, 500),
|
}, 500),
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page () {
|
page() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
paginateBy () {
|
paginateBy() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
ordering () {
|
ordering() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
orderingDirection () {
|
orderingDirection() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
query () {
|
query() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.title">
|
<main v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui stackable three column grid">
|
<div class="ui stackable three column grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<track-widget :url="'history/listenings/'" :filters="{scope: 'user', ordering: '-creation_date'}">
|
<track-widget :url="'history/listenings/'" :filters="{scope: 'user', ordering: '-creation_date'}">
|
||||||
|
@ -26,23 +26,23 @@
|
||||||
</album-widget>
|
</album-widget>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import Search from '@/components/audio/Search'
|
import Search from "@/components/audio/Search"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
import ArtistCard from '@/components/audio/artist/Card'
|
import ArtistCard from "@/components/audio/artist/Card"
|
||||||
import TrackWidget from '@/components/audio/track/Widget'
|
import TrackWidget from "@/components/audio/track/Widget"
|
||||||
import AlbumWidget from '@/components/audio/album/Widget'
|
import AlbumWidget from "@/components/audio/album/Widget"
|
||||||
import PlaylistWidget from '@/components/playlists/Widget'
|
import PlaylistWidget from "@/components/playlists/Widget"
|
||||||
|
|
||||||
const ARTISTS_URL = 'artists/'
|
const ARTISTS_URL = "artists/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'library',
|
name: "library",
|
||||||
components: {
|
components: {
|
||||||
Search,
|
Search,
|
||||||
ArtistCard,
|
ArtistCard,
|
||||||
|
@ -50,35 +50,35 @@ export default {
|
||||||
AlbumWidget,
|
AlbumWidget,
|
||||||
PlaylistWidget
|
PlaylistWidget
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
artists: [],
|
artists: [],
|
||||||
isLoadingArtists: false
|
isLoadingArtists: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchArtists()
|
this.fetchArtists()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Home')
|
title: this.$gettext("Home")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchArtists () {
|
fetchArtists() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoadingArtists = true
|
this.isLoadingArtists = true
|
||||||
let params = {
|
let params = {
|
||||||
ordering: '-creation_date',
|
ordering: "-creation_date",
|
||||||
playable: true
|
playable: true
|
||||||
}
|
}
|
||||||
let url = ARTISTS_URL
|
let url = ARTISTS_URL
|
||||||
logger.default.time('Loading latest artists')
|
logger.default.time("Loading latest artists")
|
||||||
axios.get(url, {params: params}).then((response) => {
|
axios.get(url, { params: params }).then(response => {
|
||||||
self.artists = response.data.results
|
self.artists = response.data.results
|
||||||
logger.default.timeEnd('Loading latest artists')
|
logger.default.timeEnd("Loading latest artists")
|
||||||
self.isLoadingArtists = false
|
self.isLoadingArtists = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main library pusher">
|
<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>
|
<router-link class="ui item" to="/library" exact>
|
||||||
<translate>Browse</translate>
|
<translate>Browse</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<router-link class="ui item" to="/library/playlists" exact>
|
<router-link class="ui item" to="/library/playlists" exact>
|
||||||
<translate>Playlists</translate>
|
<translate>Playlists</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</nav>
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,8 +21,16 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
showImports () {
|
showImports() {
|
||||||
return this.$store.state.auth.availablePermissions['upload'] || this.$store.state.auth.availablePermissions['library']
|
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 -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../style/vendor/media';
|
@import "../../style/vendor/media";
|
||||||
|
|
||||||
.library {
|
.library {
|
||||||
.ui.segment.head {
|
.ui.segment.head {
|
||||||
|
@ -46,18 +54,16 @@ export default {
|
||||||
}
|
}
|
||||||
&.with-background {
|
&.with-background {
|
||||||
.header {
|
.header {
|
||||||
&, .sub {
|
&,
|
||||||
|
.sub {
|
||||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
|
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.segment-content {
|
.segment-content {
|
||||||
background-color: rgba(0, 0, 0, 0.5)
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.title">
|
<main v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Browsing radios</translate>
|
<translate>Browsing radios</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -86,60 +86,59 @@
|
||||||
:total="result.count"
|
:total="result.count"
|
||||||
></pagination>
|
></pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
|
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
|
|
||||||
import OrderingMixin from '@/components/mixins/Ordering'
|
import OrderingMixin from "@/components/mixins/Ordering"
|
||||||
import PaginationMixin from '@/components/mixins/Pagination'
|
import PaginationMixin from "@/components/mixins/Pagination"
|
||||||
import TranslationsMixin from '@/components/mixins/Translations'
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
import RadioCard from '@/components/radios/Card'
|
import RadioCard from "@/components/radios/Card"
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from "@/components/Pagination"
|
||||||
|
|
||||||
const FETCH_URL = 'radios/radios/'
|
const FETCH_URL = "radios/radios/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||||
props: {
|
props: {
|
||||||
defaultQuery: {type: String, required: false, default: ''}
|
defaultQuery: { type: String, required: false, default: "" }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
RadioCard,
|
RadioCard,
|
||||||
Pagination
|
Pagination
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
let defaultOrdering = this.getOrderingFromString(
|
||||||
|
this.defaultOrdering || "-creation_date"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
result: null,
|
result: null,
|
||||||
page: parseInt(this.defaultPage),
|
page: parseInt(this.defaultPage),
|
||||||
query: this.defaultQuery,
|
query: this.defaultQuery,
|
||||||
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
||||||
orderingDirection: defaultOrdering.direction || '+',
|
orderingDirection: defaultOrdering.direction || "+",
|
||||||
ordering: defaultOrdering.field,
|
ordering: defaultOrdering.field,
|
||||||
orderingOptions: [
|
orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
|
||||||
['creation_date', 'creation_date'],
|
|
||||||
['name', 'name']
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('.ui.dropdown').dropdown()
|
$(".ui.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let searchPlaceholder = this.$gettext('Enter a radio name...')
|
let searchPlaceholder = this.$gettext("Enter a radio name...")
|
||||||
let title = this.$gettext('Radios')
|
let title = this.$gettext("Radios")
|
||||||
return {
|
return {
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
title
|
title
|
||||||
|
@ -147,7 +146,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateQueryString: _.debounce(function () {
|
updateQueryString: _.debounce(function() {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
query: {
|
query: {
|
||||||
query: this.query,
|
query: this.query,
|
||||||
|
@ -157,7 +156,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 500),
|
}, 500),
|
||||||
fetchData: _.debounce(function () {
|
fetchData: _.debounce(function() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let url = FETCH_URL
|
let url = FETCH_URL
|
||||||
|
@ -167,34 +166,34 @@ export default {
|
||||||
name__icontains: this.query,
|
name__icontains: this.query,
|
||||||
ordering: this.getOrderingAsString()
|
ordering: this.getOrderingAsString()
|
||||||
}
|
}
|
||||||
logger.default.debug('Fetching radios')
|
logger.default.debug("Fetching radios")
|
||||||
axios.get(url, {params: params}).then((response) => {
|
axios.get(url, { params: params }).then(response => {
|
||||||
self.result = response.data
|
self.result = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}, 500),
|
}, 500),
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page () {
|
page() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
paginateBy () {
|
paginateBy() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
ordering () {
|
ordering() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
orderingDirection () {
|
orderingDirection() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
query () {
|
query() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
<div v-if="isLoadingTrack" class="ui vertical segment" v-title="labels.title">
|
<div v-if="isLoadingTrack" class="ui vertical segment" v-title="labels.title">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="track">
|
<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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted music orange icon"></i>
|
<i class="circular inverted music orange icon"></i>
|
||||||
|
@ -49,8 +49,8 @@
|
||||||
<translate>Download</translate>
|
<translate>Download</translate>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe center aligned segment" v-if="upload">
|
<section class="ui vertical stripe center aligned segment" v-if="upload">
|
||||||
<h2 class="ui header"><translate>Track information</translate></h2>
|
<h2 class="ui header"><translate>Track information</translate></h2>
|
||||||
<table class="ui very basic collapsing celled center aligned table">
|
<table class="ui very basic collapsing celled center aligned table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -100,8 +100,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe center aligned segment">
|
<section class="ui vertical stripe center aligned segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Lyrics</translate>
|
<translate>Lyrics</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -117,41 +117,40 @@
|
||||||
<translate>Search on lyrics.wikia.com</translate>
|
<translate>Search on lyrics.wikia.com</translate>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2>
|
<h2>
|
||||||
<translate>User libraries</translate>
|
<translate>User libraries</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<library-widget :url="'tracks/' + id + '/libraries/'">
|
<library-widget :url="'tracks/' + id + '/libraries/'">
|
||||||
<translate slot="subtitle">This track is present in the following libraries:</translate>
|
<translate slot="subtitle">This track is present in the following libraries:</translate>
|
||||||
</library-widget>
|
</library-widget>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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'
|
const FETCH_URL = "tracks/"
|
||||||
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/'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['id'],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackPlaylistIcon,
|
TrackPlaylistIcon,
|
||||||
TrackFavoriteIcon,
|
TrackFavoriteIcon,
|
||||||
LibraryWidget
|
LibraryWidget
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
time,
|
time,
|
||||||
isLoadingTrack: true,
|
isLoadingTrack: true,
|
||||||
|
@ -160,78 +159,94 @@ export default {
|
||||||
lyrics: null
|
lyrics: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
this.fetchLyrics()
|
this.fetchLyrics()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
fetchData() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoadingTrack = true
|
this.isLoadingTrack = true
|
||||||
let url = FETCH_URL + this.id + '/'
|
let url = FETCH_URL + this.id + "/"
|
||||||
logger.default.debug('Fetching track "' + this.id + '"')
|
logger.default.debug('Fetching track "' + this.id + '"')
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(response => {
|
||||||
self.track = response.data
|
self.track = response.data
|
||||||
self.isLoadingTrack = false
|
self.isLoadingTrack = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchLyrics () {
|
fetchLyrics() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoadingLyrics = true
|
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 + '"')
|
logger.default.debug('Fetching lyrics for track "' + this.id + '"')
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(
|
||||||
self.lyrics = response.data
|
response => {
|
||||||
self.isLoadingLyrics = false
|
self.lyrics = response.data
|
||||||
}, (response) => {
|
self.isLoadingLyrics = false
|
||||||
console.error('No lyrics available')
|
},
|
||||||
self.isLoadingLyrics = false
|
response => {
|
||||||
})
|
console.error("No lyrics available")
|
||||||
|
self.isLoadingLyrics = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Track')
|
title: this.$gettext("Track")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upload () {
|
upload() {
|
||||||
if (this.track.uploads) {
|
if (this.track.uploads) {
|
||||||
return this.track.uploads[0]
|
return this.track.uploads[0]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
wikipediaUrl () {
|
wikipediaUrl() {
|
||||||
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.track.title + ' ' + this.track.artist.name)
|
return (
|
||||||
|
"https://en.wikipedia.org/w/index.php?search=" +
|
||||||
|
encodeURI(this.track.title + " " + this.track.artist.name)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
musicbrainzUrl () {
|
musicbrainzUrl() {
|
||||||
if (this.track.mbid) {
|
if (this.track.mbid) {
|
||||||
return 'https://musicbrainz.org/recording/' + this.track.mbid
|
return "https://musicbrainz.org/recording/" + this.track.mbid
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
downloadUrl () {
|
downloadUrl() {
|
||||||
let u = this.$store.getters['instance/absoluteUrl'](this.upload.listen_url)
|
let u = this.$store.getters["instance/absoluteUrl"](
|
||||||
|
this.upload.listen_url
|
||||||
|
)
|
||||||
if (this.$store.state.auth.authenticated) {
|
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
|
return u
|
||||||
},
|
},
|
||||||
lyricsSearchUrl () {
|
lyricsSearchUrl() {
|
||||||
let base = 'http://lyrics.wikia.com/wiki/Special:Search?query='
|
let base = "http://lyrics.wikia.com/wiki/Special:Search?query="
|
||||||
let query = this.track.artist.name + ' ' + this.track.title
|
let query = this.track.artist.name + " " + this.track.title
|
||||||
return base + encodeURI(query)
|
return base + encodeURI(query)
|
||||||
},
|
},
|
||||||
cover () {
|
cover() {
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
headerStyle () {
|
headerStyle() {
|
||||||
if (!this.cover) {
|
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: {
|
watch: {
|
||||||
id () {
|
id() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ui vertical stripe segment" v-title="labels.title">
|
<div class="ui vertical stripe segment" v-title="labels.title">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<section>
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate>Builder</translate>
|
<translate>Builder</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -87,28 +87,28 @@
|
||||||
</h3>
|
</h3>
|
||||||
<track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample"></track-table>
|
<track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample"></track-table>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
import BuilderFilter from './Filter'
|
import BuilderFilter from "./Filter"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import RadioButton from '@/components/radios/Button'
|
import RadioButton from "@/components/radios/Button"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
id: {required: false}
|
id: { required: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BuilderFilter,
|
BuilderFilter,
|
||||||
TrackTable,
|
TrackTable,
|
||||||
RadioButton
|
RadioButton
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -116,12 +116,12 @@ export default {
|
||||||
currentFilterType: null,
|
currentFilterType: null,
|
||||||
filters: [],
|
filters: [],
|
||||||
checkResult: null,
|
checkResult: null,
|
||||||
radioName: '',
|
radioName: "",
|
||||||
radioDesc: '',
|
radioDesc: "",
|
||||||
isPublic: true
|
isPublic: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function() {
|
||||||
let self = this
|
let self = this
|
||||||
this.fetchFilters().then(() => {
|
this.fetchFilters().then(() => {
|
||||||
if (self.id) {
|
if (self.id) {
|
||||||
|
@ -129,18 +129,18 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('.ui.dropdown').dropdown()
|
$(".ui.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchFilters: function () {
|
fetchFilters: function() {
|
||||||
let self = this
|
let self = this
|
||||||
let url = 'radios/radios/filters/'
|
let url = "radios/radios/filters/"
|
||||||
return axios.get(url).then((response) => {
|
return axios.get(url).then(response => {
|
||||||
self.availableFilters = response.data
|
self.availableFilters = response.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
add () {
|
add() {
|
||||||
this.filters.push({
|
this.filters.push({
|
||||||
config: {},
|
config: {},
|
||||||
filter: this.currentFilter,
|
filter: this.currentFilter,
|
||||||
|
@ -148,23 +148,25 @@ export default {
|
||||||
})
|
})
|
||||||
this.fetchCandidates()
|
this.fetchCandidates()
|
||||||
},
|
},
|
||||||
updateConfig (index, field, value) {
|
updateConfig(index, field, value) {
|
||||||
this.filters[index].config[field] = value
|
this.filters[index].config[field] = value
|
||||||
this.fetchCandidates()
|
this.fetchCandidates()
|
||||||
},
|
},
|
||||||
deleteFilter (index) {
|
deleteFilter(index) {
|
||||||
this.filters.splice(index, 1)
|
this.filters.splice(index, 1)
|
||||||
this.fetchCandidates()
|
this.fetchCandidates()
|
||||||
},
|
},
|
||||||
fetch: function () {
|
fetch: function() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
let url = 'radios/radios/' + this.id + '/'
|
let url = "radios/radios/" + this.id + "/"
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(response => {
|
||||||
self.filters = response.data.config.map(f => {
|
self.filters = response.data.config.map(f => {
|
||||||
return {
|
return {
|
||||||
config: f,
|
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()
|
hash: +new Date()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -174,24 +176,22 @@ export default {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchCandidates: function () {
|
fetchCandidates: function() {
|
||||||
let self = this
|
let self = this
|
||||||
let url = 'radios/radios/validate/'
|
let url = "radios/radios/validate/"
|
||||||
let final = this.filters.map(f => {
|
let final = this.filters.map(f => {
|
||||||
let c = _.clone(f.config)
|
let c = _.clone(f.config)
|
||||||
c.type = f.filter.type
|
c.type = f.filter.type
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
final = {
|
final = {
|
||||||
'filters': [
|
filters: [{ type: "group", filters: final }]
|
||||||
{'type': 'group', filters: final}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
axios.post(url, final).then((response) => {
|
axios.post(url, final).then(response => {
|
||||||
self.checkResult = response.data.filters[0]
|
self.checkResult = response.data.filters[0]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save: function () {
|
save: function() {
|
||||||
let self = this
|
let self = this
|
||||||
self.success = false
|
self.success = false
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
|
@ -202,24 +202,24 @@ export default {
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
final = {
|
final = {
|
||||||
'name': this.radioName,
|
name: this.radioName,
|
||||||
'description': this.radioDesc,
|
description: this.radioDesc,
|
||||||
'is_public': this.isPublic,
|
is_public: this.isPublic,
|
||||||
'config': final
|
config: final
|
||||||
}
|
}
|
||||||
if (this.id) {
|
if (this.id) {
|
||||||
let url = 'radios/radios/' + this.id + '/'
|
let url = "radios/radios/" + this.id + "/"
|
||||||
axios.put(url, final).then((response) => {
|
axios.put(url, final).then(response => {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.success = true
|
self.success = true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let url = 'radios/radios/'
|
let url = "radios/radios/"
|
||||||
axios.post(url, final).then((response) => {
|
axios.post(url, final).then(response => {
|
||||||
self.success = true
|
self.success = true
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.$router.push({
|
self.$router.push({
|
||||||
name: 'library.radios.detail',
|
name: "library.radios.detail",
|
||||||
params: {
|
params: {
|
||||||
id: response.data.id
|
id: response.data.id
|
||||||
}
|
}
|
||||||
|
@ -229,30 +229,28 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let title = this.$gettext('Radio Builder')
|
let title = this.$gettext("Radio Builder")
|
||||||
let placeholder = {
|
let placeholder = {
|
||||||
'name': this.$gettext('My awesome radio'),
|
name: this.$gettext("My awesome radio"),
|
||||||
'description': this.$gettext('My awesome description')
|
description: this.$gettext("My awesome description")
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
placeholder
|
placeholder
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canSave: function () {
|
canSave: function() {
|
||||||
return (
|
return this.radioName.length > 0 && this.checkErrors.length === 0
|
||||||
this.radioName.length > 0 && this.checkErrors.length === 0
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
checkErrors: function () {
|
checkErrors: function() {
|
||||||
if (!this.checkResult) {
|
if (!this.checkResult) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let errors = this.checkResult.errors
|
let errors = this.checkResult.errors
|
||||||
return errors
|
return errors
|
||||||
},
|
},
|
||||||
currentFilter: function () {
|
currentFilter: function() {
|
||||||
let self = this
|
let self = this
|
||||||
return this.availableFilters.filter(e => {
|
return this.availableFilters.filter(e => {
|
||||||
return e.type === self.currentFilterType
|
return e.type === self.currentFilterType
|
||||||
|
@ -261,7 +259,7 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
filters: {
|
filters: {
|
||||||
handler: function () {
|
handler: function() {
|
||||||
this.fetchCandidates()
|
this.fetchCandidates()
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="data.id">
|
<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>
|
<a :href="getMusicbrainzUrl('artist', data.id)" target="_blank" :title="labels.musicbrainz">{{ data.name }}</a>
|
||||||
</div>
|
</header>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<table class="ui very basic fixed single line compact table">
|
<table class="ui very basic fixed single line compact table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -32,29 +32,29 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue'
|
import Vue from "vue"
|
||||||
import CardMixin from './CardMixin'
|
import CardMixin from "./CardMixin"
|
||||||
import time from '@/utils/time'
|
import time from "@/utils/time"
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
mixins: [CardMixin],
|
mixins: [CardMixin],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
time
|
time
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
musicbrainz: this.$gettext('View on MusicBrainz')
|
musicbrainz: this.$gettext("View on MusicBrainz")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
type () {
|
type() {
|
||||||
return 'artist'
|
return "artist"
|
||||||
},
|
},
|
||||||
releasesGroups () {
|
releasesGroups() {
|
||||||
return this.data['release-group-list'].filter(r => {
|
return this.data["release-group-list"].filter(r => {
|
||||||
return r.type === 'Album'
|
return r.type === "Album"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,6 @@ export default Vue.extend({
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.ui.card {
|
.ui.card {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main class="main pusher" v-title="labels.title">
|
||||||
<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 v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
|
||||||
<div class="ui text loader"><translate>Loading notifications...</translate></div>
|
<div class="ui text loader"><translate>Loading notifications...</translate></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,19 +27,19 @@
|
||||||
<translate>We don't have any notification to display!</translate>
|
<translate>We don't have any notification to display!</translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import { mapState } from "vuex"
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import logger from '@/logging'
|
import logger from "@/logging"
|
||||||
|
|
||||||
import NotificationRow from '@/components/notifications/NotificationRow'
|
import NotificationRow from "@/components/notifications/NotificationRow"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
notifications: null,
|
notifications: null,
|
||||||
|
@ -51,64 +51,63 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
NotificationRow
|
NotificationRow
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetch(this.filters)
|
this.fetch(this.filters)
|
||||||
this.$store.commit('ui/addWebsocketEventHandler', {
|
this.$store.commit("ui/addWebsocketEventHandler", {
|
||||||
eventName: 'inbox.item_added',
|
eventName: "inbox.item_added",
|
||||||
id: 'notificationPage',
|
id: "notificationPage",
|
||||||
handler: this.handleNewNotification
|
handler: this.handleNewNotification
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed() {
|
||||||
this.$store.commit('ui/removeWebsocketEventHandler', {
|
this.$store.commit("ui/removeWebsocketEventHandler", {
|
||||||
eventName: 'inbox.item_added',
|
eventName: "inbox.item_added",
|
||||||
id: 'notificationPage',
|
id: "notificationPage"
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
events: state => state.instance.events
|
events: state => state.instance.events
|
||||||
}),
|
}),
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Notifications'),
|
title: this.$gettext("Notifications")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleNewNotification (event) {
|
handleNewNotification(event) {
|
||||||
this.notifications.results.unshift(event.item)
|
this.notifications.results.unshift(event.item)
|
||||||
},
|
},
|
||||||
fetch (params) {
|
fetch(params) {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let self = this
|
let self = this
|
||||||
axios.get('federation/inbox/', {params: params}).then((response) => {
|
axios.get("federation/inbox/", { params: params }).then(response => {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.notifications = response.data
|
self.notifications = response.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
markAllAsRead () {
|
markAllAsRead() {
|
||||||
let self = this
|
let self = this
|
||||||
let before = this.notifications.results[0].id
|
let before = this.notifications.results[0].id
|
||||||
let payload = {
|
let payload = {
|
||||||
action: 'read',
|
action: "read",
|
||||||
objects: 'all',
|
objects: "all",
|
||||||
filters: {
|
filters: {
|
||||||
is_read: false,
|
is_read: false,
|
||||||
before
|
before
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
axios.post('federation/inbox/action/', payload).then((response) => {
|
axios.post("federation/inbox/action/", payload).then(response => {
|
||||||
self.$store.commit('ui/notifications', {type: 'inbox', count: 0})
|
self.$store.commit("ui/notifications", { type: "inbox", count: 0 })
|
||||||
self.notifications.results.forEach(n => {
|
self.notifications.results.forEach(n => {
|
||||||
n.is_read = true
|
n.is_read = true
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'filters.is_read' () {
|
"filters.is_read"() {
|
||||||
this.fetch(this.filters)
|
this.fetch(this.filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 vertical stripe segment">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
<div :class="['ui', {'loading': isLoading}, 'form']"></div>
|
<div :class="['ui', {'loading': isLoading}, 'form']"></div>
|
||||||
|
@ -24,146 +24,140 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
|
|
||||||
import SettingsGroup from '@/components/admin/SettingsGroup'
|
import SettingsGroup from "@/components/admin/SettingsGroup"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SettingsGroup
|
SettingsGroup
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
settingsData: null,
|
settingsData: null,
|
||||||
current: null
|
current: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
let self = this
|
let self = this
|
||||||
this.fetchSettings().then(r => {
|
this.fetchSettings().then(r => {
|
||||||
self.$nextTick(() => {
|
self.$nextTick(() => {
|
||||||
if (self.$store.state.route.hash) {
|
if (self.$store.state.route.hash) {
|
||||||
self.scrollTo(self.$store.state.route.hash.substr(1))
|
self.scrollTo(self.$store.state.route.hash.substr(1))
|
||||||
}
|
}
|
||||||
$('select.dropdown').dropdown()
|
$("select.dropdown").dropdown()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollTo (id) {
|
scrollTo(id) {
|
||||||
this.current = id
|
this.current = id
|
||||||
document.getElementById(id).scrollIntoView()
|
document.getElementById(id).scrollIntoView()
|
||||||
},
|
},
|
||||||
fetchSettings () {
|
fetchSettings() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
return axios.get('instance/admin/settings/').then((response) => {
|
return axios.get("instance/admin/settings/").then(response => {
|
||||||
self.settingsData = response.data
|
self.settingsData = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
settings: this.$gettext('Instance settings')
|
settings: this.$gettext("Instance settings")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
groups () {
|
groups() {
|
||||||
// somehow, extraction fails if in the return block directly
|
// somehow, extraction fails if in the return block directly
|
||||||
let instanceLabel = this.$gettext('Instance information')
|
let instanceLabel = this.$gettext("Instance information")
|
||||||
let usersLabel = this.$gettext('Users')
|
let usersLabel = this.$gettext("Users")
|
||||||
let musicLabel = this.$gettext('Music')
|
let musicLabel = this.$gettext("Music")
|
||||||
let playlistsLabel = this.$gettext('Playlists')
|
let playlistsLabel = this.$gettext("Playlists")
|
||||||
let federationLabel = this.$gettext('Federation')
|
let federationLabel = this.$gettext("Federation")
|
||||||
let subsonicLabel = this.$gettext('Subsonic')
|
let subsonicLabel = this.$gettext("Subsonic")
|
||||||
let statisticsLabel = this.$gettext('Statistics')
|
let statisticsLabel = this.$gettext("Statistics")
|
||||||
let errorLabel = this.$gettext('Error reporting')
|
let errorLabel = this.$gettext("Error reporting")
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: instanceLabel,
|
label: instanceLabel,
|
||||||
id: 'instance',
|
id: "instance",
|
||||||
settings: [
|
settings: [
|
||||||
'instance__name',
|
"instance__name",
|
||||||
'instance__short_description',
|
"instance__short_description",
|
||||||
'instance__long_description'
|
"instance__long_description"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: usersLabel,
|
label: usersLabel,
|
||||||
id: 'users',
|
id: "users",
|
||||||
settings: [
|
settings: [
|
||||||
'users__registration_enabled',
|
"users__registration_enabled",
|
||||||
'common__api_authentication_required',
|
"common__api_authentication_required",
|
||||||
'users__default_permissions',
|
"users__default_permissions",
|
||||||
'users__upload_quota'
|
"users__upload_quota"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: musicLabel,
|
label: musicLabel,
|
||||||
id: 'music',
|
id: "music",
|
||||||
settings: [
|
settings: [
|
||||||
'music__transcoding_enabled',
|
"music__transcoding_enabled",
|
||||||
'music__transcoding_cache_duration',
|
"music__transcoding_cache_duration"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: playlistsLabel,
|
label: playlistsLabel,
|
||||||
id: 'playlists',
|
id: "playlists",
|
||||||
settings: [
|
settings: ["playlists__max_tracks"]
|
||||||
'playlists__max_tracks'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: federationLabel,
|
label: federationLabel,
|
||||||
id: 'federation',
|
id: "federation",
|
||||||
settings: [
|
settings: [
|
||||||
'federation__enabled',
|
"federation__enabled",
|
||||||
'federation__music_needs_approval',
|
"federation__music_needs_approval",
|
||||||
'federation__collection_page_size',
|
"federation__collection_page_size",
|
||||||
'federation__music_cache_duration',
|
"federation__music_cache_duration",
|
||||||
'federation__actor_fetch_delay'
|
"federation__actor_fetch_delay"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: subsonicLabel,
|
label: subsonicLabel,
|
||||||
id: 'subsonic',
|
id: "subsonic",
|
||||||
settings: [
|
settings: ["subsonic__enabled"]
|
||||||
'subsonic__enabled'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: statisticsLabel,
|
label: statisticsLabel,
|
||||||
id: 'statistics',
|
id: "statistics",
|
||||||
settings: [
|
settings: [
|
||||||
'instance__nodeinfo_enabled',
|
"instance__nodeinfo_enabled",
|
||||||
'instance__nodeinfo_stats_enabled',
|
"instance__nodeinfo_stats_enabled",
|
||||||
'instance__nodeinfo_private'
|
"instance__nodeinfo_private"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: errorLabel,
|
label: errorLabel,
|
||||||
id: 'reporting',
|
id: "reporting",
|
||||||
settings: [
|
settings: ["raven__front_enabled", "raven__front_dsn"]
|
||||||
'raven__front_enabled',
|
|
||||||
'raven__front_dsn'
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
settingsData () {
|
settingsData() {
|
||||||
let self = this
|
let self = this
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
$(self.$el).find('.sticky').sticky({context: '#settings-grid'})
|
$(self.$el)
|
||||||
|
.find(".sticky")
|
||||||
|
.sticky({ context: "#settings-grid" })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<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
|
<router-link
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:to="{name: 'manage.library.files'}"><translate>Files</translate></router-link>
|
:to="{name: 'manage.library.files'}"><translate>Files</translate></router-link>
|
||||||
</div>
|
</nav>
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,10 +12,12 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let title = this.$gettext('Manage library')
|
let title = this.$gettext("Manage library")
|
||||||
|
let secondaryMenu = this.$gettext("Secondary menu")
|
||||||
return {
|
return {
|
||||||
title
|
title,
|
||||||
|
secondaryMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,10 +25,8 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.ui.menu .item > .label {
|
.ui.menu .item > .label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -2em;
|
right: -2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.title">
|
<main v-title="labels.title">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header"><translate>Library files</translate></h2>
|
<h2 class="ui header"><translate>Library files</translate></h2>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<library-files-table :show-library="true"></library-files-table>
|
<library-files-table :show-library="true"></library-files-table>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LibraryFilesTable from '@/components/manage/library/FilesTable'
|
import LibraryFilesTable from "@/components/manage/library/FilesTable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LibraryFilesTable
|
LibraryFilesTable
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Files')
|
title: this.$gettext("Files")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.manageUsers">
|
<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
|
<router-link
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:to="{name: 'manage.users.users.list'}"><translate>Users</translate></router-link>
|
:to="{name: 'manage.users.users.list'}"><translate>Users</translate></router-link>
|
||||||
<router-link
|
<router-link
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:to="{name: 'manage.users.invitations.list'}"><translate>Invitations</translate></router-link>
|
:to="{name: 'manage.users.invitations.list'}"><translate>Invitations</translate></router-link>
|
||||||
</div>
|
</nav>
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,9 +15,10 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
manageUsers: this.$gettext('Manage users')
|
manageUsers: this.$gettext("Manage users"),
|
||||||
|
secondaryMenu: this.$gettext("Secondary menu")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.invitations">
|
<main v-title="labels.invitations">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header"><translate>Invitations</translate></h2>
|
<h2 class="ui header"><translate>Invitations</translate></h2>
|
||||||
<invitation-form></invitation-form>
|
<invitation-form></invitation-form>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<invitations-table></invitations-table>
|
<invitations-table></invitations-table>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import InvitationForm from '@/components/manage/users/InvitationForm'
|
import InvitationForm from "@/components/manage/users/InvitationForm"
|
||||||
import InvitationsTable from '@/components/manage/users/InvitationsTable'
|
import InvitationsTable from "@/components/manage/users/InvitationsTable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -19,9 +19,9 @@ export default {
|
||||||
InvitationsTable
|
InvitationsTable
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
invitations: this.$gettext('Invitations')
|
invitations: this.$gettext("Invitations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
<div v-if="isLoading" class="ui vertical segment">
|
<div v-if="isLoading" class="ui vertical segment">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="object">
|
<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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted user red icon"></i>
|
<i class="circular inverted user red icon"></i>
|
||||||
|
@ -102,35 +102,34 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<button @click="fetchData" class="ui basic button"><translate>Refresh</translate></button>
|
<button @click="fetchData" class="ui basic button"><translate>Refresh</translate></button>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import $ from "jquery"
|
||||||
import $ from 'jquery'
|
import axios from "axios"
|
||||||
import axios from 'axios'
|
import logger from "@/logging"
|
||||||
import logger from '@/logging'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['id'],
|
props: ["id"],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
object: null,
|
object: null,
|
||||||
permissions: []
|
permissions: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
fetchData() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let url = 'manage/users/users/' + this.id + '/'
|
let url = "manage/users/users/" + this.id + "/"
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(response => {
|
||||||
self.object = response.data
|
self.object = response.data
|
||||||
self.permissions = []
|
self.permissions = []
|
||||||
self.allPermissions.forEach(p => {
|
self.allPermissions.forEach(p => {
|
||||||
|
@ -141,60 +140,72 @@ export default {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
update (attr, toNull) {
|
update(attr, toNull) {
|
||||||
let newValue = this.object[attr]
|
let newValue = this.object[attr]
|
||||||
if (toNull && !newValue) {
|
if (toNull && !newValue) {
|
||||||
newValue = null
|
newValue = null
|
||||||
}
|
}
|
||||||
console.log(newValue, typeof(newValue))
|
console.log(newValue, typeof newValue)
|
||||||
let params = {}
|
let params = {}
|
||||||
if (attr === 'permissions') {
|
if (attr === "permissions") {
|
||||||
params['permissions'] = {}
|
params["permissions"] = {}
|
||||||
this.allPermissions.forEach(p => {
|
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 {
|
} else {
|
||||||
params[attr] = newValue
|
params[attr] = newValue
|
||||||
}
|
}
|
||||||
axios.patch('manage/users/users/' + this.id + '/', params).then((response) => {
|
axios.patch("manage/users/users/" + this.id + "/", params).then(
|
||||||
logger.default.info(`${attr} was updated succcessfully to ${newValue}`)
|
response => {
|
||||||
}, (error) => {
|
logger.default.info(
|
||||||
logger.default.error(`Error while setting ${attr} to ${newValue}`, error)
|
`${attr} was updated succcessfully to ${newValue}`
|
||||||
})
|
)
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
logger.default.error(
|
||||||
|
`Error while setting ${attr} to ${newValue}`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
inactive: this.$gettext('Determine if the user account is active or not. Inactive users cannot login or use the service.'),
|
inactive: this.$gettext(
|
||||||
uploadQuota: this.$gettext('Determine how much content the user can upload. Leave empty to use the default value of the instance.')
|
"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 [
|
return [
|
||||||
{
|
{
|
||||||
'code': 'upload',
|
code: "upload",
|
||||||
'label': this.$gettext('Upload')
|
label: this.$gettext("Upload")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'code': 'library',
|
code: "library",
|
||||||
'label': this.$gettext('Library')
|
label: this.$gettext("Library")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'code': 'federation',
|
code: "federation",
|
||||||
'label': this.$gettext('Federation')
|
label: this.$gettext("Federation")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'code': 'settings',
|
code: "settings",
|
||||||
'label': this.$gettext('Settings')
|
label: this.$gettext("Settings")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
object () {
|
object() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
$('select.dropdown').dropdown()
|
$("select.dropdown").dropdown()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.users">
|
<main v-title="labels.users">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header"><translate>Users</translate></h2>
|
<h2 class="ui header"><translate>Users</translate></h2>
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<users-table></users-table>
|
<users-table></users-table>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UsersTable from '@/components/manage/users/UsersTable'
|
import UsersTable from "@/components/manage/users/UsersTable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
UsersTable
|
UsersTable
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
users: this.$gettext('Users')
|
users: this.$gettext("Users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.confirm">
|
<main class="main pusher" v-title="labels.confirm">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2><translate>Confirm your email</translate></h2>
|
<h2><translate>Confirm your email</translate></h2>
|
||||||
<form v-if="!success" class="ui form" @submit.prevent="submit()">
|
<form v-if="!success" class="ui form" @submit.prevent="submit()">
|
||||||
|
@ -28,16 +28,16 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['defaultKey'],
|
props: ["defaultKey"],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
@ -46,30 +46,32 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
confirm: this.$gettext('Confirm your email')
|
confirm: this.$gettext("Confirm your email")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
submit() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
self.errors = []
|
self.errors = []
|
||||||
let payload = {
|
let payload = {
|
||||||
key: this.key
|
key: this.key
|
||||||
}
|
}
|
||||||
return axios.post('auth/registration/verify-email/', payload).then(response => {
|
return axios.post("auth/registration/verify-email/", payload).then(
|
||||||
self.isLoading = false
|
response => {
|
||||||
self.success = true
|
self.isLoading = false
|
||||||
}, error => {
|
self.success = true
|
||||||
self.errors = error.backendErrors
|
},
|
||||||
self.isLoading = false
|
error => {
|
||||||
})
|
self.errors = error.backendErrors
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.reset">
|
<main class="main pusher" v-title="labels.reset">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2><translate>Reset your password</translate></h2>
|
<h2><translate>Reset your password</translate></h2>
|
||||||
<form class="ui form" @submit.prevent="submit()">
|
<form class="ui form" @submit.prevent="submit()">
|
||||||
|
@ -28,29 +28,31 @@
|
||||||
<translate>Ask for a password reset</translate></button>
|
<translate>Ask for a password reset</translate></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['defaultEmail'],
|
props: ["defaultEmail"],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
email: this.defaultEmail,
|
email: this.defaultEmail,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errors: []
|
errors: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
this.$refs.email.focus()
|
this.$refs.email.focus()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let reset = this.$gettext('Reset your password')
|
let reset = this.$gettext("Reset your password")
|
||||||
let placeholder = this.$gettext('Input the email address binded to your account')
|
let placeholder = this.$gettext(
|
||||||
|
"Input the email address binded to your account"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
reset,
|
reset,
|
||||||
placeholder
|
placeholder
|
||||||
|
@ -58,25 +60,27 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
submit() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
self.errors = []
|
self.errors = []
|
||||||
let payload = {
|
let payload = {
|
||||||
email: this.email
|
email: this.email
|
||||||
}
|
}
|
||||||
return axios.post('auth/password/reset/', payload).then(response => {
|
return axios.post("auth/password/reset/", payload).then(
|
||||||
self.isLoading = false
|
response => {
|
||||||
self.$router.push({
|
self.isLoading = false
|
||||||
name: 'auth.password-reset-confirm'
|
self.$router.push({
|
||||||
})
|
name: "auth.password-reset-confirm"
|
||||||
}, error => {
|
})
|
||||||
self.errors = error.backendErrors
|
},
|
||||||
self.isLoading = false
|
error => {
|
||||||
})
|
self.errors = error.backendErrors
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.changePassword">
|
<main class="main pusher" v-title="labels.changePassword">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui small text container">
|
<div class="ui small text container">
|
||||||
<h2><translate>Change your password</translate></h2>
|
<h2><translate>Change your password</translate></h2>
|
||||||
<form v-if="!success" class="ui form" @submit.prevent="submit()">
|
<form v-if="!success" class="ui form" @submit.prevent="submit()">
|
||||||
|
@ -33,22 +33,22 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import PasswordInput from '@/components/forms/PasswordInput'
|
import PasswordInput from "@/components/forms/PasswordInput"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['defaultToken', 'defaultUid'],
|
props: ["defaultToken", "defaultUid"],
|
||||||
components: {
|
components: {
|
||||||
PasswordInput
|
PasswordInput
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
newPassword: '',
|
newPassword: "",
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
token: this.defaultToken,
|
token: this.defaultToken,
|
||||||
|
@ -57,14 +57,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
changePassword: this.$gettext('Change your password')
|
changePassword: this.$gettext("Change your password")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
submit() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
self.errors = []
|
self.errors = []
|
||||||
|
@ -74,16 +74,18 @@ export default {
|
||||||
new_password1: this.newPassword,
|
new_password1: this.newPassword,
|
||||||
new_password2: this.newPassword
|
new_password2: this.newPassword
|
||||||
}
|
}
|
||||||
return axios.post('auth/password/reset/confirm/', payload).then(response => {
|
return axios.post("auth/password/reset/confirm/", payload).then(
|
||||||
self.isLoading = false
|
response => {
|
||||||
self.success = true
|
self.isLoading = false
|
||||||
}, error => {
|
self.success = true
|
||||||
self.errors = error.backendErrors
|
},
|
||||||
self.isLoading = false
|
error => {
|
||||||
})
|
self.errors = error.backendErrors
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="main pusher" v-title="labels.title">
|
<main 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
|
<router-link
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:to="{name: 'content.libraries.index'}"><translate>Libraries</translate></router-link>
|
:to="{name: 'content.libraries.index'}"><translate>Libraries</translate></router-link>
|
||||||
<router-link
|
<router-link
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:to="{name: 'content.libraries.files'}"><translate>Tracks</translate></router-link>
|
:to="{name: 'content.libraries.files'}"><translate>Tracks</translate></router-link>
|
||||||
</div>
|
</nav>
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let title = this.$gettext('Add content')
|
let title = this.$gettext("Add content")
|
||||||
|
let secondaryMenu = this.$gettext("Secondary menu")
|
||||||
return {
|
return {
|
||||||
title
|
title,
|
||||||
|
secondaryMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">
|
<div class="ui text container">
|
||||||
<h1>{{ labels.title }}</h1>
|
<h1>{{ labels.title }}</h1>
|
||||||
<p><translate>We offer various way to grab new content and make it available here.</translate></p>
|
<p><translate>We offer various way to grab new content and make it available here.</translate></p>
|
||||||
|
@ -22,21 +22,24 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {humanSize} from '@/filters'
|
import { humanSize } from "@/filters"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Add and manage content')
|
title: this.$gettext("Add and manage content")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultQuota () {
|
defaultQuota() {
|
||||||
let quota = this.$store.state.instance.settings.users.upload_quota.value * 1000 * 1000
|
let quota =
|
||||||
|
this.$store.state.instance.settings.users.upload_quota.value *
|
||||||
|
1000 *
|
||||||
|
1000
|
||||||
return humanSize(quota)
|
return humanSize(quota)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 v-if="isLoadingLibrary" :class="['ui', {'active': isLoadingLibrary}, 'inverted', 'dimmer']">
|
||||||
<div class="ui text loader"><translate>Loading library data...</translate></div>
|
<div class="ui text loader"><translate>Loading library data...</translate></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,15 +64,15 @@
|
||||||
<library-form :library="library" @updated="libraryUpdated" @deleted="libraryDeleted" />
|
<library-form :library="library" @updated="libraryUpdated" @deleted="libraryDeleted" />
|
||||||
</div>
|
</div>
|
||||||
</detail-area>
|
</detail-area>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import DetailMixin from './DetailMixin'
|
import DetailMixin from "./DetailMixin"
|
||||||
import DetailArea from './DetailArea'
|
import DetailArea from "./DetailArea"
|
||||||
import LibraryForm from './Form'
|
import LibraryForm from "./Form"
|
||||||
import LibraryFilesTable from './FilesTable'
|
import LibraryFilesTable from "./FilesTable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [DetailMixin],
|
mixins: [DetailMixin],
|
||||||
|
@ -81,46 +81,48 @@ export default {
|
||||||
LibraryForm,
|
LibraryForm,
|
||||||
LibraryFilesTable
|
LibraryFilesTable
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentTab: 'follows',
|
currentTab: "follows",
|
||||||
isLoadingFollows: false,
|
isLoadingFollows: false,
|
||||||
follows: null
|
follows: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchFollows()
|
this.fetchFollows()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
libraryUpdated () {
|
libraryUpdated() {
|
||||||
this.hiddenForm = true
|
this.hiddenForm = true
|
||||||
this.fetch()
|
this.fetch()
|
||||||
},
|
},
|
||||||
libraryDeleted () {
|
libraryDeleted() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'content.libraries.index'
|
name: "content.libraries.index"
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchFollows () {
|
fetchFollows() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoadingLibrary = true
|
self.isLoadingLibrary = true
|
||||||
axios.get(`libraries/${this.id}/follows/`).then((response) => {
|
axios.get(`libraries/${this.id}/follows/`).then(response => {
|
||||||
self.follows = response.data
|
self.follows = response.data
|
||||||
self.isLoadingFollows = false
|
self.isLoadingFollows = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateApproved (follow, value) {
|
updateApproved(follow, value) {
|
||||||
let self = this
|
let self = this
|
||||||
let action
|
let action
|
||||||
if (value) {
|
if (value) {
|
||||||
action = 'accept'
|
action = "accept"
|
||||||
} else {
|
} else {
|
||||||
action = 'reject'
|
action = "reject"
|
||||||
}
|
}
|
||||||
axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => {
|
axios
|
||||||
follow.isLoading = false
|
.post(`federation/follows/library/${follow.uuid}/${action}/`)
|
||||||
follow.approved = value
|
.then(response => {
|
||||||
})
|
follow.isLoading = false
|
||||||
|
follow.approved = value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ui vertical aligned stripe segment">
|
<section class="ui vertical aligned stripe segment">
|
||||||
<library-files-table :default-query="query"></library-files-table>
|
<library-files-table :default-query="query"></library-files-table>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LibraryFilesTable from './FilesTable'
|
import LibraryFilesTable from "./FilesTable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['query'],
|
props: ["query"],
|
||||||
components: {
|
components: {
|
||||||
LibraryFilesTable
|
LibraryFilesTable
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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 v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
|
||||||
<div class="ui text loader"><translate>Loading Libraries...</translate></div>
|
<div class="ui text loader"><translate>Loading Libraries...</translate></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,24 +24,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import LibraryForm from './Form'
|
import LibraryForm from "./Form"
|
||||||
import LibraryCard from './Card'
|
import LibraryCard from "./Card"
|
||||||
import Quota from './Quota'
|
import Quota from "./Quota"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
hiddenForm: true,
|
hiddenForm: true,
|
||||||
libraries: []
|
libraries: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetch()
|
this.fetch()
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -50,10 +50,10 @@ export default {
|
||||||
Quota
|
Quota
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetch () {
|
fetch() {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let self = this
|
let self = this
|
||||||
axios.get('libraries/').then((response) => {
|
axios.get("libraries/").then(response => {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.libraries = response.data.results
|
self.libraries = response.data.results
|
||||||
if (self.libraries.length === 0) {
|
if (self.libraries.length === 0) {
|
||||||
|
@ -61,7 +61,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
libraryCreated (library) {
|
libraryCreated(library) {
|
||||||
this.hiddenForm = true
|
this.hiddenForm = true
|
||||||
this.libraries.unshift(library)
|
this.libraries.unshift(library)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
<div v-if="isLoading" class="ui vertical segment" v-title="labels.playlist">
|
<div v-if="isLoading" class="ui vertical segment" v-title="labels.playlist">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted list yellow icon"></i>
|
<i class="circular inverted list yellow icon"></i>
|
||||||
|
@ -39,8 +39,8 @@
|
||||||
<p slot="modal-confirm"><translate>Delete playlist</translate></p>
|
<p slot="modal-confirm"><translate>Delete playlist</translate></p>
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<template v-if="edit">
|
<template v-if="edit">
|
||||||
<playlist-editor
|
<playlist-editor
|
||||||
@playlist-updated="playlist = $event"
|
@playlist-updated="playlist = $event"
|
||||||
|
@ -51,20 +51,20 @@
|
||||||
<h2><translate>Tracks</translate></h2>
|
<h2><translate>Tracks</translate></h2>
|
||||||
<track-table :display-position="true" :tracks="tracks"></track-table>
|
<track-table :display-position="true" :tracks="tracks"></track-table>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import RadioButton from '@/components/radios/Button'
|
import RadioButton from "@/components/radios/Button"
|
||||||
import PlayButton from '@/components/audio/PlayButton'
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import PlaylistEditor from '@/components/playlists/Editor'
|
import PlaylistEditor from "@/components/playlists/Editor"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
id: {required: true},
|
id: { required: true },
|
||||||
defaultEdit: {type: Boolean, default: false}
|
defaultEdit: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PlaylistEditor,
|
PlaylistEditor,
|
||||||
|
@ -72,7 +72,7 @@ export default {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
RadioButton
|
RadioButton
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
edit: this.defaultEdit,
|
edit: this.defaultEdit,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
@ -81,18 +81,18 @@ export default {
|
||||||
playlistTracks: []
|
playlistTracks: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function() {
|
||||||
this.fetch()
|
this.fetch()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
playlist: this.$gettext('Playlist')
|
playlist: this.$gettext("Playlist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updatePlts (v) {
|
updatePlts(v) {
|
||||||
this.playlistTracks = v
|
this.playlistTracks = v
|
||||||
this.tracks = v.map((e, i) => {
|
this.tracks = v.map((e, i) => {
|
||||||
let track = e.track
|
let track = e.track
|
||||||
|
@ -100,26 +100,29 @@ export default {
|
||||||
return track
|
return track
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetch: function () {
|
fetch: function() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
let url = 'playlists/' + this.id + '/'
|
let url = "playlists/" + this.id + "/"
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(response => {
|
||||||
self.playlist = response.data
|
self.playlist = response.data
|
||||||
axios.get(url + 'tracks/').then((response) => {
|
axios
|
||||||
self.updatePlts(response.data.results)
|
.get(url + "tracks/")
|
||||||
}).then(() => {
|
.then(response => {
|
||||||
self.isLoading = false
|
self.updatePlts(response.data.results)
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
self.isLoading = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deletePlaylist () {
|
deletePlaylist() {
|
||||||
let self = this
|
let self = this
|
||||||
let url = 'playlists/' + this.id + '/'
|
let url = "playlists/" + this.id + "/"
|
||||||
axios.delete(url).then((response) => {
|
axios.delete(url).then(response => {
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
self.$store.dispatch("playlists/fetchOwn")
|
||||||
self.$router.push({
|
self.$router.push({
|
||||||
path: '/library'
|
path: "/library"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-title="labels.playlists">
|
<main v-title="labels.playlists">
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2 class="ui header"><translate>Browsing playlists</translate></h2>
|
<h2 class="ui header"><translate>Browsing playlists</translate></h2>
|
||||||
<div :class="['ui', {'loading': isLoading}, 'form']">
|
<div :class="['ui', {'loading': isLoading}, 'form']">
|
||||||
<template v-if="$store.state.auth.authenticated">
|
<template v-if="$store.state.auth.authenticated">
|
||||||
|
@ -50,59 +50,61 @@
|
||||||
:total="result.count"
|
:total="result.count"
|
||||||
></pagination>
|
></pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import _ from 'lodash'
|
import _ from "lodash"
|
||||||
import $ from 'jquery'
|
import $ from "jquery"
|
||||||
|
|
||||||
import OrderingMixin from '@/components/mixins/Ordering'
|
import OrderingMixin from "@/components/mixins/Ordering"
|
||||||
import PaginationMixin from '@/components/mixins/Pagination'
|
import PaginationMixin from "@/components/mixins/Pagination"
|
||||||
import TranslationsMixin from '@/components/mixins/Translations'
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
import PlaylistCardList from '@/components/playlists/CardList'
|
import PlaylistCardList from "@/components/playlists/CardList"
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from "@/components/Pagination"
|
||||||
|
|
||||||
const FETCH_URL = 'playlists/'
|
const FETCH_URL = "playlists/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||||
props: {
|
props: {
|
||||||
defaultQuery: {type: String, required: false, default: ''}
|
defaultQuery: { type: String, required: false, default: "" }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PlaylistCardList,
|
PlaylistCardList,
|
||||||
Pagination
|
Pagination
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
|
let defaultOrdering = this.getOrderingFromString(
|
||||||
|
this.defaultOrdering || "-creation_date"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
result: null,
|
result: null,
|
||||||
page: parseInt(this.defaultPage),
|
page: parseInt(this.defaultPage),
|
||||||
query: this.defaultQuery,
|
query: this.defaultQuery,
|
||||||
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
paginateBy: parseInt(this.defaultPaginateBy || 12),
|
||||||
orderingDirection: defaultOrdering.direction || '+',
|
orderingDirection: defaultOrdering.direction || "+",
|
||||||
ordering: defaultOrdering.field,
|
ordering: defaultOrdering.field,
|
||||||
orderingOptions: [
|
orderingOptions: [
|
||||||
['creation_date', 'creation_date'],
|
["creation_date", "creation_date"],
|
||||||
['modification_date', 'modification_date'],
|
["modification_date", "modification_date"],
|
||||||
['name', 'name']
|
["name", "name"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
$('.ui.dropdown').dropdown()
|
$(".ui.dropdown").dropdown()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
let playlists = this.$gettext('Playlists')
|
let playlists = this.$gettext("Playlists")
|
||||||
let searchPlaceholder = this.$gettext('Enter an playlist name...')
|
let searchPlaceholder = this.$gettext("Enter an playlist name...")
|
||||||
return {
|
return {
|
||||||
playlists,
|
playlists,
|
||||||
searchPlaceholder
|
searchPlaceholder
|
||||||
|
@ -110,7 +112,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateQueryString: _.debounce(function () {
|
updateQueryString: _.debounce(function() {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
query: {
|
query: {
|
||||||
query: this.query,
|
query: this.query,
|
||||||
|
@ -120,7 +122,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 250),
|
}, 250),
|
||||||
fetchData: _.debounce(function () {
|
fetchData: _.debounce(function() {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let url = FETCH_URL
|
let url = FETCH_URL
|
||||||
|
@ -130,33 +132,33 @@ export default {
|
||||||
q: this.query,
|
q: this.query,
|
||||||
ordering: this.getOrderingAsString()
|
ordering: this.getOrderingAsString()
|
||||||
}
|
}
|
||||||
axios.get(url, {params: params}).then((response) => {
|
axios.get(url, { params: params }).then(response => {
|
||||||
self.result = response.data
|
self.result = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}, 500),
|
}, 500),
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page () {
|
page() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
paginateBy () {
|
paginateBy() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
ordering () {
|
ordering() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
orderingDirection () {
|
orderingDirection() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
query () {
|
query() {
|
||||||
this.updateQueryString()
|
this.updateQueryString()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<main>
|
||||||
<div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
|
<div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</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">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted feed blue icon"></i>
|
<i class="circular inverted feed blue icon"></i>
|
||||||
|
@ -30,8 +30,8 @@
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<h2><translate>Tracks</translate></h2>
|
<h2><translate>Tracks</translate></h2>
|
||||||
<track-table :tracks="tracks"></track-table>
|
<track-table :tracks="tracks"></track-table>
|
||||||
<div class="ui center aligned basic segment">
|
<div class="ui center aligned basic segment">
|
||||||
|
@ -43,26 +43,26 @@
|
||||||
:total="totalTracks"
|
:total="totalTracks"
|
||||||
></pagination>
|
></pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from "axios"
|
||||||
import TrackTable from '@/components/audio/track/Table'
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
import RadioButton from '@/components/radios/Button'
|
import RadioButton from "@/components/radios/Button"
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from "@/components/Pagination"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
id: {required: true}
|
id: { required: true }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
TrackTable,
|
TrackTable,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
Pagination
|
Pagination
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
radio: null,
|
radio: null,
|
||||||
|
@ -71,46 +71,49 @@ export default {
|
||||||
page: 1
|
page: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function() {
|
||||||
this.fetch()
|
this.fetch()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels () {
|
labels() {
|
||||||
return {
|
return {
|
||||||
title: this.$gettext('Radio')
|
title: this.$gettext("Radio")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectPage: function (page) {
|
selectPage: function(page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
},
|
},
|
||||||
fetch: function () {
|
fetch: function() {
|
||||||
let self = this
|
let self = this
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
let url = 'radios/radios/' + this.id + '/'
|
let url = "radios/radios/" + this.id + "/"
|
||||||
axios.get(url).then((response) => {
|
axios.get(url).then(response => {
|
||||||
self.radio = response.data
|
self.radio = response.data
|
||||||
axios.get(url + 'tracks/', {params: {page: this.page}}).then((response) => {
|
axios
|
||||||
this.totalTracks = response.data.count
|
.get(url + "tracks/", { params: { page: this.page } })
|
||||||
this.tracks = response.data.results
|
.then(response => {
|
||||||
}).then(() => {
|
this.totalTracks = response.data.count
|
||||||
self.isLoading = false
|
this.tracks = response.data.results
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
self.isLoading = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteRadio () {
|
deleteRadio() {
|
||||||
let self = this
|
let self = this
|
||||||
let url = 'radios/radios/' + this.id + '/'
|
let url = "radios/radios/" + this.id + "/"
|
||||||
axios.delete(url).then((response) => {
|
axios.delete(url).then(response => {
|
||||||
self.$router.push({
|
self.$router.push({
|
||||||
path: '/library'
|
path: "/library"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
page: function () {
|
page: function() {
|
||||||
this.fetch()
|
this.fetch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue