funkwhale/front/src/components/audio/ChannelForm.vue

325 lines
12 KiB
Vue

<template>
<form class="ui form" @submit.prevent.stop="submit">
<div v-if="errors.length > 0" role="alert" class="ui negative message">
<h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while saving channel</translate></h4>
<ul class="list">
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<template v-if="metadataChoices">
<fieldset v-if="creating && step === 1" class="ui grouped channel-type required field">
<legend>
<translate translate-context="Content/Channel/Paragraph">What this channel will be used for?</translate>
</legend>
<div class="ui hidden divider"></div>
<div class="field">
<div :class="['ui', 'radio', 'checkbox', {selected: choice.value == newValues.content_category}]" v-for="choice in categoryChoices">
<input type="radio" name="channel-category" :id="`category-${choice.value}`" :value="choice.value" v-model="newValues.content_category">
<label :for="`category-${choice.value}`">
<span :class="['right floated', 'placeholder', 'image', {circular: choice.value === 'music'}]"></span>
<strong>{{ choice.label }}</strong>
<div class="ui small hidden divider"></div>
{{ choice.helpText }}
</label>
</div>
</div>
</fieldset>
<template v-if="!creating || step === 2">
<div class="ui required field">
<label for="channel-name">
<translate translate-context="Content/Channel/*">Name</translate>
</label>
<input type="text" required v-model="newValues.name" :placeholder="labels.namePlaceholder">
</div>
<div class="ui required field">
<label for="channel-username">
<translate translate-context="Content/Channel/*">Social Network Name</translate>
</label>
<div class="ui left labeled input">
<div class="ui basic label">@</div>
<input type="text" :required="creating" :disabled="!creating" :placeholder="labels.usernamePlaceholder" v-model="newValues.username">
</div>
<template v-if="creating">
<div class="ui small hidden divider"></div>
<p>
<translate translate-context="Content/Channels/Paragraph">Used in URLs and to follow this channel on the federation. You cannot change it afterwards.</translate>
</p>
</template>
</div>
<div class="six wide column">
<attachment-input
v-model="newValues.cover"
:required="false"
:image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
@delete="newValues.cover = null">
<translate translate-context="Content/Channel/*" slot="label">Channel Picture</translate>
</attachment-input>
</div>
<div class="ui small hidden divider"></div>
<div class="ui stackable grid row">
<div class="ten wide column">
<div class="ui field">
<label for="channel-tags">
<translate translate-context="*/*/*">Tags</translate>
</label>
<tags-selector
v-model="newValues.tags"
id="channel-tags"
:required="false"></tags-selector>
</div>
</div>
<div class="six wide column" v-if="newValues.content_category === 'podcast'">
<div class="ui required field">
<label for="channel-language">
<translate translate-context="*/*/*">Language</translate>
</label>
<select
name="channel-language"
id="channel-language"
v-model="newValues.metadata.language"
required
class="ui search selection dropdown">
<option v-for="v in metadataChoices.language" :value="v.value">{{ v.label }}</option>
</select>
</div>
</div>
</div>
<div class="ui small hidden divider"></div>
<div class="ui field">
<label for="channel-name">
<translate translate-context="*/*/*">Description</translate>
</label>
<content-form v-model="newValues.description"></content-form>
</div>
<div class="ui two fields" v-if="newValues.content_category === 'podcast'">
<div class="ui required field">
<label for="channel-itunes-category">
<translate translate-context="*/*/*">Category</translate>
</label>
<select
name="itunes-category"
id="itunes-category"
v-model="newValues.metadata.itunes_category"
required
class="ui dropdown">
<option v-for="v in metadataChoices.itunes_category" :value="v.value">{{ v.label }}</option>
</select>
</div>
<div class="ui field">
<label for="channel-itunes-category">
<translate translate-context="*/*/*">Subcategory</translate>
</label>
<select
name="itunes-category"
id="itunes-category"
v-model="newValues.metadata.itunes_subcategory"
:disabled="!newValues.metadata.itunes_category"
class="ui dropdown">
<option v-for="v in itunesSubcategories" :value="v">{{ v }}</option>
</select>
</div>
</div>
<div class="ui two fields" v-if="newValues.content_category === 'podcast'">
<div class="ui field">
<label for="channel-itunes-email">
<translate translate-context="*/*/*">Owner email</translate>
</label>
<input
name="channel-itunes-email"
id="channel-itunes-email"
type="email"
v-model="newValues.metadata.owner_email">
</div>
<div class="ui field">
<label for="channel-itunes-name">
<translate translate-context="*/*/*">Owner name</translate>
</label>
<input
name="channel-itunes-name"
id="channel-itunes-name"
maxlength="255"
v-model="newValues.metadata.owner_name">
</div>
</div>
<p>
<translate translate-context="*/*/*">Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.</translate>
</p>
</template>
</template>
<div v-else class="ui active inverted dimmer">
<div class="ui text loader">
<translate translate-context="*/*/*">Loading</translate>
</div>
</div>
</form>
</template>
<script>
import axios from 'axios'
import AttachmentInput from '@/components/common/AttachmentInput'
import TagsSelector from '@/components/library/TagsSelector'
function slugify(text) {
return text.toString().toLowerCase()
.replace(/\s+/g, '') // Remove spaces
.replace(/[^\w]+/g, '') // Remove all non-word chars
}
export default {
props: {
object: {type: Object, required: false, default: null},
step: {type: Number, required: false, default: 1},
},
components: {
AttachmentInput,
TagsSelector
},
created () {
this.fetchMetadataChoices()
},
data () {
let oldValues = {}
if (this.object) {
oldValues.metadata = {...(this.object.metadata || {})}
oldValues.name = this.object.artist.name
oldValues.description = this.object.artist.description
oldValues.cover = this.object.artist.cover
oldValues.tags = this.object.artist.tags
oldValues.content_category = this.object.artist.content_category
oldValues.username = this.object.actor.preferred_username
}
return {
isLoading: false,
errors: [],
metadataChoices: null,
newValues: {
name: oldValues.name || "",
username: oldValues.username || "",
tags: oldValues.tags || [],
description: (oldValues.description || {}).text || "",
cover: (oldValues.cover || {}).uuid || null,
content_category: oldValues.content_category || "podcast",
metadata: oldValues.metadata || {},
}
}
},
computed: {
creating () {
return this.object === null
},
categoryChoices () {
return [
{
value: "podcast",
label: this.$pgettext('*/*/*', "Podcasts"),
helpText: this.$pgettext('Content/Channels/Help', "Host your episodes and keep your community updated."),
},
{
value: "music",
label: this.$pgettext('*/*/*', "Artist discography"),
helpText: this.$pgettext('Content/Channels/Help', "Publish music you make as a nice discography of albums and singles."),
}
]
},
itunesSubcategories () {
for (let index = 0; index < this.metadataChoices.itunes_category.length; index++) {
const element = this.metadataChoices.itunes_category[index];
if (element.value === this.newValues.metadata.itunes_category) {
return element.children || []
}
}
return []
},
labels () {
return {
namePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', "Awesome channel name"),
usernamePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', "awesomechannelname"),
}
},
submittable () {
let v = this.newValues.name && this.newValues.username
if (this.newValues.content_category === 'podcast') {
v = v && this.newValues.metadata.itunes_category && this.newValues.metadata.language
}
return !!v
}
},
methods: {
fetchMetadataChoices () {
let self = this
axios.get('channels/metadata-choices').then((response) => {
self.metadataChoices = response.data
}, error => {
self.errors = error.backendErrors
})
},
submit () {
this.isLoading = true
let self = this
let handler = this.creating ? axios.post : axios.patch
let url = this.creating ? `channels/` : `channels/${this.object.uuid}`
let payload = {
name: this.newValues.name,
username: this.newValues.username,
tags: this.newValues.tags,
content_category: this.newValues.content_category,
cover: this.newValues.cover,
metadata: this.newValues.metadata,
}
if (this.newValues.description) {
payload.description = {
content_type: 'text/markdown',
text: this.newValues.description,
}
} else {
payload.description = null
}
handler(url, payload).then((response) => {
self.isLoading = false
if (self.creating) {
self.$emit('created', response.data)
} else {
self.$emit('updated', response.data)
}
}, error => {
self.isLoading = false
self.errors = error.backendErrors
self.$emit('errored', self.errors)
})
}
},
watch: {
"newValues.name" (v) {
if (this.creating) {
this.newValues.username = slugify(v)
}
},
"newValues.metadata.itunes_category" (v) {
this.newValues.metadata.itunes_subcategory = null
},
"newValues.content_category": {
handler (v) {
this.$emit("category", v)
},
immediate: true
},
isLoading: {
handler (v) {
this.$emit("loading", v)
},
immediate: true
},
submittable: {
handler (v) {
this.$emit("submittable", v)
},
immediate: true
},
}
}
</script>