fix(front): [WIP] Upload Podcast

This commit is contained in:
upsiflu 2025-03-26 13:04:46 +01:00
parent 1911aa799d
commit 70192906ba
5 changed files with 58 additions and 33 deletions

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContentCategory, Channel, BackendError } from '~/types' import type { ContentCategory, Channel, BackendError } from '~/types'
import type { paths } from '~/generated/types'
import { slugify } from 'transliteration' import { slugify } from 'transliteration'
import { reactive, computed, ref, watchEffect, watch } from 'vue' import { reactive, computed, ref, watchEffect, watch } from 'vue'
@ -25,7 +26,7 @@ interface Events {
interface Props { interface Props {
object?: Channel | null object?: Channel | null
step: number step?: number
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
@ -51,6 +52,8 @@ const tagList = computed(() => ({
others: [].map(tag => ({ type: 'custom' as const, label: tag })) others: [].map(tag => ({ type: 'custom' as const, label: tag }))
})) }))
// If props has an object, then this form edits, else it creates
// TODO: rename to `process : 'creating' | 'editing'`
const creating = computed(() => props.object === null) const creating = computed(() => props.object === null)
const categoryChoices = computed(() => [ const categoryChoices = computed(() => [
{ {
@ -121,8 +124,11 @@ watchEffect(() => emit('submittable', submittable.value))
// TODO (wvffle): Add loader / Use Suspense // TODO (wvffle): Add loader / Use Suspense
const fetchMetadataChoices = async () => { const fetchMetadataChoices = async () => {
try { try {
const response = await axios.get('channels/metadata-choices') const response = await axios.get<paths['/api/v2/channels/metadata-choices/']['get']['responses']['200']['content']['application/json']>('channels/metadata-choices/')
metadataChoices.value = response.data // Old code expected as a response { language, itunes_category? }
// but actual response is Channel
if (response.data.metadata)
metadataChoices.value = response.data.metadata
} catch (error) { } catch (error) {
errors.value = (error as BackendError).backendErrors errors.value = (error as BackendError).backendErrors
} }

View File

@ -67,11 +67,11 @@ const store = useStore()
const errors = ref([] as string[]) const errors = ref([] as string[])
const values = reactive<{ const values = reactive<{
channel: string; // Channel UUID channelUuid: string | null; // Channel UUID
license: unknown; license: string | null;
album: unknown; album: unknown;
}>({ }>({
channel: props.channel?.uuid ?? null, channelUuid: props.channel?.uuid ?? null,
license: null, license: null,
album: null album: null
}) })
@ -81,7 +81,7 @@ const files = ref([] as VueUploadItem[])
// //
// Channels // Channels
// //
const availableChannels = ref<Channel[]>([]) const availableChannels = ref<Channel[] | null>(null)
/* /*
availableChannels>1? :=1 :=0 availableChannels>1? :=1 :=0
@ -110,15 +110,24 @@ const channelDropdownId = ref<Channel['artist']['id'] | null>(null)
const isLoading = ref(false) const isLoading = ref(false)
const selectedChannel = computed(() => const selectedChannel = computed(() =>
// Deeplink / Preset channel
props.channel props.channel
? props.channel ? props.channel
// Not yet loaded the available channels
: availableChannels.value === null
? null
// No channels available
: availableChannels.value.length === 0 : availableChannels.value.length === 0
? (createEmptyChannel(), null) ? (createEmptyChannel(), null)
// Exactly one available channel
: availableChannels.value.length === 1 : availableChannels.value.length === 1
? availableChannels.value[0] ? availableChannels.value[0]
: availableChannels.value.find(({ artist }) => artist.id === channelDropdownId.value) // Multiple available channels
: availableChannels.value.find(({ artist }) => artist.id === channelDropdownId.value) || null
) )
const emptyChannelCreateRequest:components['schemas']['ChannelCreateRequest'] = { const emptyChannelCreateRequest:components['schemas']['ChannelCreateRequest'] = {
name: store.state.auth.fullUsername, name: store.state.auth.fullUsername,
username: store.state.auth.username, username: store.state.auth.username,
@ -141,9 +150,16 @@ const createEmptyChannel = async () => {
const fetchChannels = async () => { const fetchChannels = async () => {
isLoading.value = true isLoading.value = true
try { try {
const response = await axios.get('channels/', { params: { scope: 'me' /* Ask Pablo: which param to filter for `music` | `podcast`? const response = await axios.get<paths['/api/v2/channels/']['get']['responses']['200']['content']['application/json']>(
Answer: const isPodcast = computed(() => channel.value?.artist?.content_category === 'podcast')*/ } }) 'channels/',
availableChannels.value = response.data.results { params: { scope: 'me' } }
)
availableChannels.value = response.data.results.filter(channel =>
props.filter === undefined
? true
: channel.artist?.content_category === props.filter
)
} catch (error) { } catch (error) {
errors.value = (error as BackendError).backendErrors errors.value = (error as BackendError).backendErrors
} }
@ -197,13 +213,13 @@ const remainingSpace = computed(() => Math.max(
// //
const includeDraftUploads = ref() const includeDraftUploads = ref()
const draftUploads = ref([] as Upload[]) const draftUploads = ref([] as Upload[])
whenever(() => values.channel !== null, async () => { whenever(() => values.channelUuid !== null, async () => {
files.value = [] files.value = []
draftUploads.value = [] draftUploads.value = []
try { try {
const response = await axios.get('uploads', { const response = await axios.get('uploads', {
params: { import_status: 'draft', channel: values.channel } params: { import_status: 'draft', channel: values.channelUuid }
}) })
draftUploads.value = response.data.results as Upload[] draftUploads.value = response.data.results as Upload[]
@ -465,8 +481,7 @@ defineExpose({
<div :class="['ui', 'required', 'field']"> <div :class="['ui', 'required', 'field']">
<label <label
v-if="availableChannels.length === 1" v-if="availableChannels !== null && availableChannels.length === 1"
for="channel-dropdown"
> >
{{ `${t('components.channels.UploadForm.label.channel')}: ${selectedChannel?.artist.name}` }} {{ `${t('components.channels.UploadForm.label.channel')}: ${selectedChannel?.artist.name}` }}
</label> </label>
@ -477,7 +492,7 @@ defineExpose({
{{ t('components.channels.UploadForm.label.channel') }} {{ t('components.channels.UploadForm.label.channel') }}
</label> </label>
<select <select
v-if="availableChannels.length > 1" v-if="availableChannels !== null && availableChannels.length > 1"
id="channel-dropdown" id="channel-dropdown"
v-model="channelDropdownId" v-model="channelDropdownId"
class="dropdown" class="dropdown"
@ -492,12 +507,13 @@ defineExpose({
</select> </select>
</div> </div>
<album-select <album-select
v-if="selectedChannel && albumSelection && albumSelection.albums.length > 0" v-if="selectedChannel !== null && albumSelection && albumSelection.albums.length > 0"
v-model="albumSelection" v-model="albumSelection"
:class="['ui', 'field']" :class="['ui', 'field']"
/> />
<div> <div>
<license-select <license-select
v-if="values.license !== null"
v-model="values.license" v-model="values.license"
:class="['ui', 'field']" :class="['ui', 'field']"
/> />

View File

@ -30,7 +30,7 @@ interface Events {
} }
interface Props { interface Props {
id: number id: number | string
} }
const store = useStore() const store = useStore()
@ -80,12 +80,10 @@ const fetchData = async () => {
artistCredit.value = albumResponse.data.artist_credit artistCredit.value = albumResponse.data.artist_credit
// fetch the first artist of the album
const artistResponse = await axios.get(`artists/${albumResponse.data.artist_credit[0].artist.id}/`) const artistResponse = await axios.get(`artists/${albumResponse.data.artist_credit[0].artist.id}/`)
artist.value = artistResponse.data artist.value = artistResponse.data
if (artist.value?.channel) {
artist.value.channel.artist = artist.value
}
object.value = albumResponse.data object.value = albumResponse.data
if (object.value) { if (object.value) {
@ -216,6 +214,7 @@ const remove = async () => {
</Layout> </Layout>
<Layout flex> <Layout flex>
<PlayButton <PlayButton
v-if="object.tracks"
split split
:tracks="object.tracks" :tracks="object.tracks"
:is-playable="object.is_playable" :is-playable="object.is_playable"

View File

@ -13,7 +13,7 @@ import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Spacer from '~/components/ui/Spacer.vue' import Spacer from '~/components/ui/Spacer.vue'
import Input from '~/components/ui/Input.vue' import Input from '~/components/ui/Input.vue'
import Textarea from '~/components/ui/Textarea.vue' // import Textarea from '~/components/ui/Textarea.vue'
import Pills from '~/components/ui/Pills.vue' import Pills from '~/components/ui/Pills.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
@ -268,12 +268,14 @@ const resetField = (fieldId: string) => {
</select> </select>
</template> </template>
<template v-else-if="fieldConfig.type === 'content'"> <template v-else-if="fieldConfig.type === 'content'">
<Textarea TEXTAREA
<b> v-model: </b>{{ values[fieldConfig.id].text }}
<!-- <Textarea
v-model="values[fieldConfig.id].text" v-model="values[fieldConfig.id].text"
:label="fieldConfig.label" :label="fieldConfig.label"
:field-id="fieldConfig.id" :field-id="fieldConfig.id"
initial-lines="3" initial-lines="3"
/> /> -->
</template> </template>
<!-- TODO: Style Attachment Input --> <!-- TODO: Style Attachment Input -->
<template v-else-if="fieldConfig.type === 'attachment'"> <template v-else-if="fieldConfig.type === 'attachment'">
@ -318,7 +320,9 @@ const resetField = (fieldId: string) => {
</Button> </Button>
</Layout> </Layout>
<Spacer /> <Spacer />
<Textarea TEXTAREA
<b> v-model: </b>{{ summary }}
<!-- <Textarea
id="change-summary" id="change-summary"
v-model="summary" v-model="summary"
name="change-summary" name="change-summary"
@ -326,7 +330,7 @@ const resetField = (fieldId: string) => {
:label="t('components.library.EditForm.label.summary')" :label="t('components.library.EditForm.label.summary')"
:placeholder="labels.summaryPlaceholder" :placeholder="labels.summaryPlaceholder"
> >
<Button <Link
v-if="objectType === 'track'" v-if="objectType === 'track'"
low-height low-height
secondary secondary
@ -334,8 +338,8 @@ const resetField = (fieldId: string) => {
:to="{name: 'library.tracks.detail', params: {id: object.id }}" :to="{name: 'library.tracks.detail', params: {id: object.id }}"
> >
{{ t('components.library.EditForm.button.cancel') }} {{ t('components.library.EditForm.button.cancel') }}
</Button> </Link>
</Textarea> </Textarea> -->
<Button <Button
:is-loading="isLoading" :is-loading="isLoading"
primary primary

View File

@ -480,7 +480,7 @@ const updateSubscriptionCount = (delta: number) => {
v-if="object.artist?.tags && object.artist?.tags.length > 0" v-if="object.artist?.tags && object.artist?.tags.length > 0"
:tags="object.artist.tags" :tags="object.artist.tags"
:limit="5" :limit="5"
show-more="true" :show-more="true"
/> />
<Tabs> <Tabs>
<Tab <Tab