Skip to content
Snippets Groups Projects
ChannelForm.vue 12.7 KiB
Newer Older
Georg Krause's avatar
Georg Krause committed
  <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>
Georg Krause's avatar
Georg Krause committed
        <li
          v-for="(error, key) in errors"
          :key="key"
        >
          {{ error }}
        </li>
      </ul>
    </div>
    <template v-if="metadataChoices">
Georg Krause's avatar
Georg Krause committed
      <fieldset
        v-if="creating && step === 1"
        class="ui grouped channel-type required field"
      >
        <legend>
Georg Krause's avatar
Georg Krause committed
          <translate translate-context="Content/Channel/Paragraph">
            What will this channel be used for?
          </translate>
        </legend>
Georg Krause's avatar
Georg Krause committed
        <div class="ui hidden divider" />
Georg Krause's avatar
Georg Krause committed
          <div
            v-for="(choice, key) in categoryChoices"
            :key="key"
            :class="['ui', 'radio', 'checkbox', {selected: choice.value == newValues.content_category}]"
          >
            <input
              :id="`category-${choice.value}`"
              v-model="newValues.content_category"
              type="radio"
              name="channel-category"
              :value="choice.value"
            >
            <label :for="`category-${choice.value}`">
Georg Krause's avatar
Georg Krause committed
              <span :class="['right floated', 'placeholder', 'image', {circular: choice.value === 'music'}]" />
              <strong>{{ choice.label }}</strong>
