feat: add missing upload views

merge-requests/2666/merge^2
Kasper Seweryn 2024-01-21 14:28:06 +01:00 zatwierdzone przez Georg Krause
rodzic f69e08b2a9
commit 443a535758
5 zmienionych plików z 268 dodań i 192 usunięć

Wyświetl plik

@ -0,0 +1,183 @@
<script setup lang="ts">
import { ref } from 'vue';
import { UploadGroup } from '~/ui/stores/upload'
import VerticalCollapse from '~/ui/components/VerticalCollapse.vue'
import UploadList from '~/ui/components/UploadList.vue'
import { UseTimeAgo } from '@vueuse/components'
import { Icon } from '@iconify/vue'
defineProps<{ groups: UploadGroup[], isUploading?: boolean }>()
const openUploadGroup = ref<UploadGroup>()
const toggle = (group: UploadGroup) => {
openUploadGroup.value = openUploadGroup.value === group
? undefined
: group
}
const labels = {
'music-library': 'Music library',
'music-channel': 'Music channel',
'podcast-channel': 'Podcast channel',
}
const getDescription = (group: UploadGroup) => {
if (group.queue.length === 0) return 'Unknown album'
return group.queue.reduce((acc, { metadata }) => {
if (!metadata) return acc
let element = group.type === 'music-library'
? metadata.tags.album
: metadata.tags.title
element = acc.length < 3
? element
: '...'
if (!acc.includes(element)) {
acc.push(element)
}
return acc
}, [] as string[]).join(', ')
}
</script>
<template>
<div>
<div
class="upload-group"
v-for="group of groups"
:key="group.guid"
>
<div class="flex items-center">
<div class="upload-group-header">
<div class="upload-group-title">{{ labels[group.type] }}</div>
<div class="upload-group-albums">{{ getDescription(group) }}</div>
</div>
<div class="timeago">
<UseTimeAgo :time="group.createdAt" v-slot="{ timeAgo }">{{ timeAgo }}</UseTimeAgo>
</div>
<FwPill v-if="group.failedCount > 0" color="red">
<template #image>
<div class="flex items-center justify-center">{{ group.failedCount }}</div>
</template>
failed
</FwPill>
<FwPill v-if="group.importedCount > 0" color="blue">
<template #image>
<div class="flex items-center justify-center">{{ group.importedCount }}</div>
</template>
imported
</FwPill>
<FwPill v-if="group.processingCount > 0" color="secondary">
<template #image>
<div class="flex items-center justify-center">{{ group.processingCount }}</div>
</template>
processing
</FwPill>
<FwButton
@click="toggle(group)"
variant="ghost"
color="secondary"
class="icon-only"
>
<template #icon>
<Icon icon="bi:chevron-right" :rotate="group === openUploadGroup ? 1 : 0" />
</template>
</FwButton>
</div>
<div v-if="isUploading" class="flex items-center upload-progress">
<FwButton v-if="group.processingCount === 0 && group.failedCount > 0" @click="group.retry()" color="secondary">Retry</FwButton>
<FwButton v-else-if="group.queue.length !== group.importedCount" @click="group.cancel()" color="secondary">Interrupt</FwButton>
<div class="progress">
<div class="progress-bar" :style="{ width: `${group.progress}%` }" />
</div>
<div class="shrink-0">
{{ group.importedCount }} / {{ group.queue.length }} files imported
</div>
</div>
<VerticalCollapse @click.stop :open="openUploadGroup === group" class="collapse">
<UploadList :uploads="group.queue" />
</VerticalCollapse>
</div>
</div>
</template>
<style scoped lang="scss">
.upload-group {
&:not(:first-child) {
border-top: 1px solid var(--fw-gray-200);
padding-top: 1rem;
}
.upload-group-header {
.upload-group-title {
color: var(--fw-gray-960);
font-size: 0.9375rem;
}
.upload-group-albums {
color: var(--fw-gray-960);
font-size: 0.875rem;
}
}
}
.timeago {
margin-left: auto;
margin-right: 1rem;
font-size: 0.875rem;
color: var(--fw-gray-600);
}
.upload-progress {
font-size: 0.875rem;
color: var(--fw-gray-600);
padding-top: 0.5rem;
padding-bottom: 1rem;
> :deep(.funkwhale.button) {
margin: 0rem;
}
> :deep(.funkwhale.button) + .progress {
margin-left: 1rem;
}
.progress {
width: 100%;
height: 0.5rem;
background-color: var(--fw-gray-200);
border-radius: 1rem;
margin: 0 1rem 0 0;
position: relative;
.progress-bar {
height: 100%;
background-color: var(--fw-primary);
border-radius: 1rem;
width: 0;
transition: width 0.2s ease;
}
}
}
.collapse {
padding-bottom: 1rem;
}
</style>

