feat(front): #2081 new upload process

This commit is contained in:
ArneBo 2025-02-11 10:31:42 +01:00
parent 0caee2181d
commit 3cd7548cf0
6 changed files with 603 additions and 619 deletions

View File

@ -18,11 +18,9 @@ import AlbumSelect from '~/components/channels/AlbumSelect.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue'
import Loader from '~/components/ui/Loader.vue'
interface Events { interface Events {
@ -485,20 +483,17 @@ const createEmptyChannel = async () => {
console.log("Error:", error) console.log("Error:", error)
} }
} }
</script> </script>
<template> <template>
<form <form
: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 --> <!-- Error message -->
<Alert
<div
v-if="errors.length > 0" v-if="errors.length > 0"
role="alert"
class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
{{ t('components.channels.UploadForm.header.error') }} {{ t('components.channels.UploadForm.header.error') }}
@ -511,14 +506,21 @@ const createEmptyChannel = async () => {
{{ error }} {{ error }}
</li> </li>
</ul> </ul>
</div> </Alert>
<!-- Select Album and License --> <!-- Select Album and License -->
<div :class="['ui', 'required', 'field']"> <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>
<select
v-if="availableChannels.count >= 1"
id="channel-dropdown"
>
<option v-for="channel in availableChannels.channels" :value="channel.uuid">
{{ channel.artist.name }}
</option>
</select>
</div> </div>
<album-select <album-select
v-model.number="values.album" v-model.number="values.album"
@ -539,43 +541,37 @@ const createEmptyChannel = async () => {
</div> </div>
<!-- Files to upload --> <!-- Files to upload -->
<template v-if="remainingSpace === 0">
<div <Alert
v-if="remainingSpace === 0" red
role="alert"
class="ui warning message"
> >
<div class="content"> <i class="bi bi-exclamation-triangle" />
<p>
<i class="warning icon" />
{{ t('components.channels.UploadForm.warning.quota') }} {{ t('components.channels.UploadForm.warning.quota') }}
</p> </Alert>
</div> </template>
</div>
<template v-else> <template v-else>
<div <Alert
v-if="draftUploads?.length > 0 && includeDraftUploads === undefined" v-if="draftUploads?.length > 0 && includeDraftUploads === undefined"
class="ui visible info message" blue
> >
<p> <p>
<i class="redo icon" /> <i class="bi bi-circle-clockwise" />
{{ t('components.channels.UploadForm.message.pending') }} {{ t('components.channels.UploadForm.message.pending') }}
</p> </p>
<button <Button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = false" @click.stop.prevent="includeDraftUploads = false"
> >
{{ t('components.channels.UploadForm.button.ignore') }} {{ t('components.channels.UploadForm.button.ignore') }}
</button> </Button>
<button <Button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = true" @click.stop.prevent="includeDraftUploads = true"
> >
{{ t('components.channels.UploadForm.button.resume') }} {{ t('components.channels.UploadForm.button.resume') }}
</button> </Button>
</div> </Alert>
<div <Alert
v-if="uploadedFiles.length > 0" v-if="uploadedFiles.length > 0"
v-bind="{[ uploadedFiles.some(file=>file.error) ? 'red' : 'green' ]:true}"
> >
<div <div
v-for="file in uploadedFiles" v-for="file in uploadedFiles"
@ -583,27 +579,22 @@ const createEmptyChannel = async () => {
class="channel-file" class="channel-file"
> >
<div class="content"> <div class="content">
<div <Button
v-if="file.response?.uuid" v-if="file.response?.uuid"
role="button" icon="bi-pencil-fill"
class="ui basic icon button" class="ui basic icon button"
:title="labels.editTitle" :title="labels.editTitle"
@click.stop.prevent="selectedUploadId = file.response?.uuid" @click.stop.prevent="selectedUploadId = file.response?.uuid"
> />
<i class="pencil icon" />
</div>
<div <div
v-if="file.error" v-if="file.error"
class="ui basic danger icon label" class="ui basic danger icon label"
:title="file.error.toString()" :title="file.error.toString()"
@click.stop.prevent="selectedUploadId = file.response?.uuid" @click.stop.prevent="selectedUploadId = file.response?.uuid"
> >
<i class="warning sign icon" /> <i class="bi bi-exclamation-triangle-fill" />
</div> </div>
<div <Loader v-else-if="file.active && !file.response" />
v-else-if="file.active && !file.response"
class="ui active slow inline loader"
/>
</div> </div>
<h4 class="ui header"> <h4 class="ui header">
<template v-if="file.metadata.title"> <template v-if="file.metadata.title">
@ -649,7 +640,8 @@ const createEmptyChannel = async () => {
</div> </div>
</h4> </h4>
</div> </div>
</div> </Alert>
</template>
<upload-metadata-form <upload-metadata-form
v-if="selectedUpload" v-if="selectedUpload"
v-model:values="uploadImportData[selectedUploadId]" v-model:values="uploadImportData[selectedUploadId]"
@ -668,12 +660,11 @@ const createEmptyChannel = async () => {
<file-upload-widget <file-upload-widget
ref="upload" ref="upload"
v-model="files" v-model="files"
:class="['ui', 'icon', 'basic', 'button', 'channels']"
:data="baseImportMetadata" :data="baseImportMetadata"
@input-file="beforeFileUpload" @input-file="beforeFileUpload"
> >
<div> <div>
<i class="upload icon" />&nbsp; <i class="bi bi-upload" />&nbsp;
{{ t('components.channels.UploadForm.message.dragAndDrop') }} {{ t('components.channels.UploadForm.message.dragAndDrop') }}
</div> </div>
<div class="ui very small divider" /> <div class="ui very small divider" />
@ -681,7 +672,5 @@ const createEmptyChannel = async () => {
{{ t('components.channels.UploadForm.label.openBrowser') }} {{ t('components.channels.UploadForm.label.openBrowser') }}
</div> </div>
</file-upload-widget> </file-upload-widget>
<div class="ui hidden divider" />
</template>
</form> </form>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BackendError, Library, FileSystem } from '~/types' import type { BackendError, Library, FileSystem, PrivacyLevel } from '~/types'
import type { VueUploadItem } from 'vue-upload-component' import type { VueUploadItem } from 'vue-upload-component'
import { computed, ref, reactive, watch, nextTick } from 'vue' import { computed, ref, reactive, watch, nextTick } from 'vue'
@ -19,6 +19,11 @@ import FsLogs from './FsLogs.vue'
import useWebSocketHandler from '~/composables/useWebSocketHandler' import useWebSocketHandler from '~/composables/useWebSocketHandler'
import updateQueryString from '~/composables/updateQueryString' import updateQueryString from '~/composables/updateQueryString'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue'
import Slider from '~/components/ui/Slider.vue'
interface Events { interface Events {
(e: 'uploads-finished', delta: number):void (e: 'uploads-finished', delta: number):void
@ -158,16 +163,6 @@ const sortedFiles = computed(() => {
const hasActiveUploads = computed(() => files.value.some(file => file.active)) const hasActiveUploads = computed(() => files.value.some(file => file.active))
// const isOpen = computed({
// get() {
// return store.state.ui.modalsOpen.has(modalName);
// },
// set(value) {
// store.commit('ui/setModal', [modalName, value]);
// }
// })
// //
// Quota status // Quota status
// //
@ -303,10 +298,122 @@ useEventListener(window, 'beforeunload', (event) => {
event.preventDefault() event.preventDefault()
return (event.returnValue = t('components.library.FileUpload.message.listener')) return (event.returnValue = t('components.library.FileUpload.message.listener'))
}) })
const sharedLabels = useSharedLabels()
const options = {
me: sharedLabels.fields.privacy_level.choices.me,
instance: sharedLabels.fields.privacy_level.choices.instance,
everyone: sharedLabels.fields.privacy_level.choices.everyone
} as const satisfies Record<PrivacyLevel, string>;
const option = ref<keyof typeof options>("me");
</script> </script>
<template> <template>
<div class="component-file-upload"> <div :class="{loading: isLoadingQuota}">
<div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']">
<div class="label">
{{ t('components.library.FileUpload.label.remainingSpace') }}
</div>
<div class="value">
{{ humanSize(remainingSpace * 1000 * 1000) }}
</div>
</div>
</div>
<Slider :options="options" v-model="option" :label="t('components.manage.library.UploadsTable.label.visibility')" />
<file-upload-widget
ref="upload"
v-model="files"
:data="uploadData"
@input-file="inputFile"
>
<Button primary icon="bi bi-upload" >
{{ t('components.library.FileUpload.label.uploadWidget') }}
</Button>
<p>
{{ t('components.library.FileUpload.label.extensions', {extensions: supportedExtensions.join(', ')}) }}
</p>
</file-upload-widget>
<Alert blue>
<h2 class="ui header">
{{ t('components.library.FileUpload.header.local') }}
</h2>
<p>
{{ t('components.library.FileUpload.message.local.message') }}
</p>
<ul>
<li v-if="library.privacy_level != 'me'">
{{ t('components.library.FileUpload.message.local.copyright') }}
</li>
<li>
{{ t('components.library.FileUpload.message.local.tag') }}&nbsp;
<a
href="http://picard.musicbrainz.org/"
target="_blank"
>{{ t('components.library.FileUpload.link.picard') }}</a>
</li>
<li>
{{ t('components.library.FileUpload.message.local.format') }}
</li>
</ul>
</Alert>
<h2 class="ui header">
{{ t('components.library.FileUpload.header.server') }}
</h2>
<Alert
v-if="fsErrors.length > 0"
red
>
<h3 class="header">
{{ t('components.library.FileUpload.header.failure') }}
</h3>
<ul class="list">
<li
v-for="(error, key) in fsErrors"
:key="key"
>
{{ error }}
</li>
</ul>
</Alert>
<fs-browser
v-model="fsPath"
:loading="isLoadingFs"
:data="fsStatus"
@import="importFs"
/>
<template v-if="fsStatus && fsStatus.import">
<h3 class="ui header">
{{ t('components.library.FileUpload.header.status') }}
</h3>
<p v-if="fsStatus.import.reference !== importReference">
{{ t('components.library.FileUpload.description.previousImport') }}
</p>
<p v-else>
{{ t('components.library.FileUpload.description.import') }}
</p>
<Button
v-if="fsStatus.import.status === 'started' || fsStatus.import.status === 'pending'"
secondary
@click="cancelFsScan"
>
{{ t('components.library.FileUpload.button.cancel') }}
</Button>
<fs-logs :data="fsStatus.import" />
</template>
<library-files-table
:needs-refresh="needsRefresh"
ordering-config-name="library.detail.upload"
:filters="{import_reference: importReference}"
:custom-objects="Object.values(uploads.objects)"
@fetch-start="needsRefresh = false"
/>
<div class="ui top attached tabular menu"> <div class="ui top attached tabular menu">
<a <a
href="" href=""
@ -325,7 +432,7 @@ useEventListener(window, 'beforeunload', (event) => {
class="ui warning label" class="ui warning label"
> >
{{ uploadedFilesCount + erroredFilesCount }} {{ uploadedFilesCount + erroredFilesCount }}
<span class="slash symbol" /> <span class="bi bi-slash-circle" />
{{ files.length }} {{ files.length }}
</div> </div>
<div <div
@ -333,7 +440,7 @@ useEventListener(window, 'beforeunload', (event) => {
:class="['ui', {'success': erroredFilesCount === 0}, {'danger': erroredFilesCount > 0}, 'label']" :class="['ui', {'success': erroredFilesCount === 0}, {'danger': erroredFilesCount > 0}, 'label']"
> >
{{ uploadedFilesCount + erroredFilesCount }} {{ uploadedFilesCount + erroredFilesCount }}
<span class="slash symbol" /> <span class="bi bi-slash-circle" />
{{ files.length }} {{ files.length }}
</div> </div>
</a> </a>
@ -354,7 +461,7 @@ useEventListener(window, 'beforeunload', (event) => {
class="ui warning label" class="ui warning label"
> >
{{ processedFilesCount }} {{ processedFilesCount }}
<span class="slash symbol" /> <span class="bi bi-slash-circle" />
{{ processableFiles }} {{ processableFiles }}
</div> </div>
<div <div
@ -362,66 +469,15 @@ useEventListener(window, 'beforeunload', (event) => {
:class="['ui', {'success': uploads.errored === 0}, {'danger': uploads.errored > 0}, 'label']" :class="['ui', {'success': uploads.errored === 0}, {'danger': uploads.errored > 0}, 'label']"
> >
{{ processedFilesCount }} {{ processedFilesCount }}
<span class="slash symbol" /> <span class="bi bi-slash-circle" />
{{ processableFiles }} {{ processableFiles }}
</div> </div>
</a> </a>
</div> </div>
<div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'uploads'}]">
<div :class="['ui', {loading: isLoadingQuota}, 'container']">
<div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']">
<div class="label">
{{ t('components.library.FileUpload.label.remainingSpace') }}
</div>
<div class="value">
{{ humanSize(remainingSpace * 1000 * 1000) }}
</div>
</div>
<div class="ui divider" />
<h2 class="ui header">
{{ t('components.library.FileUpload.header.local') }}
</h2>
<div class="ui message">
<p>
{{ t('components.library.FileUpload.message.local.message') }}
</p>
<ul>
<li v-if="library.privacy_level != 'me'">
{{ t('components.library.FileUpload.message.local.copyright') }}
</li>
<li>
{{ t('components.library.FileUpload.message.local.tag') }}&nbsp;
<a
href="http://picard.musicbrainz.org/"
target="_blank"
>{{ t('components.library.FileUpload.link.picard') }}</a>
</li>
<li>
{{ t('components.library.FileUpload.message.local.format') }}
</li>
</ul>
</div>
<file-upload-widget
ref="upload"
v-model="files"
:class="['ui', 'icon', 'basic', 'button']"
:data="uploadData"
@input-file="inputFile"
>
<i class="upload icon" />&nbsp;
{{ t('components.library.FileUpload.label.uploadWidget') }}
<br>
<br>
<i>
{{ t('components.library.FileUpload.label.extensions', {extensions: supportedExtensions.join(', ')}) }}
</i>
</file-upload-widget>
</div>
<div <div
v-if="files.length > 0" v-if="files.length > 0"
class="table-wrapper" class="table-wrapper"
> >
<div class="ui hidden divider" />
<table class="ui unstackable table"> <table class="ui unstackable table">
<thead> <thead>
<tr> <tr>
@ -443,12 +499,13 @@ useEventListener(window, 'beforeunload', (event) => {
<th /> <th />
<th /> <th />
<th> <th>
<button <Button
class="ui right floated small basic button" tiny
style="float: right;"
@click.prevent="retry(retryableFiles)" @click.prevent="retry(retryableFiles)"
> >
{{ t('components.library.FileUpload.button.retry') }} {{ t('components.library.FileUpload.button.retry') }}
</button> </Button>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -468,7 +525,7 @@ useEventListener(window, 'beforeunload', (event) => {
:data-tooltip="labels.tooltips[file.error]" :data-tooltip="labels.tooltips[file.error]"
> >
<span class="ui danger icon label"> <span class="ui danger icon label">
<i class="question circle outline icon" /> {{ file.error }} <i class="bi bi-question-circle-fill" /> {{ file.error }}
</span> </span>
</span> </span>
<span <span
@ -500,84 +557,35 @@ useEventListener(window, 'beforeunload', (event) => {
</td> </td>
<td> <td>
<template v-if="file.error"> <template v-if="file.error">
<button <Button
v-if="retryableFiles.includes(file)" v-if="retryableFiles.includes(file)"
class="ui tiny basic icon right floated button" tiny
style="float: right;"
:title="labels.tooltips.retry" :title="labels.tooltips.retry"
icon="bi-arrow-clockwise"
@click.prevent="retry([file])" @click.prevent="retry([file])"
> />
<i class="redo icon" />
</button>
</template> </template>
<template v-else-if="!file.success"> <template v-else-if="!file.success">
<button <Button
class="ui tiny basic danger icon right floated button" tiny
@click.prevent="upload.remove(file)" @click.prevent="upload.remove(file)"
style="float: right;"
icon="bi-trash-fill"
> >
<i class="delete icon" /> </Button>
</button>
</template> </template>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="ui divider" />
<h2 class="ui header">
{{ t('components.library.FileUpload.header.server') }}
</h2>
<div
v-if="fsErrors.length > 0"
role="alert"
class="ui negative message"
>
<h3 class="header">
{{ t('components.library.FileUpload.header.failure') }}
</h3>
<ul class="list">
<li
v-for="(error, key) in fsErrors"
:key="key"
>
{{ error }}
</li>
</ul>
</div>
<fs-browser
v-model="fsPath"
:loading="isLoadingFs"
:data="fsStatus"
@import="importFs"
/>
<template v-if="fsStatus && fsStatus.import">
<h3 class="ui header">
{{ t('components.library.FileUpload.header.status') }}
</h3>
<p v-if="fsStatus.import.reference !== importReference">
{{ t('components.library.FileUpload.description.previousImport') }}
</p>
<p v-else>
{{ t('components.library.FileUpload.description.import') }}
</p>
<button
v-if="fsStatus.import.status === 'started' || fsStatus.import.status === 'pending'"
class="ui button"
@click="cancelFsScan"
>
{{ t('components.library.FileUpload.button.cancel') }}
</button>
<fs-logs :data="fsStatus.import" />
</template>
</div>
<div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'processing'}]">
<library-files-table
:needs-refresh="needsRefresh"
ordering-config-name="library.detail.upload"
:filters="{import_reference: importReference}"
:custom-objects="Object.values(uploads.objects)"
@fetch-start="needsRefresh = false"
/>
</div>
</div>
</template> </template>
<style scoped lang="scss">
.file-uploads {
padding: 32px;
border-radius: var(--fw-border-radius);
border: 2px dashed var(--border-color);
}
</style>

View File

@ -86,7 +86,7 @@ export default { inheritAttrs: false }
<file-upload <file-upload
ref="upload" ref="upload"
v-bind="$attrs" v-bind="$attrs"
:post-action="store.getters['instance/absoluteUrl']('/api/v1/uploads/')" :post-action="store.getters['instance/absoluteUrl']('/api/v2/uploads/')"
:multiple="true" :multiple="true"
:thread="1" :thread="1"
:custom-action="uploadAction" :custom-action="uploadAction"

View File

@ -2,9 +2,12 @@
import type { FileSystem, FSEntry } from '~/types' import type { FileSystem, FSEntry } from '~/types'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
interface Events { interface Events {
@ -34,23 +37,23 @@ const handleClick = (entry: FSEntry) => {
value.value.push(entry.name) value.value.push(entry.name)
} }
const path = computed (() => props.data.root + '/' + value.value.join('/'))
</script> </script>
<template> <template>
<div :class="['ui', { loading }, 'segment']"> <div :class="['ui', { loading }]">
<div class="ui fluid action input"> <Layout flex>
<input <Input
class="ui disabled" v-model="path"
disabled disabled
:value="props.data.root + '/' + value.join('/')" />
>
<Button <Button
class="ui button" primary
@click.prevent="emit('import')" @click.prevent="emit('import')"
> >
{{ t('components.library.FsBrowser.button.import') }} {{ t('components.library.FsBrowser.button.import') }}
</Button> </Button>
</div> </Layout>
<div class="ui list component-fs-browser"> <div class="ui list component-fs-browser">
<a <a
v-if="value.length > 0" v-if="value.length > 0"
@ -58,7 +61,7 @@ const handleClick = (entry: FSEntry) => {
href="" href=""
@click.prevent="handleClick({ name: '..', dir: true })" @click.prevent="handleClick({ name: '..', dir: true })"
> >
<i class="folder icon" /> <i class="bi bi-folder" />
<div class="content"> <div class="content">
<div class="header doubledot symbol" /> <div class="header doubledot symbol" />
</div> </div>
@ -72,11 +75,11 @@ const handleClick = (entry: FSEntry) => {
> >
<i <i
v-if="e.dir" v-if="e.dir"
class="folder icon" class="bi bi-folder"
/> />
<i <i
v-else v-else
class="file icon" class="bi bi-file-earmark-music-fill"
/> />
<div class="content"> <div class="content">
<div class="header">{{ e.name }}</div> <div class="header">{{ e.name }}</div>

View File

@ -1,24 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'; import { useStore } from '~/store'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
import { useModal } from '~/ui/composables/useModal.ts' import { useModal } from '~/ui/composables/useModal.ts'
import Modal from '~/components/ui/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue' import Spacer from '~/components/ui/Spacer.vue'
import Alert from '~/components/ui/Alert.vue'; import Alert from '~/components/ui/Alert.vue'
import Card from '~/components/ui/Card.vue'; import Card from '~/components/ui/Card.vue'
import Pagination from '~/components/ui/Pagination.vue';
import type { Actor, Channel } from '~/types'; import type { Actor, Channel } from '~/types'
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'; import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
import type { VueUploadItem } from 'vue-upload-component'; import type { VueUploadItem } from 'vue-upload-component'
import ChannelUpload from '~/components/channels/UploadForm.vue'; import ChannelUpload from '~/components/channels/UploadForm.vue'
import LibraryUpload from '~/components/library/FileUpload.vue'; import LibraryUpload from '~/components/library/FileUpload.vue'
import LibraryWidget from '~/components/federation/LibraryWidget.vue' import LibraryWidget from '~/components/federation/LibraryWidget.vue'
const { t } = useI18n() const { t } = useI18n()
@ -75,20 +73,14 @@ const channelUpload = ref();
v-model="isOpen" v-model="isOpen"
> >
<!-- Alert -->
<template #alert v-if="state.page === 'selectDestination'">
<Alert blue>
Before uploading, please ensure your files are tagged properly.
We recommend using Picard for that purpose.
</Alert>
</template>
<!-- Page content --> <!-- Page content -->
<!-- Page 1 --> <!-- Page 1 -->
<Layout flex style="place-content:center" v-if="state.page === 'selectDestination'"> <Layout flex style="place-content:center" v-if="state.page === 'selectDestination'">
<Card small title="Music" <Card
small
title="Music"
solid
icon="bi-upload primary solid" icon="bi-upload primary solid"
@click="destinationSelected('channel')" @click="destinationSelected('channel')"
> >
@ -97,7 +89,10 @@ const channelUpload = ref();
</template> </template>
{{ "Publish music you make" /* TODO: Translate */ }} {{ "Publish music you make" /* TODO: Translate */ }}
</Card> </Card>
<Card small title="Podcast" <Card
small
solid
title="Podcast"
icon="bi-upload primary solid" icon="bi-upload primary solid"
@click="destinationSelected('podcast')" @click="destinationSelected('podcast')"
> >
@ -106,7 +101,10 @@ const channelUpload = ref();
</template> </template>
{{ "Publish podcasts you make" /* TODO: Translate */ }} {{ "Publish podcasts you make" /* TODO: Translate */ }}
</Card> </Card>
<Card small title="Mix & Share" <Card
small
solid
title="Mix & Share"
icon="bi-upload" icon="bi-upload"
@click="destinationSelected('library')" @click="destinationSelected('library')"
> >
@ -128,9 +126,9 @@ const channelUpload = ref();
<!-- --> <!-- -->
<!-- Privacy Slider --> <!-- Privacy Slider -->
<!-- <LibraryUpload v-if="state.uploadDestination === 'library'" <LibraryUpload v-if="state.uploadDestination === 'library'"
:library="{uuid: 'string'} /* Get corresponding library from user */"> :library="{uuid: 'string'} /* Get corresponding library from user */">
</LibraryUpload> --> </LibraryUpload>
{{ state.files }} {{ state.files }}
</Layout> </Layout>

View File

@ -12,16 +12,23 @@ import { useI18n } from 'vue-i18n'
import time from '~/utils/time' import time from '~/utils/time'
import axios from 'axios' import axios from 'axios'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useSmartSearch from '~/composables/navigation/useSmartSearch' import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import usePage from '~/composables/navigation/usePage' import usePage from '~/composables/navigation/usePage'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import ActionTable from '~/components/common/ActionTable.vue'
import Layout from '~/components/ui/Layout.vue'
import Alert from '~/components/ui/Alert.vue'
import Pagination from '~/components/ui/Pagination.vue'
import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
import Loader from '~/components/ui/Loader.vue'
interface Events { interface Events {
(e: 'fetch-start'): void (e: 'fetch-start'): void
} }
@ -133,22 +140,17 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
</script> </script>
<template> <template>
<div> <Layout form>
<div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field"> <div class="ui six wide field">
<label for="files-search"> <form @submit.prevent="console.log(search);query = search">
{{ t('views.content.libraries.FilesTable.label.search') }} <Input
</label> search
<form @submit.prevent="query = search.value">
<input
id="files-search" id="files-search"
ref="search" :label="t('views.content.libraries.FilesTable.label.search')"
name="search" v-model="search"
type="text"
:value="query"
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> />
</form> </form>
</div> </div>
<div class="field"> <div class="field">
@ -217,28 +219,21 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
</select> </select>
</div> </div>
</div> </div>
</div> </Layout>
<import-status-modal <import-status-modal
v-if="detailedUpload" v-if="detailedUpload"
v-model:show="showUploadDetailModal" v-model:show="showUploadDetailModal"
:upload="detailedUpload" :upload="detailedUpload"
/> />
<div class="dimmable"> <Loader v-if="isLoading" />
<div <Alert
v-if="isLoading"
class="ui active inverted dimmer"
>
<div class="ui loader" />
</div>
<div
v-else-if="!result || result?.results.length === 0 && !needsRefresh" v-else-if="!result || result?.results.length === 0 && !needsRefresh"
class="ui placeholder segment" blue
align-text="center"
> >
<div class="ui icon header"> <i class="bi bi-upload" />
<i class="upload icon" />
{{ t('views.content.libraries.FilesTable.empty.noTracks') }} {{ t('views.content.libraries.FilesTable.empty.noTracks') }}
</div> </Alert>
</div>
<action-table <action-table
v-else v-else
:id-field="'uuid'" :id-field="'uuid'"
@ -317,14 +312,13 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
:title="getImportStatusChoice(scope.obj.import_status).help" :title="getImportStatusChoice(scope.obj.import_status).help"
@click.prevent="addSearchToken('status', scope.obj.import_status)" @click.prevent="addSearchToken('status', scope.obj.import_status)"
>{{ getImportStatusChoice(scope.obj.import_status).label }}</a> >{{ getImportStatusChoice(scope.obj.import_status).label }}</a>
<button <Button
class="ui tiny basic icon button" secondary
:title="sharedLabels.fields.import_status.label" :title="sharedLabels.fields.import_status.label"
:aria-label="labels.showStatus" :aria-label="labels.showStatus"
icon="bi-question-circle-fill"
@click="detailedUpload = scope.obj; showUploadDetailModal = true" @click="detailedUpload = scope.obj; showUploadDetailModal = true"
> />
<i class="question circle outline icon" />
</button>
</td> </td>
<td v-if="scope.obj.duration"> <td v-if="scope.obj.duration">
{{ time.parse(scope.obj.duration) }} {{ time.parse(scope.obj.duration) }}
@ -340,19 +334,11 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
</td> </td>
</template> </template>
</action-table> </action-table>
</div>
<div> <div>
<pagination <Pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"
v-model:current="page" v-model:page="page"
:compact="true" :pages="Math.ceil(result.count / paginateBy)"
:paginate-by="paginateBy"
:total="result.count"
/> />
<span v-if="result && result.results.length > 0">
{{ t('views.content.libraries.FilesTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
</span>
</div>
</div> </div>
</template> </template>