From 8096f34ed7c3dfdee15c2eaa138316a7540a8873 Mon Sep 17 00:00:00 2001 From: wvffle Date: Fri, 15 Mar 2024 15:12:28 +0000 Subject: [PATCH] feat(ui): connect upload page to real backend --- front/src/ui/pages/upload/history.vue | 5 ++ front/src/ui/pages/upload/index.vue | 67 ++++++++++++++++++++++++++- front/src/ui/stores/upload.ts | 54 +++++++++++++++------ 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/front/src/ui/pages/upload/history.vue b/front/src/ui/pages/upload/history.vue index ca2356987..9459973e1 100644 --- a/front/src/ui/pages/upload/history.vue +++ b/front/src/ui/pages/upload/history.vue @@ -1,12 +1,17 @@ diff --git a/front/src/ui/pages/upload/index.vue b/front/src/ui/pages/upload/index.vue index ef6b4e9b8..710c9389c 100644 --- a/front/src/ui/pages/upload/index.vue +++ b/front/src/ui/pages/upload/index.vue @@ -2,6 +2,8 @@ import { Icon } from '@iconify/vue' import { useUploadsStore, type UploadGroupType } from '~/ui/stores/upload' import { ref } from 'vue' +import axios from 'axios' +import { useAsyncState } from '@vueuse/core' interface Tab { label: string @@ -35,8 +37,15 @@ const currentTab = ref(tabs[0]) const uploads = useUploadsStore() const openLibrary = () => { - uploads.createUploadGroup(currentTab.value.key) + uploads.createUploadGroup(currentTab.value.key, target.value?.uuid) } + +const target = ref() +const { state: items } = useAsyncState( + axios.get('/libraries/?scope=me') + .then(t => t.data.results), + [] +) diff --git a/front/src/ui/stores/upload.ts b/front/src/ui/stores/upload.ts index 135a5c4ab..69afa45af 100644 --- a/front/src/ui/stores/upload.ts +++ b/front/src/ui/stores/upload.ts @@ -9,14 +9,16 @@ import type { MetadataParsingResult } from '~/ui/workers/file-metadata-parser' import type { Tags } from '~/ui/composables/metadata' import useLogger from '~/composables/useLogger' +import useWebSocketHandler from '~/composables/useWebSocketHandler' export type UploadGroupType = 'music-library' | 'music-channel' | 'podcast-channel' -export type FailReason = 'missing-tags' | 'upload-failed' | 'upload-cancelled' +export type FailReason = 'missing-tags' | 'upload-failed' | 'upload-cancelled' | 'import-failed' export class UploadGroupEntry { id = nanoid() abortController = new AbortController() progress = 0 + guid?: string error?: Error failReason?: FailReason @@ -32,26 +34,34 @@ export class UploadGroupEntry { } async upload () { + if (!this.metadata) return + const body = new FormData() - body.append('file', this.file) + body.append('metadata', JSON.stringify({ + title: this.metadata.tags.title, + album: { name: this.metadata.tags.album }, + artist: { name: this.metadata.tags.artist }, + })) + + body.append('target', JSON.stringify({ + library: this.uploadGroup.targetGUID + })) + + body.append('audioFile', this.file) const logger = useLogger() - await axios.post(this.uploadGroup.uploadUrl, body, { + const { data } = await axios.post(this.uploadGroup.uploadUrl, body, { headers: { 'Content-Type': 'multipart/form-data' }, signal: this.abortController.signal, onUploadProgress: (e) => { // NOTE: If e.total is absent, we use the file size instead. This is only an approximation, as e.total is the total size of the request, not just the file. // see: https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/total this.progress = Math.floor(e.loaded / (e.total ?? this.file.size) * 100) - - if (this.progress === 100) { - logger.info(`[${this.id}] upload complete!`) - } } }) - logger.info(`[${this.id}] import complete!`) - this.importedAt = new Date() + logger.info(`[${this.id}] upload complete!`) + this.guid = data.guid } fail (reason: FailReason, error: Error) { @@ -82,7 +92,7 @@ export class UploadGroupEntry { } export class UploadGroup { - static entries = Object.create(null) + static entries = reactive(Object.create(null)) queue: UploadGroupEntry[] = [] createdAt = new Date() @@ -90,6 +100,7 @@ export class UploadGroup { constructor ( public guid: string, public type: UploadGroupType, + public targetGUID: string, public uploadUrl: string ) { } @@ -174,18 +185,33 @@ whenever(workerMetadata, (reactiveData) => { export const useUploadsStore = defineStore('uploads', () => { const logger = useLogger() - const createUploadGroup = async (type: UploadGroupType) => { - // TODO: API call - const uploadGroup = new UploadGroup('guid:' + nanoid(), type, 'https://httpbin.org/post') + useWebSocketHandler('import.status_updated', (event) => { + for (const group of uploadGroups) { + const upload = group.queue.find(entry => entry.guid === event.upload.uuid) + if (!upload) continue + + if (event.new_status !== 'failed') { + upload.importedAt = event.upload.import_date + } else { + upload.fail('import-failed') + } + break + } + }) + + const createUploadGroup = async (type: UploadGroupType, targetGUID: string) => { + const { data } = await axios.post('/api/v2/upload-groups', { baseUrl: '/' }) + const uploadGroup = new UploadGroup(data.guid, type, targetGUID, data.uploadUrl) uploadGroups.push(uploadGroup) currentUploadGroup.value = uploadGroup } const currentUpload = computed(() => uploadQueue[currentIndex.value]) const isUploading = computed(() => !!currentUpload.value) + const currentUploadWithMetadata = computed(() => currentUpload.value?.metadata ? currentUpload.value : undefined) // Upload the file whenever it is available - whenever(currentUpload, (entry) => entry.upload().catch((error) => { + whenever(currentUploadWithMetadata, (entry) => entry.upload().catch((error) => { // The tags were missing, so we have cancelled the upload if (error.code === 'ERR_CANCELED') { return