Wyświetl plik

@ -3,6 +3,7 @@ import type { UploadGroupEntry } from '~/ui/stores/upload'
import { bytesToHumanSize } from '~/ui/composables/bytes'
import { UseTimeAgo } from '@vueuse/components'
import CoverArt from '~/ui/components/CoverArt.vue'
import { Icon } from '@iconify/vue'
defineProps<{
uploads: UploadGroupEntry[]
@ -25,15 +26,22 @@ defineProps<{
</div>
</Transition>
<div class="upload-state">
<FwPill :color="track.failReason ? 'red' : track.importedAt ? 'blue' : 'secondary'">
<FwTooltip v-if="track.failReason" :tooltip="track.failReason">
<FwPill color="red">
<template #image>
<Icon icon="bi:question" class="h-4 w-4" />
</template>
failed
</FwPill>
</FwTooltip>
<FwPill v-else :color="track.importedAt ? 'blue' : 'secondary'">
{{
track.failReason
? 'failed'
: track.importedAt
? 'imported'
: track.progress === 100
? 'processing'
: 'uploading'
track.importedAt
? 'imported'
: track.progress === 100
? 'processing'
: 'uploading'
}}
</FwPill>
<div v-if="track.importedAt" class="track-timeago">

Wyświetl plik

@ -1,13 +1,50 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import type { UploadGroupEntry } from '~/ui/stores/upload';
import { computed } from 'vue';
import { bytesToHumanSize } from '~/ui/composables/bytes';
import { useUploadsStore, type UploadGroupEntry } from '~/ui/stores/upload';
import CoverArt from '~/ui/components/CoverArt.vue'
const allTracks: UploadGroupEntry[] = [
]
interface Recording {
guid: string
title: string
artist: string
album: string
uploadDate: Date
format: string
size: string
metadata: UploadGroupEntry['metadata']
}
const intl = new Intl.DateTimeFormat('en', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
// TODO: Fetch tracks from server
const uploads = useUploadsStore()
const allTracks = computed<Recording[]>(() => {
return uploads.uploadGroups.flatMap(group => group.queue.map<Recording>((entry) => ({
guid: entry.id,
title: entry.metadata?.tags.title || 'Unknown title',
artist: entry.metadata?.tags.artist || 'Unknown artist',
album: entry.metadata?.tags.album || 'Unknown album',
uploadDate: group.createdAt,
format: 'flac',
size: bytesToHumanSize(entry.file.size),
metadata: entry.metadata
})))
})
const columns = [
{ key: '', label: '' }
{ key: '>index', label: '#' },
{ key: 'title', label: 'Title' },
{ key: 'artist', label: 'Artist' },
{ key: 'album', label: 'Album' },
{ key: 'uploadDate', label: 'Upload date' },
{ key: 'format', label: 'Format' },
{ key: 'size', label: 'Size' }
]
</script>
@ -18,8 +55,20 @@ const columns = [
<h3>There is no file in your library</h3>
<p>Try uploading some before coming back here!</p>
</div>
<FwTable :rows="allTracks">
<FwTable v-else
id-key="guid"
:columns="columns"
:rows="allTracks"
>
<template #col-title="{ row, value }">
<div class="flex items-center">
<CoverArt :src="row.metadata" class="mr-2" />
{{ value }}
</div>
</template>
<template #col-upload-date="{ value }">
{{ intl.format(value) }}
</template>
</FwTable>
</template>

Wyświetl plik

@ -1,3 +1,12 @@
<script setup lang="ts">
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
</script>
<template>
history
<UploadGroupList :groups="history" />
</template>

Wyświetl plik

@ -1,182 +1,9 @@
<script setup lang="ts">
import { ref } from 'vue';
import { UploadGroup, useUploadsStore } from '~/ui/stores/upload'
import VerticalCollapse from '~/ui/components/VerticalCollapse.vue'
import UploadList from '~/ui/components/UploadList.vue'
import { UseTimeAgo } from '@vueuse/components'
import { Icon } from '@iconify/vue'
import UploadGroupList from '~/ui/components/UploadGroupList.vue'
import { useUploadsStore } from '~/ui/stores/upload'
const uploads = useUploadsStore()
const openUploadGroup = ref<UploadGroup>()
const toggle = (group: UploadGroup) => {
openUploadGroup.value = openUploadGroup.value === group
? undefined
: group
}
const labels = {
'music-library': 'Music library',
'music-channel': 'Music channel',
'podcast-channel': 'Podcast channel',
}
const getDescription = (group: UploadGroup) => {
if (group.queue.length === 0) return 'Unknown album'
return group.queue.reduce((acc, { metadata }) => {
if (!metadata) return acc
let element = group.type === 'music-library'
? metadata.tags.album
: metadata.tags.title
element = acc.length < 3
? element
: '...'
if (!acc.includes(element)) {
acc.push(element)
}
return acc
}, [] as string[]).join(', ')
}
</script>
<template>
<div>
<div
class="upload-group"
v-for="group of uploads.uploadGroups"
:key="group.guid"
>
<div class="flex items-center">
<div class="upload-group-header">
<div class="upload-group-title">{{ labels[group.type] }}</div>
<div class="upload-group-albums">{{ getDescription(group) }}</div>
</div>
<div class="timeago">
<UseTimeAgo :time="group.createdAt" v-slot="{ timeAgo }">{{ timeAgo }}</UseTimeAgo>
</div>
<FwPill v-if="group.failedCount > 0" color="red">
<template #image>
<div class="flex items-center justify-center">{{ group.failedCount }}</div>
</template>
failed
</FwPill>
<FwPill v-if="group.importedCount > 0" color="blue">
<template #image>
<div class="flex items-center justify-center">{{ group.importedCount }}</div>
</template>
imported
</FwPill>
<FwPill v-if="group.processingCount > 0" color="secondary">
<template #image>
<div class="flex items-center justify-center">{{ group.processingCount }}</div>
</template>
processing
</FwPill>
<FwButton
@click="toggle(group)"
variant="ghost"
color="secondary"
class="icon-only"
>
<template #icon>
<Icon icon="bi:chevron-right" :rotate="group === openUploadGroup ? 1 : 0" />
</template>
</FwButton>
</div>
<div class="flex items-center upload-progress">
<FwButton v-if="group.processingCount === 0 && group.failedCount > 0" @click="group.retry()" color="secondary">Retry</FwButton>
<FwButton v-else-if="group.queue.length !== group.importedCount" @click="group.cancel()" color="secondary">Interrupt</FwButton>
<div class="progress">
<div class="progress-bar" :style="{ width: `${group.progress}%` }" />
</div>
<div class="shrink-0">
{{ group.importedCount }} / {{ group.queue.length }} files imported
</div>
</div>
<VerticalCollapse @click.stop :open="openUploadGroup === group" class="collapse">
<UploadList :uploads="group.queue" />
</VerticalCollapse>
</div>
</div>
<UploadGroupList :groups="uploads.uploadGroups" :is-uploading="true" />
</template>
<style scoped lang="scss">
.upload-group {
&:not(:first-child) {
border-top: 1px solid var(--fw-gray-200);
padding-top: 1rem;
}
.upload-group-header {
.upload-group-title {
color: var(--fw-gray-960);
font-size: 0.9375rem;
}
.upload-group-albums {
color: var(--fw-gray-960);
font-size: 0.875rem;
}
}
}
.timeago {
margin-left: auto;
margin-right: 1rem;
font-size: 0.875rem;
color: var(--fw-gray-600);
}
.upload-progress {
font-size: 0.875rem;
color: var(--fw-gray-600);
padding-top: 0.5rem;
padding-bottom: 1rem;
> :deep(.funkwhale.button) {
margin: 0rem;
}
> :deep(.funkwhale.button) + .progress {
margin-left: 1rem;
}
.progress {
width: 100%;
height: 0.5rem;
background-color: var(--fw-gray-200);
border-radius: 1rem;
margin: 0 1rem 0 0;
position: relative;
.progress-bar {
height: 100%;
background-color: var(--fw-primary);
border-radius: 1rem;
width: 0;
transition: width 0.2s ease;
}
}
}
.collapse {
padding-bottom: 1rem;
}
</style>