Georg Krause's avatar
Georg Krause committed
              <div class="ui small hidden divider" />
              {{ 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>
Georg Krause's avatar
Georg Krause committed
          <input
            v-model="newValues.name"
            type="text"
            required
            :placeholder="labels.namePlaceholder"
          >
        </div>
        <div class="ui required field">
          <label for="channel-username">
            <translate translate-context="Content/Channel/*">Fediverse handle</translate>
          </label>
          <div class="ui left labeled input">
Georg Krause's avatar
Georg Krause committed
            <div class="ui basic label">
              @
            </div>
            <input
              v-model="newValues.username"
              type="text"
              :required="creating"
              :disabled="!creating"
              :placeholder="labels.usernamePlaceholder"
            >
          </div>
          <template v-if="creating">
Georg Krause's avatar
Georg Krause committed
            <div class="ui small hidden divider" />
Georg Krause's avatar
Georg Krause committed
              <translate translate-context="Content/Channels/Paragraph">
                Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
              </translate>
            </p>
          </template>
        </div>
        <div class="six wide column">
          <attachment-input
            v-model="newValues.cover"
            :required="false"
            :image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
Georg Krause's avatar
Georg Krause committed
            @delete="newValues.cover = null"
          >
            <translate
              slot="label"
              translate-context="Content/Channel/*"
            >
              Channel Picture
            </translate>
          </attachment-input>
        </div>
Georg Krause's avatar
Georg Krause committed
        <div class="ui small hidden divider" />
        <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
                id="channel-tags"
Georg Krause's avatar
Georg Krause committed
                v-model="newValues.tags"
                :required="false"
              />
Georg Krause's avatar
Georg Krause committed
          <div
            v-if="newValues.content_category === 'podcast'"
            class="six wide column"
          >
            <div class="ui required field">
              <label for="channel-language">
                <translate translate-context="*/*/*">Language</translate>
              </label>
              <select
                id="channel-language"
                v-model="newValues.metadata.language"
Georg Krause's avatar
Georg Krause committed
                name="channel-language"
Georg Krause's avatar
Georg Krause committed
                class="ui search selection dropdown"
              >
                <option
                  v-for="(v, key) in metadataChoices.language"
                  :key="key"
                  :value="v.value"
                >
                  {{ v.label }}
                </option>
Georg Krause's avatar
Georg Krause committed
        <div class="ui small hidden divider" />
        <div class="ui field">
          <label for="channel-name">
            <translate translate-context="*/*/*">Description</translate>
          </label>
Georg Krause's avatar
Georg Krause committed
          <content-form v-model="newValues.description" />
Georg Krause's avatar
Georg Krause committed
        <div
          v-if="newValues.content_category === 'podcast'"
          class="ui two fields"
        >
          <div class="ui required field">
            <label for="channel-itunes-category">
              <translate translate-context="*/*/*">Category</translate>
            </label>
            <select
              id="itunes-category"
              v-model="newValues.metadata.itunes_category"
Georg Krause's avatar
Georg Krause committed
              name="itunes-category"
Georg Krause's avatar
Georg Krause committed
              class="ui dropdown"
            >
              <option
                v-for="(v, key) in metadataChoices.itunes_category"
                :key="key"
                :value="v.value"
              >
                {{ v.label }}
              </option>
            </select>
          </div>
          <div class="ui field">
            <label for="channel-itunes-category">
              <translate translate-context="*/*/*">Subcategory</translate>
            </label>
            <select
              id="itunes-category"
              v-model="newValues.metadata.itunes_subcategory"
Georg Krause's avatar
Georg Krause committed
              name="itunes-category"
              :disabled="!newValues.metadata.itunes_category"
Georg Krause's avatar
Georg Krause committed
              class="ui dropdown"
            >
              <option
                v-for="(v, key) in itunesSubcategories"
                :key="key"
                :value="v"
              >
                {{ v }}
              </option>
Georg Krause's avatar
Georg Krause committed
        <div
          v-if="newValues.content_category === 'podcast'"
          class="ui two fields"
        >
          <div class="ui field">
            <label for="channel-itunes-email">
              <translate translate-context="*/*/*">Owner e-mail address</translate>
Georg Krause's avatar
Georg Krause committed
              v-model="newValues.metadata.owner_email"
              name="channel-itunes-email"
          </div>
          <div class="ui field">
            <label for="channel-itunes-name">
              <translate translate-context="*/*/*">Owner name</translate>
            </label>
            <input
              id="channel-itunes-name"
Georg Krause's avatar
Georg Krause committed
              v-model="newValues.metadata.owner_name"
              name="channel-itunes-name"
Georg Krause's avatar
Georg Krause committed
          <translate translate-context="*/*/*">
            Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
          </translate>
Georg Krause's avatar
Georg Krause committed
    <div
      v-else
      class="ui active inverted dimmer"
    >
      <div class="ui text loader">
Georg Krause's avatar
Georg Krause committed
        <translate translate-context="*/*/*">
          Loading
        </translate>
      </div>
    </div>
  </form>
</template>

<script>
import axios from 'axios'

Ciaran Ainsworth's avatar
Ciaran Ainsworth committed
import AttachmentInput from '@/components/common/AttachmentInput.vue'
import TagsSelector from '@/components/library/TagsSelector.vue'
Georg Krause's avatar
Georg Krause committed
function slugify (text) {
  return text.toString().toLowerCase()
Georg Krause's avatar
Georg Krause committed
    .replace(/\s+/g, '') // Remove spaces
    .replace(/[^\w]+/g, '') // Remove all non-word chars
}

export default {
  components: {
    AttachmentInput,
    TagsSelector
  },
Georg Krause's avatar
Georg Krause committed
  props: {
    object: { type: Object, required: false, default: null },
    step: { type: Number, required: false, default: 1 }
Georg Krause's avatar
Georg Krause committed
    const oldValues = {}
Georg Krause's avatar
Georg Krause committed
      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: {
Georg Krause's avatar
Georg Krause committed
        name: oldValues.name || '',
        username: oldValues.username || '',
        tags: oldValues.tags || [],
Georg Krause's avatar
Georg Krause committed
        description: (oldValues.description || {}).text || '',
        cover: (oldValues.cover || {}).uuid || null,
Georg Krause's avatar
Georg Krause committed
        content_category: oldValues.content_category || 'podcast',
        metadata: oldValues.metadata || {}
      }
    }
  },
  computed: {
    creating () {
      return this.object === null
    },
    categoryChoices () {
      return [
        {
Georg Krause's avatar
Georg Krause committed
          value: 'podcast',
          label: this.$pgettext('*/*/*', 'Podcasts'),
          helpText: this.$pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.')
Georg Krause's avatar
Georg Krause committed
          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++) {
Georg Krause's avatar
Georg Krause committed
        const element = this.metadataChoices.itunes_category[index]
        if (element.value === this.newValues.metadata.itunes_category) {
          return element.children || []
        }
      }
      return []
    },
    labels () {
      return {
Georg Krause's avatar
Georg Krause committed
        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
    }
  },
Georg Krause's avatar
Georg Krause committed
  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
    }
  },

  created () {
    this.fetchMetadataChoices()
  },
  methods: {
    fetchMetadataChoices () {
Georg Krause's avatar
Georg Krause committed
      const self = this
      axios.get('channels/metadata-choices').then((response) => {
        self.metadataChoices = response.data
      }, error => {
        self.errors = error.backendErrors
      })
    },
    submit () {
      this.isLoading = true
Georg Krause's avatar
Georg Krause committed
      const self = this
      const handler = this.creating ? axios.post : axios.patch
      const url = this.creating ? 'channels/' : `channels/${this.object.uuid}`
      const payload = {
        name: this.newValues.name,
        username: this.newValues.username,
        tags: this.newValues.tags,
        content_category: this.newValues.content_category,
        cover: this.newValues.cover,
Georg Krause's avatar
Georg Krause committed
        metadata: this.newValues.metadata
      }
      if (this.newValues.description) {
        payload.description = {
          content_type: 'text/markdown',
Georg Krause's avatar
Georg Krause committed
          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)
      })
    }
  }
}
</script>