Newer
Older
<template>
<div>
<div class="ui inline form">
<div class="fields">
<div class="ui six wide field">
<label>
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
</label>
<form @submit.prevent="search.query = $refs.search.value">
<input
name="search"
ref="search"
type="text"
:value="search.query"
:placeholder="labels.searchPlaceholder"
/>
</form>
</div>
<div class="field">
<label>
<translate translate-context="Content/*/*/Noun">Import status</translate>
</label>
<select
class="ui dropdown"
@change="addSearchToken('status', $event.target.value)"
:value="getTokenValue('status', '')"
>
<option value>
<translate translate-context="Content/*/Dropdown">All</translate>
</option>
<option value="pending">
<translate translate-context="Content/Library/*/Short">Pending</translate>
</option>
<option value="skipped">
<translate translate-context="Content/Library/*">Skipped</translate>
</option>
<option value="errored">
<translate translate-context="Content/Library/Dropdown">Failed</translate>
</option>
<option value="finished">
<translate translate-context="Content/Library/*">Finished</translate>
</option>
</select>
</div>
<div class="field">
<label>
<translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate>
</label>
<select class="ui dropdown" v-model="ordering">
<option
v-for="option in orderingOptions"
:value="option[0]"
>{{ sharedLabels.filters[option[1]] }}</option>
</select>
</div>
<div class="field">
<label>
<translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate>
</label>
<select class="ui dropdown" v-model="orderingDirection">
<option value="+">
<translate translate-context="Content/Search/Dropdown">Ascending</translate>
</option>
<option value="-">
<translate translate-context="Content/Search/Dropdown">Descending</translate>
</option>
</select>
</div>
</div>
</div>
<import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" />
<div class="dimmable">
<div v-if="isLoading" class="ui active inverted dimmer">
<div class="ui loader"></div>
</div>
<div v-else-if="!result && result.results.length === 0 && !needsRefresh" class="ui placeholder segment">
<div class="ui icon header">
<i class="upload icon"></i>
<translate
translate-context="Content/Home/Placeholder"
>No tracks have been added to this library yet</translate>
</div>
@action-launched="fetchData"
:id-field="'uuid'"
:objects-data="result"
:custom-objects="customObjects"
:actions="actions"
:refreshable="true"
:needs-refresh="needsRefresh"
:filters="actionFilters"
>
<template slot="header-cells">
<th>
<translate translate-context="*/*/*/Noun">Title</translate>
</th>
<th>
<translate translate-context="*/*/*/Noun">Artist</translate>
</th>
<th>
<translate translate-context="*/*/*">Album</translate>
</th>
<th>
<translate translate-context="*/*/*/Noun">Upload date</translate>
</th>
<th>
<translate translate-context="Content/*/*/Noun">Import status</translate>
</th>
<th>
<translate translate-context="Content/*/*">Duration</translate>
</th>
<th>
<translate translate-context="Content/*/*/Noun">Size</translate>
</th>
</template>
<template slot="row-cells" slot-scope="scope">
<template v-if="scope.obj.track">
<td>
<router-link :to="{name: 'library.tracks.detail', params: {id: scope.obj.track.id }}" :title="scope.obj.track.title">
{{ scope.obj.track.title|truncate(25) }}
</router-link>
<span
class="discrete link"
@click="addSearchToken('artist', scope.obj.track.artist.name)"
:title="scope.obj.track.artist.name"
>{{ scope.obj.track.artist.name|truncate(20) }}</span>
<span
class="discrete link"
@click="addSearchToken('album', scope.obj.track.album.title)"
:title="scope.obj.track.album.title"
>{{ scope.obj.track.album.title|truncate(20) }}</span>
</td>
</template>
<template v-else>
<td :title="scope.obj.source">{{ scope.obj.source | truncate(25) }}</td>
<td></td>
<td></td>
</template>
<td>
<human-date :date="scope.obj.creation_date"></human-date>
</td>
Eliot Berriot
committed
<td>
<span
class="discrete link"
@click="addSearchToken('status', scope.obj.import_status)"
:title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"
>{{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }}</span>
<button
class="ui tiny basic icon button"
:title="sharedLabels.fields.import_status.detailTitle"
@click="detailedUpload = scope.obj; showUploadDetailModal = true"
>
Eliot Berriot
committed
<i class="question circle outline icon"></i>
</button>
<td v-if="scope.obj.duration">{{ time.parse(scope.obj.duration) }}</td>
<translate translate-context="*/*/*">N/A</translate>
<td v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</td>
<translate translate-context="*/*/*">N/A</translate>
</td>
</template>
</action-table>
</div>
<div>
<pagination
v-if="result && result.count > paginateBy"
@page-changed="page = $event; fetchData()"
:compact="true"
:current="page"
:paginate-by="paginateBy"
:total="result.count"
<span v-if="result && result.results.length > 0">
<translate
translate-context="Content/*/Paragraph"
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
>Showing results %{ start }-%{ end } on %{ total }</translate>
</span>
</div>
</div>
</template>
<script>
import axios from "axios";
import _ from "@/lodash";
import time from "@/utils/time";
import { normalizeQuery, parseTokens } from "@/search";
import Pagination from "@/components/Pagination";
import ActionTable from "@/components/common/ActionTable";
import OrderingMixin from "@/components/mixins/Ordering";
import TranslationsMixin from "@/components/mixins/Translations";
import SmartSearchMixin from "@/components/mixins/SmartSearch";
import ImportStatusModal from "@/components/library/ImportStatusModal";
mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
filters: { type: Object, required: false },
needsRefresh: { type: Boolean, required: false, default: false },
customObjects: {
type: Array,
required: false,
default: () => {
return [];
}
}
},
components: {
Pagination,
Eliot Berriot
committed
ActionTable,
Eliot Berriot
committed
detailedUpload: null,
showUploadDetailModal: false,
isLoading: false,
result: null,
page: 1,
search: {
query: this.defaultQuery,
tokens: parseTokens(normalizeQuery(this.defaultQuery))
},
orderingOptions: [
["creation_date", "creation_date"],
["title", "track_title"],
["size", "size"],
["duration", "duration"],
["bitrate", "bitrate"],
["album_title", "album_title"],
["artist_name", "artist_name"]
created() {
this.fetchData();
fetchData() {
this.$emit("fetch-start");
let params = _.merge(
{
page: this.page,
page_size: this.paginateBy,
ordering: this.getOrderingAsString(),
q: this.search.query
},
this.filters || {}
);
let self = this;
self.isLoading = true;
self.checked = [];
axios.get("/uploads/", { params: params }).then(
response => {
self.result = response.data;
self.isLoading = false;
},
error => {
self.isLoading = false;
self.errors = error.backendErrors;
}
);
Eliot Berriot
committed
},
searchPlaceholder: this.$pgettext(
"Content/Library/Input.Placeholder",
"Search by title, artist, album…"
)
};
var currentFilters = {
q: this.search.query
return _.merge(currentFilters, this.filters);
actions() {
let deleteMsg = this.$pgettext("*/*/*/Verb", "Delete");
let relaunchMsg = this.$pgettext(
"Content/Library/Dropdown/Verb",
"Restart import"
);
label: deleteMsg,
isDangerous: true,
allowAll: true
},
{
label: relaunchMsg,
isDangerous: true,
allowAll: true,
filterCheckable: f => {
return f.import_status != "finished";
orderingDirection: function() {
this.page = 1;
this.fetchData();
page: function() {
this.fetchData();
ordering: function() {
this.page = 1;
this.fetchData();
search(newValue) {
this.page = 1;
this.fetchData();