feat(front): [WIP] new upload process channel form #2081
This commit is contained in:
parent
a8d5011796
commit
50dd404dab
|
@ -15,14 +15,11 @@ interface Events {
|
|||
(e: 'created'): void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
channel: Channel
|
||||
}
|
||||
const channel = defineModel<Channel>({required: true})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<Events>()
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const title = ref('')
|
||||
|
||||
|
@ -35,7 +32,7 @@ const submit = async () => {
|
|||
try {
|
||||
await axios.post('albums/', {
|
||||
title: title.value,
|
||||
artist: props.channel.artist?.id
|
||||
artist: channel.value.artist?.id
|
||||
})
|
||||
|
||||
emit('created')
|
||||
|
@ -77,12 +74,10 @@ defineExpose({
|
|||
</ul>
|
||||
</Alert>
|
||||
<div class="ui required field">
|
||||
<label for="album-title">
|
||||
{{ t('components.channels.AlbumForm.label.albumTitle') }}
|
||||
</label>
|
||||
<Input
|
||||
v-model="title"
|
||||
type="text"
|
||||
:label="t('components.channels.AlbumForm.label.albumTitle')"
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
|
|
|
@ -1,63 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
import type { Channel } from '~/types'
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
import ChannelAlbumForm from '~/components/channels/AlbumForm.vue'
|
||||
import type { Channel, BackendError } from '~/types'
|
||||
|
||||
import { watch, ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
import { watch, ref, emit } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModal } from '~/ui/composables/useModal.ts'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
|
||||
interface Events {
|
||||
(e: 'created'): void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
channel: Channel
|
||||
}
|
||||
import Input from '~/components/ui/Input.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<Events>()
|
||||
defineProps<Props>()
|
||||
const channel = defineModel<Channel>({required: true})
|
||||
defineEmits(['created'])
|
||||
const newAlbumTitle = ref<string>('')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const submittable = ref(false)
|
||||
const show = ref(false)
|
||||
const errors = ref<string[]>([])
|
||||
|
||||
const {isOpen:show} = useModal('album')
|
||||
|
||||
defineEmits(['created'])
|
||||
|
||||
watch(show, () => {
|
||||
isLoading.value = false
|
||||
submittable.value = false
|
||||
})
|
||||
|
||||
const albumForm = ref()
|
||||
// Create a new Album and tell the parent to re-fetch all albums
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
|
||||
const submit = async () => {
|
||||
isLoading.value = true
|
||||
errors.value = []
|
||||
|
||||
try {
|
||||
await axios.post('albums/', {
|
||||
title: newAlbumTitle.value,
|
||||
artist_credit: [channel.value.artist?.id]
|
||||
})
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
}
|
||||
|
||||
emit('created')
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="t(channel.artist.content_category === 'podcast' ? 'components.channels.AlbumModal.header.newSeries' : 'components.channels.AlbumModal.header.newAlbum')"
|
||||
<Modal :title="t(channel?.artist?.content_category === 'podcast' ? 'components.channels.AlbumModal.header.newSeries' : 'components.channels.AlbumModal.header.newAlbum')"
|
||||
v-model="show"
|
||||
class="small"
|
||||
:cancel="t('components.channels.AlbumModal.button.cancel')"
|
||||
>
|
||||
<div class="scrolling content">
|
||||
<channel-album-form
|
||||
ref="albumForm"
|
||||
:channel="channel"
|
||||
@loading="isLoading = $event"
|
||||
@submittable="submittable = $event"
|
||||
@created="emit('created')"
|
||||
<template #alert>
|
||||
<Alert
|
||||
v-if="errors.length > 0"
|
||||
red
|
||||
>
|
||||
<h4 class="header">
|
||||
{{ t('components.channels.AlbumForm.header.error') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
v-for="(error, key) in errors"
|
||||
:key="key"
|
||||
>
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
<Layout form
|
||||
:class="['ui', {loading: isLoading}, 'form']"
|
||||
@submit.stop.prevent
|
||||
>
|
||||
<Input
|
||||
v-model="newAlbumTitle"
|
||||
required
|
||||
type="text"
|
||||
:label="t('components.channels.AlbumForm.label.albumTitle')"
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<template #actions>
|
||||
<Button
|
||||
:is-loading="isLoading"
|
||||
:disabled="!submittable"
|
||||
primary
|
||||
@click.stop.prevent="albumForm.submit()"
|
||||
@click.stop.prevent="submit()"
|
||||
>
|
||||
{{ t('components.channels.AlbumModal.button.create') }}
|
||||
</Button>
|
||||
|
|
|
@ -2,78 +2,82 @@
|
|||
import type { Album, Channel } from '~/types'
|
||||
|
||||
import axios from 'axios'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModal } from '~/ui/composables/useModal.ts'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: string | null
|
||||
channel: Channel | null
|
||||
}
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<Events>()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: null,
|
||||
channel: null
|
||||
})
|
||||
const model = defineModel<{channel: Channel, albumId: Album['id'] | '', albums: Album[]}>({ required: true })
|
||||
|
||||
const value = useVModel(props, 'modelValue', emit)
|
||||
|
||||
const albums = reactive<Album[]>([])
|
||||
const albums = ref<Album[]>([])
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
albums.length = 0
|
||||
if (!props.channel?.artist) return
|
||||
|
||||
const fetchAlbums = async () => {
|
||||
isLoading.value = true
|
||||
const response = await axios.get('albums/', {
|
||||
params: {
|
||||
artist: props.channel?.artist.id,
|
||||
artist: model.value.channel.artist.id,
|
||||
include_channels: 'true'
|
||||
}
|
||||
})
|
||||
|
||||
console.log("I found another album with artist", props.channel?.artist.name, ":", response.data.results)
|
||||
albums.push(...response.data.results)
|
||||
console.log("I found another album with artist", model.value.channel.artist.name, ":", response.data.results)
|
||||
albums.value = response.data.results
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
watch(() => props.channel, fetchData, { immediate: true })
|
||||
watch(() => model.value.channel, fetchAlbums, { immediate: true })
|
||||
watch(albums, (value) => {
|
||||
if (value.length === 1)
|
||||
selectedAlbumId.value = albums.value[0].id
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label for="album-dropdown">
|
||||
<span v-if="channel && channel.artist && channel.artist.content_category === 'podcast'">
|
||||
{{ t('components.channels.AlbumSelect.label.series') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('components.channels.AlbumSelect.label.album') }}
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
id="album-dropdown"
|
||||
v-model="value"
|
||||
class="ui search normal dropdown"
|
||||
<label for="album-dropdown">
|
||||
<span v-if="model.channel.artist.content_category === 'podcast'">
|
||||
{{ t('components.channels.AlbumSelect.label.series') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('components.channels.AlbumSelect.label.album') }}
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
id="album-dropdown"
|
||||
v-model="model.albumId"
|
||||
class="ui search normal dropdown"
|
||||
>
|
||||
<option value="">
|
||||
{{ t('components.channels.AlbumSelect.option.none') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="album in albums"
|
||||
:key="album.id"
|
||||
:value="album.id"
|
||||
>
|
||||
<option value="">
|
||||
{{ t('components.channels.AlbumSelect.option.none') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="album in albums"
|
||||
:key="album.id"
|
||||
:value="album.id"
|
||||
>
|
||||
{{ album.title }}
|
||||
{{ t('components.channels.AlbumSelect.meta.tracks', album.tracks_count) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
{{ album.title }}
|
||||
{{ t('components.channels.AlbumSelect.meta.tracks', album.tracks_count) }}
|
||||
</option>
|
||||
</select>
|
||||
<Layout stack>
|
||||
<Spacer :size="4" />
|
||||
<Link
|
||||
solid
|
||||
primary
|
||||
icon="bi-plus"
|
||||
:to="useModal('album').to"
|
||||
>
|
||||
Add Album
|
||||
<AlbumModal
|
||||
v-model="model.channel"
|
||||
@created="fetchAlbums"
|
||||
/>
|
||||
</Link>
|
||||
</Layout>
|
||||
</template>
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import type { BackendError, Channel, Upload, Track } from '~/types'
|
||||
import type { BackendError, Channel, Upload, Track, Album } from '~/types'
|
||||
import type { VueUploadItem } from 'vue-upload-component'
|
||||
|
||||
import { computed, ref, reactive, watchEffect, watch } from 'vue'
|
||||
import { computed, ref, reactive, watchEffect, watch, onMounted } from 'vue'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { humanSize } from '~/utils/filters'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
import { useModal } from '~/ui/composables/useModal.ts'
|
||||
|
||||
import axios from 'axios'
|
||||
import { type paths, type schemas, type operations, type components } from '~/generated/types.ts'
|
||||
import { type paths, type operations, type components } from '~/generated/types.ts'
|
||||
|
||||
import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
|
||||
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
|
||||
import LicenseSelect from '~/components/channels/LicenseSelect.vue'
|
||||
import AlbumSelect from '~/components/channels/AlbumSelect.vue'
|
||||
import AlbumModal from '~/components/channels/AlbumModal.vue'
|
||||
|
||||
import useErrorHandler from '~/composables/useErrorHandler'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Loader from '~/components/ui/Loader.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
|
||||
|
||||
interface Events {
|
||||
|
@ -28,7 +33,7 @@ interface Events {
|
|||
}
|
||||
|
||||
interface Props {
|
||||
channel?: Channel | null
|
||||
channel: Channel | null
|
||||
}
|
||||
|
||||
interface QuotaStatus {
|
||||
|
@ -72,29 +77,90 @@ const files = ref([] as VueUploadItem[])
|
|||
//
|
||||
// Channels
|
||||
//
|
||||
const availableChannels = reactive({
|
||||
channels: [] as Channel[],
|
||||
count: 0,
|
||||
loading: false
|
||||
})
|
||||
const availableChannels = ref<Channel[]>([])
|
||||
|
||||
/*
|
||||
availableChannels>1? :=1 :=0
|
||||
| | |
|
||||
v v v
|
||||
props select a channel | create empty channel
|
||||
| | null
|
||||
v v |
|
||||
channelDropdownId v
|
||||
|
|
||||
v
|
||||
selectedChannel
|
||||
|
|
||||
v
|
||||
as a model to Album
|
||||
|
|
||||
v
|
||||
albums
|
||||
|
||||
*/
|
||||
|
||||
// In the channel dropdown, we can select a value
|
||||
//
|
||||
|
||||
const channelDropdownId = ref<Channel['artist']['id'] | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
const selectedChannel = computed(()=>
|
||||
props.channel ? props.channel
|
||||
: availableChannels.value.length===0 ? (createEmptyChannel(), null)
|
||||
: availableChannels.value.length===1 ? availableChannels.value[0]
|
||||
: availableChannels.value.find(({artist}) => artist.id === channelDropdownId.value)
|
||||
)
|
||||
|
||||
const emptyChannelCreateRequest:components['schemas']['ChannelCreateRequest'] = {
|
||||
name: store.state.auth.fullUsername,
|
||||
username: store.state.auth.username,
|
||||
description: null,
|
||||
tags: [],
|
||||
content_category: 'music',
|
||||
}
|
||||
|
||||
const createEmptyChannel = async () => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'channels/',
|
||||
(emptyChannelCreateRequest satisfies operations['create_channel_2']['requestBody']['content']['application/json'])
|
||||
)
|
||||
console.log("Created Channel: ", response.data)
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
console.log("Error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchChannels = async () => {
|
||||
availableChannels.loading = true
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await axios.get('channels/', { params: { scope: 'me' } })
|
||||
availableChannels.channels = response.data.results
|
||||
availableChannels.count = response.data.count
|
||||
availableChannels.value = response.data.results
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
}
|
||||
|
||||
availableChannels.loading = false
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const selectedChannel = computed(() => availableChannels.channels[0])
|
||||
// Albums
|
||||
const albumSelection = ref<{channel: Channel, albumId: Album['id'] | '', albums: Album[]}>
|
||||
|
||||
watch(selectedChannel, (channel) =>
|
||||
albumSelection.value =
|
||||
{ channel: channel,
|
||||
albumId: '',
|
||||
albums: []
|
||||
}
|
||||
)
|
||||
|
||||
const channelChange = async (channelId) => {
|
||||
selectedChannel.value = channelId
|
||||
await fetchAlbums(channelId)
|
||||
}
|
||||
|
||||
//
|
||||
// Quota and space
|
||||
//
|
||||
const quotaStatus = ref()
|
||||
|
@ -341,34 +407,29 @@ const labels = computed(() => ({
|
|||
editTitle: t('components.channels.UploadForm.button.edit')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
const publish = async () => {
|
||||
console.log("starting publish...")
|
||||
isLoading.value = true
|
||||
|
||||
errors.value = []
|
||||
|
||||
|
||||
console.log("first, let's try to create an empty channel...")
|
||||
createEmptyChannel();
|
||||
|
||||
try {
|
||||
// Post list of uuids of uploadedFiles to axios action:publish
|
||||
|
||||
/* { import_status: components["schemas"]["ImportStatusEnum"];
|
||||
audio_file: string;} */
|
||||
|
||||
const theUpdate : components['schemas']['PatchedUploadForOwnerRequest'] = {
|
||||
import_status: 'pending',
|
||||
}
|
||||
// const theUpdate : components['schemas']['PatchedUploadForOwnerRequest'] = {
|
||||
// import_status: 'pending',
|
||||
// }
|
||||
|
||||
await axios.post('uploads/action/', {
|
||||
action: 'publish',
|
||||
objects: uploadedFiles.value.map((file) => file.response?.uuid)
|
||||
} satisfies paths['/api/v2/uploads/action/']['post']['requestBody']['content']['application/json'],
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${store.state.auth.oauth}` }
|
||||
})
|
||||
// await axios.post('uploads/action/', {
|
||||
// action: 'publish',
|
||||
// objects: uploadedFiles.value.map((file) => file.response?.uuid)
|
||||
// } satisfies paths['/api/v2/uploads/action/']['post']['requestBody']['content']['application/json'],
|
||||
// {
|
||||
// headers: { 'Authorization': `Bearer ${store.state.auth.oauth}` }
|
||||
// })
|
||||
|
||||
|
||||
console.log("Channels Store Before: ", store.state.channels)
|
||||
|
@ -396,10 +457,6 @@ defineExpose({
|
|||
publish
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Api Calls
|
||||
|
||||
// Create a new channel
|
||||
|
@ -460,35 +517,12 @@ ChannelCreateRequest: {
|
|||
};
|
||||
};
|
||||
*/
|
||||
|
||||
const emptyChannelCreateRequest:schemas['ChannelCreateRequest'] = {
|
||||
name: 'empty channel',
|
||||
username: store.state.auth.username,
|
||||
description: null,
|
||||
tags: [],
|
||||
content_category: 'music',
|
||||
}
|
||||
|
||||
//json
|
||||
const emptyCreate_channel_2RequestBodyContentJson:operations['create_channel_2']['requestBody']['content']['application/json'] =
|
||||
emptyChannelCreateRequest
|
||||
|
||||
//post
|
||||
const createEmptyChannel = async () => {
|
||||
try {
|
||||
const response = await axios.post('channels/', emptyCreate_channel_2RequestBodyContentJson)
|
||||
console.log("Created Channel: ", response.data)
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
console.log("Error:", error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<form
|
||||
:class="['ui', { loading: availableChannels.loading }, 'form component-file-upload']"
|
||||
:class="['ui', { loading: isLoading }, 'form component-file-upload']"
|
||||
@submit.stop.prevent
|
||||
>
|
||||
<!-- Error message -->
|
||||
|
@ -510,23 +544,30 @@ const createEmptyChannel = async () => {
|
|||
<!-- Select Album and License -->
|
||||
|
||||
<div :class="['ui', 'required', 'field']">
|
||||
<label for="channel-dropdown">
|
||||
<label v-if="availableChannels.length === 1" for="channel-dropdown">
|
||||
{{ t('components.channels.UploadForm.label.channel') }}: {{ selectedChannel?.artist.name }}
|
||||
</label>
|
||||
<label v-else for="channel-dropdown">
|
||||
{{ t('components.channels.UploadForm.label.channel') }}
|
||||
</label>
|
||||
<select
|
||||
v-if="availableChannels.count > 1"
|
||||
v-if="availableChannels.length > 1"
|
||||
v-model="channelDropdownId"
|
||||
id="channel-dropdown"
|
||||
class="dropdown"
|
||||
>
|
||||
<option v-for="channel in availableChannels.channels" :value="channel.uuid">
|
||||
<option v-for="channel in availableChannels" :value="channel.artist.id">
|
||||
{{ channel.artist.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<album-select
|
||||
v-model.number="values.album"
|
||||
:channel="selectedChannel"
|
||||
:class="['ui', 'field']"
|
||||
/>
|
||||
<Layout flex>
|
||||
<album-select
|
||||
v-if="selectedChannel"
|
||||
v-model="albumSelection"
|
||||
:class="['ui', 'field']"
|
||||
/>
|
||||
</Layout>
|
||||
<license-select
|
||||
v-model="values.license"
|
||||
:class="['ui', 'field']"
|
||||
|
|
|
@ -64,7 +64,7 @@ const channelUpload = ref();
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Modal overPopover
|
||||
<Modal
|
||||
:title="modalTitle"
|
||||
v-model="isOpen"
|
||||
>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { computed, ref, reactive, watch } from 'vue'
|
|||
import { whenever } from '@vueuse/core'
|
||||
import { useStore } from '~/store'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModal } from '~/ui/composables/useModal.ts'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -17,6 +18,7 @@ import Loader from '~/components/ui/Loader.vue'
|
|||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
|
||||
import useWebSocketHandler from '~/composables/useWebSocketHandler'
|
||||
|
||||
|
@ -153,7 +155,7 @@ const albumModal = ref()
|
|||
<channel-entries
|
||||
:key="String(episodesKey) + 'entries'"
|
||||
:is-podcast="isPodcast"
|
||||
:default-cover="object.artist?.cover"
|
||||
:default-cover="object.artist?.cover || null"
|
||||
:limit="25"
|
||||
:filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"
|
||||
>
|
||||
|
@ -191,17 +193,18 @@ const albumModal = ref()
|
|||
v-if="isOwner"
|
||||
class="actions"
|
||||
>
|
||||
<a @click.stop.prevent="albumModal.show = true">
|
||||
<Link
|
||||
:to="useModal('album').to"
|
||||
>
|
||||
<i class="bi bi-plus" />
|
||||
{{ t('views.channels.DetailOverview.link.addAlbum') }}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</h2>
|
||||
</channel-series>
|
||||
<album-modal
|
||||
v-if="isOwner"
|
||||
ref="albumModal"
|
||||
:channel="object"
|
||||
:model-value="object"
|
||||
@created="albumModal.show = false; seriesKey = new Date()"
|
||||
/>
|
||||
</section>
|
||||
|
|
Loading…
Reference in New Issue