feat(ui): connect upload page to real backend

wvffle/new-upload-process
wvffle 2024-03-15 15:12:28 +00:00
rodzic c9d22336b1
commit bd30858fb3
3 zmienionych plików z 110 dodań i 16 usunięć

Wyświetl plik

@ -1,12 +1,17 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core';
import axios from 'axios';
import UploadGroupList from '~/ui/components/UploadGroupList.vue'
import { useUploadsStore } from '~/ui/stores/upload'
// TODO: Fetch upload history from server
const uploads = useUploadsStore()
const history = uploads.uploadGroups
const { state: data } = useAsyncState(axios.post('/api/v2/upload-groups', { baseUrl: '/' }).then(t => t.data), [])
</script>
<template>
{{ data }}
<UploadGroupList :groups="history" />
</template>

Wyświetl plik

@ -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),
[]
)
</script>
<template>
@ -61,13 +70,34 @@ const openLibrary = () => {
</FwCard>
</div>
<FwButton @click="openLibrary">
<FwSelect :items="items" v-model="target" id-key="uuid">
<template #item="{ item }">
<div class="library-item">
<div class="box" />
<div>
<div>{{ item.name }}</div>
<div>
Shared with <fw-pill color="blue">{{ item.privacy_level }}</fw-pill>
<div>{{ item.uploads_count }} uploads</div>
</div>
</div>
</div>
</template>
</FwSelect>
<FwButton :disabled="!target" @click="openLibrary">
Open library
</FwButton>
</div>
</template>
<style scoped lang="scss">
:deep(.funkwhale.select) {
margin-bottom: 1rem;
}
.funkwhale.card {
--fw-card-width: 12.5rem;
--fw-border-radius: 1rem;
@ -124,4 +154,37 @@ const openLibrary = () => {
.upload > .funkwhale.button {
margin-left: 0;
}
.library-item {
width: 100%;
display: flex;
align-items: center;
> .box {
width: 2.75rem;
height: 2.75rem;
flex-shrink: 0;
background: var(--fw-pastel-blue-1);
border-radius: 8px;
margin-right:8px;
+ div {
width: 100%;
> :last-child {
display: flex;
width: 100%;
> div {
margin-left: auto;
}
}
}
}
.selected {
font-size: 1rem;
}
}
</style>

Wyświetl plik

@ -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