refactor(front): [WIP] modernize upload form (for Channels)

This commit is contained in:
upsiflu 2025-02-06 18:56:10 +01:00
parent 60463d405e
commit b78d1f8992
1 changed files with 163 additions and 169 deletions

View File

@ -3,15 +3,13 @@ import type { BackendError, Channel, Upload, Track } from '~/types'
import type { VueUploadItem } from 'vue-upload-component' import type { VueUploadItem } from 'vue-upload-component'
import { computed, ref, reactive, watchEffect, watch } from 'vue' import { computed, ref, reactive, watchEffect, watch } from 'vue'
import { whenever, useCurrentElement } from '@vueuse/core' import { whenever } from '@vueuse/core'
import { humanSize } from '~/utils/filters' import { humanSize } from '~/utils/filters'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery'
import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue' import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
import FileUploadWidget from '~/components/library/FileUploadWidget.vue' import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
import LicenseSelect from '~/components/channels/LicenseSelect.vue' import LicenseSelect from '~/components/channels/LicenseSelect.vue'
@ -21,7 +19,6 @@ import useErrorHandler from '~/composables/useErrorHandler'
interface Events { interface Events {
(e: 'status', status: UploadStatus): void (e: 'status', status: UploadStatus): void
(e: 'step', step: 1 | 2 | 3): void
} }
interface Props { interface Props {
@ -238,7 +235,7 @@ const fetchAudioMetadata = async (uuid: string) => {
for (const key of ['title', 'position', 'tags'] as const) { for (const key of ['title', 'position', 'tags'] as const) {
if (uploadImportData[uuid][key] === undefined) { if (uploadImportData[uuid][key] === undefined) {
uploadImportData[uuid][key] = response.data[key] as never // uploadImportData[uuid][key] = response.data[key] as never
} }
} }
@ -302,27 +299,7 @@ const retry = async (file: VueUploadItem) => {
fetchChannels() fetchChannels()
fetchQuota() fetchQuota()
// watch(selectedUploadId, async (_, from) => {
// Step
//
const step = ref<1 | 2 | 3>(1)
watchEffect(() => {
emit('step', step.value)
if (step.value === 2) {
selectedUploadId.value = null
}
})
watch(selectedUploadId, async (to, from) => {
if (to) {
step.value = 3
}
if (!to && step.value !== 2) {
step.value = 2
}
if (from) { if (from) {
await patchUpload(from, { import_metadata: uploadImportData[from] }) await patchUpload(from, { import_metadata: uploadImportData[from] })
} }
@ -360,29 +337,42 @@ const labels = computed(() => ({
const isLoading = ref(false) const isLoading = ref(false)
const publish = async () => { const publish = async () => {
console.log("starting publish...")
isLoading.value = true isLoading.value = true
errors.value = [] errors.value = []
try { try {
await axios.post('uploads/action/', { // Post list of uuids of uploadedFiles to axios action:publish
await axios.put('uploads/action/', {
action: 'publish', action: 'publish',
objects: uploadedFiles.value.map((file) => file.response?.uuid) objects: uploadedFiles.value.map((file) => file.response?.uuid)
}) })
console.log("starting posting to axios action:publish...")
console.log("Channels Store Before: ", store.state.channels)
// Tell the store that the uploaded files are pending import
store.commit('channels/publish', { store.commit('channels/publish', {
uploads: uploadedFiles.value.map((file) => ({ ...file.response, import_status: 'pending' })), uploads: uploadedFiles.value.map((file) => ({ ...file.response, import_status: 'pending' })),
channel: selectedChannel.value channel: selectedChannel.value
}) })
console.log("Channels Store After: ", store.state.channels)
} catch (error) { } catch (error) {
// TODO: Use inferred error type instead of typecasting
errors.value = (error as BackendError).backendErrors errors.value = (error as BackendError).backendErrors
console.log("Error:", error)
} }
isLoading.value = false isLoading.value = false
console.log("...finished publish")
} }
defineExpose({ defineExpose({
step,
publish publish
}) })
</script> </script>
@ -392,6 +382,8 @@ defineExpose({
:class="['ui', { loading: availableChannels.loading }, 'form component-file-upload']" :class="['ui', { loading: availableChannels.loading }, 'form component-file-upload']"
@submit.stop.prevent @submit.stop.prevent
> >
<!-- Error message -->
<div <div
v-if="errors.length > 0" v-if="errors.length > 0"
role="alert" role="alert"
@ -409,7 +401,10 @@ defineExpose({
</li> </li>
</ul> </ul>
</div> </div>
<div :class="['ui', 'required', {hidden: step > 1}, 'field']">
<!-- Select Album and License -->
<div :class="['ui', 'required', 'field']">
<label for="channel-dropdown"> <label for="channel-dropdown">
{{ t('components.channels.UploadForm.label.channel') }}: {{ selectedChannel?.artist.name }} {{ t('components.channels.UploadForm.label.channel') }}: {{ selectedChannel?.artist.name }}
</label> </label>
@ -417,13 +412,13 @@ defineExpose({
<album-select <album-select
v-model.number="values.album" v-model.number="values.album"
:channel="selectedChannel" :channel="selectedChannel"
:class="['ui', {hidden: step > 1}, 'field']" :class="['ui', 'field']"
/> />
<license-select <license-select
v-model="values.license" v-model="values.license"
:class="['ui', {hidden: step > 1}, 'field']" :class="['ui', 'field']"
/> />
<div :class="['ui', {hidden: step > 1}, 'message']"> <div :class="['ui', 'message']">
<div class="content"> <div class="content">
<p> <p>
<i class="copyright icon" /> <i class="copyright icon" />
@ -431,152 +426,151 @@ defineExpose({
</p> </p>
</div> </div>
</div> </div>
<template v-if="step === 2 || step === 3">
<!-- Files to upload -->
<div
v-if="remainingSpace === 0"
role="alert"
class="ui warning message"
>
<div class="content">
<p>
<i class="warning icon" />
{{ t('components.channels.UploadForm.warning.quota') }}
</p>
</div>
</div>
<template v-else>
<div <div
v-if="remainingSpace === 0" v-if="draftUploads?.length > 0 && includeDraftUploads === undefined"
role="alert" class="ui visible info message"
class="ui warning message" >
<p>
<i class="redo icon" />
{{ t('components.channels.UploadForm.message.pending') }}
</p>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = false"
>
{{ t('components.channels.UploadForm.button.ignore') }}
</button>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = true"
>
{{ t('components.channels.UploadForm.button.resume') }}
</button>
</div>
<div
v-if="uploadedFiles.length > 0"
>
<div
v-for="file in uploadedFiles"
:key="file.id"
class="channel-file"
>
<div class="content">
<div
v-if="file.response?.uuid"
role="button"
class="ui basic icon button"
:title="labels.editTitle"
@click.stop.prevent="selectedUploadId = file.response?.uuid"
>
<i class="pencil icon" />
</div>
<div
v-if="file.error"
class="ui basic danger icon label"
:title="file.error.toString()"
@click.stop.prevent="selectedUploadId = file.response?.uuid"
>
<i class="warning sign icon" />
</div>
<div
v-else-if="file.active && !file.response"
class="ui active slow inline loader"
/>
</div>
<h4 class="ui header">
<template v-if="file.metadata.title">
{{ file.metadata.title }}
</template>
<template v-else>
{{ file.name }}
</template>
<div class="sub header">
<template v-if="file.response?.uuid">
{{ humanSize(file.size ?? 0) }}
<template v-if="file.response.duration">
<span class="middle middledot symbol" />
<human-duration :duration="file.response.duration" />
</template>
</template>
<template v-else>
<span v-if="file.active">
{{ t('components.channels.UploadForm.status.uploading') }}
</span>
<span v-else-if="file.error">
{{ t('components.channels.UploadForm.status.errored') }}
</span>
<span v-else>
{{ t('components.channels.UploadForm.status.pending') }}
</span>
<span class="middle middledot symbol" />
{{ humanSize(file.size ?? 0) }}
<span class="middle middledot symbol" />
{{ parseFloat(file.progress ?? '0') }}
<span class="percent symbol" />
</template>
<span class="middle middledot symbol" />
<a @click.stop.prevent="remove(file)">
{{ t('components.channels.UploadForm.button.remove') }}
</a>
<template v-if="file.error">
<span class="middle middledot symbol" />
<a @click.stop.prevent="retry(file)">
{{ t('components.channels.UploadForm.button.retry') }}
</a>
</template>
</div>
</h4>
</div>
</div>
<upload-metadata-form
v-if="selectedUpload"
v-model:values="uploadImportData[selectedUploadId]"
:upload="selectedUpload"
/>
<div
class="ui message"
> >
<div class="content"> <div class="content">
<p> <p>
<i class="warning icon" /> <i class="info icon" />
{{ t('components.channels.UploadForm.warning.quota') }} {{ t('components.channels.UploadForm.description.extensions', {extensions: store.state.ui.supportedExtensions.join(', ')}) }}
</p> </p>
</div> </div>
</div> </div>
<template v-else> <file-upload-widget
<div ref="upload"
v-if="step === 2 && draftUploads?.length > 0 && includeDraftUploads === undefined" v-model="files"
class="ui visible info message" :class="['ui', 'icon', 'basic', 'button', 'channels']"
> :data="baseImportMetadata"
<p> @input-file="beforeFileUpload"
<i class="redo icon" /> >
{{ t('components.channels.UploadForm.message.pending') }} <div>
</p> <i class="upload icon" />&nbsp;
<button {{ t('components.channels.UploadForm.message.dragAndDrop') }}
class="ui basic button"
@click.stop.prevent="includeDraftUploads = false"
>
{{ t('components.channels.UploadForm.button.ignore') }}
</button>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = true"
>
{{ t('components.channels.UploadForm.button.resume') }}
</button>
</div> </div>
<div <div class="ui very small divider" />
v-if="uploadedFiles.length > 0" <div>
:class="[{hidden: step === 3}]" {{ t('components.channels.UploadForm.label.openBrowser') }}
>
<div
v-for="file in uploadedFiles"
:key="file.id"
class="channel-file"
>
<div class="content">
<div
v-if="file.response?.uuid"
role="button"
class="ui basic icon button"
:title="labels.editTitle"
@click.stop.prevent="selectedUploadId = file.response?.uuid"
>
<i class="pencil icon" />
</div>
<div
v-if="file.error"
class="ui basic danger icon label"
:title="file.error.toString()"
@click.stop.prevent="selectedUploadId = file.response?.uuid"
>
<i class="warning sign icon" />
</div>
<div
v-else-if="file.active && !file.response"
class="ui active slow inline loader"
/>
</div>
<h4 class="ui header">
<template v-if="file.metadata.title">
{{ file.metadata.title }}
</template>
<template v-else>
{{ file.name }}
</template>
<div class="sub header">
<template v-if="file.response?.uuid">
{{ humanSize(file.size ?? 0) }}
<template v-if="file.response.duration">
<span class="middle middledot symbol" />
<human-duration :duration="file.response.duration" />
</template>
</template>
<template v-else>
<span v-if="file.active">
{{ t('components.channels.UploadForm.status.uploading') }}
</span>
<span v-else-if="file.error">
{{ t('components.channels.UploadForm.status.errored') }}
</span>
<span v-else>
{{ t('components.channels.UploadForm.status.pending') }}
</span>
<span class="middle middledot symbol" />
{{ humanSize(file.size ?? 0) }}
<span class="middle middledot symbol" />
{{ parseFloat(file.progress ?? '0') }}
<span class="percent symbol" />
</template>
<span class="middle middledot symbol" />
<a @click.stop.prevent="remove(file)">
{{ t('components.channels.UploadForm.button.remove') }}
</a>
<template v-if="file.error">
<span class="middle middledot symbol" />
<a @click.stop.prevent="retry(file)">
{{ t('components.channels.UploadForm.button.retry') }}
</a>
</template>
</div>
</h4>
</div>
</div> </div>
<upload-metadata-form </file-upload-widget>
v-if="selectedUpload" <div class="ui hidden divider" />
v-model:values="uploadImportData[selectedUploadId]"
:upload="selectedUpload"
/>
<div
v-if="step === 2"
class="ui message"
>
<div class="content">
<p>
<i class="info icon" />
{{ t('components.channels.UploadForm.description.extensions', {extensions: store.state.ui.supportedExtensions.join(', ')}) }}
</p>
</div>
</div>
<file-upload-widget
ref="upload"
v-model="files"
:class="['ui', 'icon', 'basic', 'button', 'channels', {hidden: step === 3}]"
:data="baseImportMetadata"
@input-file="beforeFileUpload"
>
<div>
<i class="upload icon" />&nbsp;
{{ t('components.channels.UploadForm.message.dragAndDrop') }}
</div>
<div class="ui very small divider" />
<div>
{{ t('components.channels.UploadForm.label.openBrowser') }}
</div>
</file-upload-widget>
<div class="ui hidden divider" />
</template>
</template> </template>
</form> </form>
</template> </template>