mirror of
https://gitlab.com/RemixDev/deemix-webui.git
synced 2024-12-28 02:16:11 +00:00
workflow: reset feature/search-page-improvement branch
This commit is contained in:
parent
66b1ebe244
commit
7f0d621f62
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -20,8 +20,8 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
# .vscode
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
|
|
28
.vscode/tasks.json
vendored
Normal file
28
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"label": "npm: build",
|
||||
"detail": "npm-run-all --sequential clean build:js"
|
||||
},
|
||||
{
|
||||
// Workaround for dev script
|
||||
"type": "npm",
|
||||
"script": "dev:gui",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: dev:gui",
|
||||
"detail": "npm-run-all --parallel serve:gui watch:js",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
3019
package-lock.json
generated
3019
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -4,12 +4,12 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>deemix</title>
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/material-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/OpenSans.css">
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0">
|
||||
|
||||
<script>
|
||||
if (localStorage.getItem('selectedTheme')) {
|
||||
document.documentElement.setAttribute('data-theme', localStorage.getItem('selectedTheme'))
|
||||
|
|
File diff suppressed because one or more lines are too long
10
src/App.vue
10
src/App.vue
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="app">
|
||||
<div class="app-container">
|
||||
<TheSidebar />
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
|||
</div>
|
||||
|
||||
<BaseLoadingPlaceholder
|
||||
:hidden="isSocketConnected"
|
||||
text="Connecting to the server..."
|
||||
:hidden="isSocketConnected"
|
||||
additionalClasses="absolute top-0 left-0 w-screen h-screen bg-black bg-opacity-50 z-50"
|
||||
/>
|
||||
|
||||
|
@ -44,7 +44,7 @@ import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.v
|
|||
import TheContextMenu from '@components/globals/TheContextMenu.vue'
|
||||
import TheTrackPreview from '@components/globals/TheTrackPreview.vue'
|
||||
import TheQualityModal from '@components/globals/TheQualityModal.vue'
|
||||
import ConfirmModal from '@components/globals/ConfirmModal.vue'
|
||||
// import ConfirmModal from '@components/globals/ConfirmModal.vue'
|
||||
|
||||
import TheSidebar from '@components/TheSidebar.vue'
|
||||
import TheSearchBar from '@components/TheSearchBar.vue'
|
||||
|
@ -65,8 +65,8 @@ export default {
|
|||
TheQualityModal,
|
||||
BaseLoadingPlaceholder,
|
||||
TheContextMenu,
|
||||
TheContent,
|
||||
ConfirmModal
|
||||
TheContent
|
||||
// ConfirmModal
|
||||
},
|
||||
mounted() {
|
||||
socket.on('connect', () => {
|
||||
|
|
|
@ -5,8 +5,12 @@ window.vol = {
|
|||
preview_max_volume: 100
|
||||
}
|
||||
|
||||
import '@/styles/css/material-icons.css'
|
||||
import '@/styles/css/OpenSans.css'
|
||||
|
||||
import '@/styles/scss/style.scss'
|
||||
import '@/styles/css/components.css'
|
||||
import '@/styles/css/helpers.css'
|
||||
|
||||
import App from '@/App.vue'
|
||||
import i18n from '@/plugins/i18n'
|
||||
|
@ -174,9 +178,9 @@ socket.on('errorMessage', function(error) {
|
|||
|
||||
socket.on('queueError', function(queueItem) {
|
||||
if (queueItem.errid) {
|
||||
toast(queueItem.link+ " - " +i18n.t(`errors.ids.${queueItem.errid}`), 'error')
|
||||
toast(queueItem.link + ' - ' + i18n.t(`errors.ids.${queueItem.errid}`), 'error')
|
||||
} else {
|
||||
toast(queueItem.link+ " - " +queueItem.error, 'error')
|
||||
toast(queueItem.link + ' - ' + queueItem.error, 'error')
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../styles/scss/base/_variables.scss';
|
||||
// src/components/TheContent.vue
|
||||
#container {
|
||||
--container-width: 95%;
|
||||
|
||||
|
@ -41,11 +39,11 @@
|
|||
width: var(--container-width);
|
||||
transform: scale(1);
|
||||
|
||||
@media only screen and (min-width: $small) {
|
||||
@media only screen and (min-width: 601px) {
|
||||
--container-width: 85%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $medium) {
|
||||
@media only screen and (min-width: 993px) {
|
||||
--container-width: 70%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,7 +168,8 @@ export default {
|
|||
}
|
||||
} else {
|
||||
if (isShowingSearch && sameAsLastSearch) {
|
||||
this.$root.$emit('mainSearch:updateResults', term)
|
||||
// ? Has this any sense since we're not performing any call?
|
||||
// this.$root.$emit('mainSearch:updateResults', term)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<span
|
||||
v-if="hasFails"
|
||||
class="inline-flex"
|
||||
class="flex items-center"
|
||||
:class="{ clickable: finishedWithFails }"
|
||||
style="justify-content: center"
|
||||
@click="finishedWithFails ? $emit('show-errors', queueItem) : null"
|
||||
|
@ -181,6 +181,36 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate {
|
||||
0% {
|
||||
left: -35%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate-short {
|
||||
0% {
|
||||
left: -200%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
100% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<template functional>
|
||||
<div
|
||||
:id="props.id"
|
||||
class="flex justify-center items-center flex-col flex-1 h-full"
|
||||
:class="props.additionalClasses"
|
||||
v-show="!props.hidden"
|
||||
>
|
||||
<span class="mb-5">{{ props.text }}</span>
|
||||
<span class="mb-5">{{ props.text || 'Loading...' }}</span>
|
||||
|
||||
<div class="lds-ring">
|
||||
<div></div>
|
||||
|
@ -60,27 +59,3 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Loading...'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
additionalClasses: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import '../../styles/scss/base/_variables.scss';
|
||||
|
||||
.smallmodal {
|
||||
position: fixed;
|
||||
z-index: 1250;
|
||||
|
@ -47,11 +45,11 @@
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
@media only screen and (min-width: $small) {
|
||||
@media only screen and (min-width: 601px) {
|
||||
--modal-content-width: 85%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $medium) {
|
||||
@media only screen and (min-width: 993px) {
|
||||
--modal-content-width: 70%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
<a href="https://deemix.app" target="_blank">🌍 {{ $t('about.officialWebsite') }}</a>
|
||||
</li> -->
|
||||
<li>
|
||||
<a href="https://git.fuwafuwa.moe/RemixDev/deemix" target="_blank">🚀 {{ $t('about.officialRepo') }}</a>
|
||||
<a href="https://codeberg.org/RemixDev/deemix" target="_blank">🚀 {{ $t('about.officialRepo') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.fuwafuwa.moe/RemixDev/deemix-webui" target="_blank">💻 {{ $t('about.officialWebuiRepo') }}</a>
|
||||
<a href="https://codeberg.org/RemixDev/deemix-webui" target="_blank">💻 {{ $t('about.officialWebuiRepo') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.reddit.com/r/deemix" target="_blank">🤖 {{ $t('about.officialSubreddit') }}</a>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div id="artist_tab" class="relative image-header" ref="root">
|
||||
<header
|
||||
class="inline-flex"
|
||||
class="flex items-center"
|
||||
:style="{
|
||||
'background-image':
|
||||
'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\'' + image + '\')'
|
||||
|
@ -14,7 +14,7 @@
|
|||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="link"
|
||||
class="rounded-full bg-primary text-grayscale-870 cursor-pointer w-16 h-16 grid place-items-center right"
|
||||
class="rounded-full bg-primary text-grayscale-870 cursor-pointer w-16 h-16 grid place-items-center ml-auto"
|
||||
>
|
||||
<i class="material-icons text-4xl" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
|
@ -54,13 +54,13 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="release in showTable" :key="release.id">
|
||||
<router-link tag="td" class="inline-flex clickable" :to="{ name: 'Album', params: { id: release.id } }">
|
||||
<router-link tag="td" class="flex items-center clickable" :to="{ name: 'Album', params: { id: release.id } }">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="release.cover_small"
|
||||
style="margin-right: 16px; width: 56px; height: 56px"
|
||||
/>
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit-icon"> explicit </i>
|
||||
{{ release.title }}
|
||||
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color: #ff7300">
|
||||
fiber_new
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<table class="table table--charts">
|
||||
<tbody>
|
||||
<tr v-for="track in chart" class="track_row">
|
||||
<td class="top-tracks-position" :class="{ first: track.position === 1 }">
|
||||
<td class="p-3 text-center cursor-default" :class="{ first: track.position === 1 }">
|
||||
{{ track.position }}
|
||||
</td>
|
||||
<td class="table__icon table__icon--big">
|
||||
|
|
|
@ -26,9 +26,7 @@
|
|||
</ul>
|
||||
|
||||
<button class="btn btn-primary" v-if="!activeTabEmpty" style="margin-bottom: 2rem" @click="downloadAllOfType">
|
||||
{{
|
||||
$t('globals.download', { thing: $tc(`globals.listTabs.${activeTab}N`, getTabLenght() )})
|
||||
}}
|
||||
{{ $t('globals.downloadAll', { thing: $tc(`globals.listTabs.${activeTab}`, 2) }) }}
|
||||
</button>
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'playlist' }">
|
||||
|
@ -166,7 +164,7 @@
|
|||
</div>
|
||||
<table v-if="tracks.length > 0" class="table">
|
||||
<tr v-for="track in tracks" class="track_row">
|
||||
<td class="top-tracks-position" :class="{ first: track.position === 1 }">
|
||||
<td class="p-3 text-center cursor-default" :class="{ first: track.position === 1 }">
|
||||
{{ track.position }}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -389,12 +387,6 @@ export default {
|
|||
|
||||
return toDownload
|
||||
},
|
||||
getTabLenght(tab = this.activeTab) {
|
||||
let total = this[`${tab}s`].length
|
||||
// TODO: Add Spotify playlists to downlaod queue as well
|
||||
//if (tab === "playlist") total += this.spotifyPlaylists.length
|
||||
return total
|
||||
},
|
||||
getLovedTracksPlaylist() {
|
||||
let lovedTracks = this.playlists.filter(playlist => {
|
||||
return playlist.is_loved_track
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<section class="py-6 border-0 border-t border-solid border-grayscale-500" ref="notLogged" v-if="!isLoggedIn">
|
||||
<p id="home_not_logged_text" class="mb-4">{{ $t('home.needTologin') }}</p>
|
||||
<router-link tag="button" name="button" :to="{ name: 'Settings' }" class="btn btn-primary">
|
||||
<router-link tag="button" class="btn btn-primary" name="button" :to="{ name: 'Settings' }">
|
||||
{{ $t('home.openSettings') }}
|
||||
</router-link>
|
||||
</section>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<div v-else>
|
||||
<header
|
||||
class="inline-flex"
|
||||
class="flex items-center"
|
||||
:style="{
|
||||
'background-image':
|
||||
'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\'' + image + '\')'
|
||||
|
@ -68,7 +68,7 @@
|
|||
@contextmenu.prevent="openQualityModal"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="link"
|
||||
class="rounded-full bg-primary text-grayscale-870 cursor-pointer w-16 h-16 grid place-items-center right"
|
||||
class="rounded-full bg-primary text-grayscale-870 cursor-pointer w-16 h-16 grid place-items-center ml-auto"
|
||||
>
|
||||
<i class="material-icons text-4xl" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
|
@ -121,10 +121,11 @@
|
|||
</table>
|
||||
|
||||
<div v-if="type == 'album'">
|
||||
<router-link tag="button" :to="{ name: 'Album', params: { id } }">
|
||||
<router-link tag="button" class="btn btn-primary" name="button" :to="{ name: 'Album', params: { id } }">
|
||||
{{ $t('linkAnalyzer.table.tracklist') }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div v-if="countries.length">
|
||||
<p v-for="country in countries">{{ country[0] }} - {{ country[1] }}</p>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
<keep-alive>
|
||||
<component
|
||||
:is="currentTab.component"
|
||||
:results="results"
|
||||
:viewInfo="getViewInfo()"
|
||||
want-headers
|
||||
@add-to-queue="addToQueue"
|
||||
@change-search-tab="changeSearchTab"
|
||||
></component>
|
||||
|
@ -43,10 +44,20 @@ import { sendAddToQueue } from '@/utils/downloads'
|
|||
import { numberWithDots, convertDuration } from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
import { reduceSearchResults, formatSingleTrack, formatAlbums, formatArtist, formatPlaylist } from '@/data/search'
|
||||
|
||||
const resetObj = { data: [], next: 0, total: 0, hasLoaded: false }
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
props: {
|
||||
performScrolledSearch: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const $t = this.$t.bind(this)
|
||||
const $tc = this.$tc.bind(this)
|
||||
|
@ -54,33 +65,45 @@ export default {
|
|||
return {
|
||||
currentTab: {
|
||||
name: '',
|
||||
component: {}
|
||||
searchType: '',
|
||||
component: {},
|
||||
viewInfo: '',
|
||||
formatFunc: () => {}
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
name: $t('globals.listTabs.all'),
|
||||
searchType: 'all',
|
||||
component: ResultsAll
|
||||
component: ResultsAll,
|
||||
viewInfo: 'allTab'
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.track', 2),
|
||||
searchType: 'track',
|
||||
component: ResultsTracks
|
||||
component: ResultsTracks,
|
||||
viewInfo: 'trackTab',
|
||||
formatFunc: formatSingleTrack
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.album', 2),
|
||||
searchType: 'album',
|
||||
component: ResultsAlbums
|
||||
component: ResultsAlbums,
|
||||
viewInfo: 'albumTab',
|
||||
formatFunc: formatAlbums
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.artist', 2),
|
||||
searchType: 'artist',
|
||||
component: ResultsArtists
|
||||
component: ResultsArtists,
|
||||
viewInfo: 'artistTab',
|
||||
formatFunc: formatArtist
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.playlist', 2),
|
||||
searchType: 'playlist',
|
||||
component: ResultsPlaylists
|
||||
component: ResultsPlaylists,
|
||||
viewInfo: 'playlistTab',
|
||||
formatFunc: formatPlaylist
|
||||
}
|
||||
],
|
||||
results: {
|
||||
|
@ -88,35 +111,23 @@ export default {
|
|||
allTab: {
|
||||
ORDER: [],
|
||||
TOP_RESULT: [],
|
||||
ALBUM: {},
|
||||
ARTIST: {},
|
||||
TRACK: {},
|
||||
PLAYLIST: {}
|
||||
ALBUM: {
|
||||
hasLoaded: false
|
||||
},
|
||||
ARTIST: {
|
||||
hasLoaded: false
|
||||
},
|
||||
TRACK: {
|
||||
hasLoaded: false
|
||||
},
|
||||
PLAYLIST: {
|
||||
hasLoaded: false
|
||||
}
|
||||
},
|
||||
trackTab: {
|
||||
data: [],
|
||||
next: 0,
|
||||
total: 0,
|
||||
loaded: false
|
||||
},
|
||||
albumTab: {
|
||||
data: [],
|
||||
next: 0,
|
||||
total: 0,
|
||||
loaded: false
|
||||
},
|
||||
artistTab: {
|
||||
data: [],
|
||||
next: 0,
|
||||
total: 0,
|
||||
loaded: false
|
||||
},
|
||||
playlistTab: {
|
||||
data: [],
|
||||
next: 0,
|
||||
total: 0,
|
||||
loaded: false
|
||||
}
|
||||
trackTab: { ...resetObj },
|
||||
albumTab: { ...resetObj },
|
||||
artistTab: { ...resetObj },
|
||||
playlistTab: { ...resetObj }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -125,87 +136,82 @@ export default {
|
|||
return this.results.query !== ''
|
||||
},
|
||||
loadedTabs() {
|
||||
const loaded = []
|
||||
const tabsLoaded = []
|
||||
|
||||
for (const resultKey in this.results) {
|
||||
if (this.results.hasOwnProperty(resultKey)) {
|
||||
const result = this.results[resultKey]
|
||||
const currentResult = this.results[resultKey]
|
||||
|
||||
if (result.loaded) {
|
||||
loaded.push(resultKey.replace(/Tab/g, ''))
|
||||
if (currentResult.hasLoaded) {
|
||||
tabsLoaded.push(resultKey.replace(/Tab/g, ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded
|
||||
}
|
||||
},
|
||||
props: {
|
||||
performScrolledSearch: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
return tabsLoaded
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('mainSearch:checkLoadMoreContent', this.checkLoadMoreContent)
|
||||
this.$root.$on('mainSearch:showNewResults', this.checkIfShowNewResults)
|
||||
this.$root.$on('mainSearch:showNewResults', this.checkIfPerformNewMainSearch)
|
||||
this.$root.$on('mainSearch:updateResults', this.checkIfUpdateResults)
|
||||
|
||||
socket.on('mainSearch', this.handleMainSearch)
|
||||
socket.on('mainSearch', this.saveMainSearchResult)
|
||||
socket.on('search', this.handleSearch)
|
||||
},
|
||||
methods: {
|
||||
changeSearchTab(sectionName) {
|
||||
sectionName = sectionName.toLowerCase()
|
||||
numberWithDots,
|
||||
convertDuration,
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
getViewInfo() {
|
||||
if (this.currentTab.searchType === 'all') {
|
||||
return this.results.allTab
|
||||
}
|
||||
|
||||
let newTab = this.tabs.find(tab => {
|
||||
return tab.searchType === sectionName
|
||||
return reduceSearchResults(this.results[this.currentTab.viewInfo], this.currentTab.formatFunc)
|
||||
},
|
||||
changeSearchTab(tabName) {
|
||||
tabName = tabName.toLowerCase()
|
||||
|
||||
const newTab = this.tabs.find(tab => {
|
||||
return tab.searchType === tabName
|
||||
})
|
||||
|
||||
if (!newTab) {
|
||||
console.error(`No tab ${sectionName} found`)
|
||||
console.error(`No tab ${tabName} found`)
|
||||
return
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0)
|
||||
this.currentTab = newTab
|
||||
},
|
||||
checkIfShowNewResults(term, mainSelected) {
|
||||
let needToPerformNewSearch = term !== this.results.query /* || mainSelected == 'search_tab' */
|
||||
checkIfPerformNewMainSearch(searchTerm) {
|
||||
let needToPerformNewMainSearch = searchTerm !== this.results.query
|
||||
|
||||
if (needToPerformNewSearch) {
|
||||
this.showNewResults(term)
|
||||
if (needToPerformNewMainSearch) {
|
||||
this.performNewMainSearch(searchTerm)
|
||||
}
|
||||
},
|
||||
checkIfUpdateResults(term) {
|
||||
let needToUpdateSearch = term === this.results.query && this.currentTab.searchType !== 'all'
|
||||
|
||||
if (needToUpdateSearch) {
|
||||
let resetObj = { data: [], next: 0, total: 0, loaded: false }
|
||||
this.results[this.currentTab.searchType + 'Tab'] = { ...resetObj }
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
showNewResults(term) {
|
||||
performNewMainSearch(term) {
|
||||
socket.emit('mainSearch', { term })
|
||||
|
||||
// Showing loading placeholder
|
||||
this.$root.$emit('updateSearchLoadingState', true)
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
checkLoadMoreContent(searchSelected) {
|
||||
if (this.results[searchSelected.split('_')[0] + 'Tab'].data.length !== 0) return
|
||||
// ! Updates search only if the search term is the same as before AND we're not in the ALL tab. Wtf
|
||||
checkIfUpdateResults(term) {
|
||||
let needToUpdateSearch = term === this.results.query && this.currentTab.searchType !== 'all'
|
||||
|
||||
this.search(searchSelected.split('_')[0])
|
||||
if (needToUpdateSearch) {
|
||||
this.results[this.currentTab.searchType + 'Tab'] = { ...resetObj }
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
addToQueue(e) {
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
numberWithDots,
|
||||
convertDuration,
|
||||
search(type) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
|
@ -217,29 +223,34 @@ export default {
|
|||
scrolledSearch() {
|
||||
if (this.currentTab.searchType === 'all') return
|
||||
|
||||
let currentTab = `${this.currentTab.searchType}Tab`
|
||||
const currentTabKey = `${this.currentTab.searchType}Tab`
|
||||
const needToPerformScrolledSearch = this.results[currentTabKey].next < this.results[currentTabKey].total
|
||||
|
||||
if (this.results[currentTab].next < this.results[currentTab].total) {
|
||||
if (needToPerformScrolledSearch) {
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
handleMainSearch(result) {
|
||||
// Hiding loading placeholder
|
||||
saveMainSearchResult(searchResult) {
|
||||
// Hide loading placeholder
|
||||
this.$root.$emit('updateSearchLoadingState', false)
|
||||
|
||||
let resetObj = { data: [], next: 0, total: 0, loaded: false }
|
||||
this.results.query = searchResult.QUERY
|
||||
|
||||
this.results.allTab = searchResult
|
||||
this.results.allTab.TRACK.hasLoaded = true
|
||||
this.results.allTab.ALBUM.hasLoaded = true
|
||||
this.results.allTab.ARTIST.hasLoaded = true
|
||||
this.results.allTab.PLAYLIST.hasLoaded = true
|
||||
|
||||
this.results.allTab = result
|
||||
this.results.trackTab = { ...resetObj }
|
||||
this.results.albumTab = { ...resetObj }
|
||||
this.results.artistTab = { ...resetObj }
|
||||
this.results.playlistTab = { ...resetObj }
|
||||
this.results.query = result.QUERY
|
||||
},
|
||||
handleSearch(result) {
|
||||
const { next: nextResult, total, type, data } = result
|
||||
const { next: nextResult, total, type, data: newData } = result
|
||||
|
||||
let currentTab = type + 'Tab'
|
||||
const currentTabKey = type + 'Tab'
|
||||
let next = 0
|
||||
|
||||
if (nextResult) {
|
||||
|
@ -248,16 +259,16 @@ export default {
|
|||
next = total
|
||||
}
|
||||
|
||||
if (this.results[currentTab].total != total) {
|
||||
this.results[currentTab].total = total
|
||||
if (this.results[currentTabKey].total !== total) {
|
||||
this.results[currentTabKey].total = total
|
||||
}
|
||||
|
||||
if (this.results[currentTab].next != next) {
|
||||
this.results[currentTab].next = next
|
||||
this.results[currentTab].data = this.results[currentTab].data.concat(data)
|
||||
if (this.results[currentTabKey].next !== next) {
|
||||
this.results[currentTabKey].next = next
|
||||
this.results[currentTabKey].data = this.results[currentTabKey].data.concat(newData)
|
||||
}
|
||||
|
||||
this.results[currentTab].loaded = true
|
||||
this.results[currentTabKey].hasLoaded = true
|
||||
},
|
||||
isTabLoaded(tab) {
|
||||
return this.loadedTabs.indexOf(tab.searchType) !== -1 || tab.searchType === 'all'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div id="settings_tab" class="fixed-footer " ref="root">
|
||||
<div id="settings_tab" class="fixed-footer" ref="root">
|
||||
<h1 class="mb-8 text-5xl">{{ $t('settings.title') }}</h1>
|
||||
|
||||
<div id="logged_in_info" v-if="isLoggedIn" ref="loggedInInfo">
|
||||
|
@ -22,7 +22,7 @@
|
|||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">person</i>{{ $t('settings.login.title') }}
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
|
@ -68,11 +68,11 @@
|
|||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">web</i>{{ $t('settings.appearance.title') }}
|
||||
</h3>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="changeSlimDownloads" />
|
||||
<span class="checkbox_text">{{ $t('settings.appearance.slimDownloadTab') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="changeSlimSidebar" />
|
||||
<span class="checkbox_text">{{ $t('settings.appearance.slimSidebar') }}</span>
|
||||
</label>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons">folder</i>{{ $t('settings.downloadPath.title') }}
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<div class="flex items-center">
|
||||
<input autocomplete="off" type="text" v-model="settings.downloadLocation" />
|
||||
<button
|
||||
id="select_downloads_folder"
|
||||
|
@ -116,7 +116,7 @@
|
|||
</h3>
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createPlaylistFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createPlaylistFolder') }}</span>
|
||||
</label>
|
||||
|
@ -126,7 +126,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createArtistFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createArtistFolder') }}</span>
|
||||
</label>
|
||||
|
@ -137,7 +137,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="settings-container__third">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createAlbumFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createAlbumFolder') }}</span>
|
||||
</label>
|
||||
|
@ -149,17 +149,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createCDFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createCDFolder') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createStructurePlaylist" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createStructurePlaylist') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createSingleFolder" />
|
||||
<span class="checkbox_text">{{ $t('settings.folders.createSingleFolder') }}</span>
|
||||
</label>
|
||||
|
@ -172,7 +172,7 @@
|
|||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.padTracks" />
|
||||
<span class="checkbox_text">{{ $t('settings.trackTitles.padTracks') }}</span>
|
||||
</label>
|
||||
|
@ -224,34 +224,34 @@
|
|||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.fallbackBitrate" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.fallbackBitrate') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.fallbackSearch" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.fallbackSearch') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.logErrors" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.logErrors') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.logSearched" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.logSearched') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-container__third settings-container__third--only-checkbox">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.syncedLyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.syncedLyrics') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.createM3U8File" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.createM3U8File') }}</span>
|
||||
</label>
|
||||
|
@ -263,7 +263,7 @@
|
|||
<input type="text" v-model="settings.playlistFilenameTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.saveDownloadQueue" />
|
||||
<span class="checkbox_text">{{ $t('settings.downloads.saveDownloadQueue') }}</span>
|
||||
</label>
|
||||
|
@ -274,7 +274,7 @@
|
|||
<i class="material-icons">album</i>{{ $t('settings.covers.title') }}
|
||||
</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtwork" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.saveArtwork') }}</span>
|
||||
</label>
|
||||
|
@ -284,7 +284,7 @@
|
|||
<input type="text" v-model="settings.coverImageTemplate" />
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.saveArtworkArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.saveArtworkArtist') }}</span>
|
||||
</label>
|
||||
|
@ -319,7 +319,7 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.embeddedArtworkPNG" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.embeddedArtworkPNG') }}</span>
|
||||
</label>
|
||||
|
@ -327,7 +327,7 @@
|
|||
⚠️ {{ $t('settings.covers.embeddedPNGWarning') }}
|
||||
</p>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.coverDescriptionUTF8" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.coverDescriptionUTF8') }}</span>
|
||||
</label>
|
||||
|
@ -345,106 +345,106 @@
|
|||
|
||||
<div class="settings-container">
|
||||
<div class="settings-container__half">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.title" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.title') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.artist" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.artist') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.album" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.album') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.cover" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.cover') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackNumber" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.trackNumber') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.trackTotal" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.trackTotal') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discNumber" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.discNumber') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.discTotal" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.discTotal') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.albumArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.albumArtist') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.genre" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.genre') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.year" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.year') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.date" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.date') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.explicit" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.explicit') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-container__half">
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.isrc" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.isrc') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.length" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.length') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.barcode" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.barcode') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.bpm" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.bpm') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.replayGain" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.replayGain') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.label" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.label') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.lyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.lyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.syncedLyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.syncedLyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.copyright" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.copyright') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.composer" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.composer') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.involvedPeople" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.involvedPeople') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.source" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.source') }}</span>
|
||||
</label>
|
||||
|
@ -457,17 +457,17 @@
|
|||
<i class="material-icons">list</i>{{ $t('settings.other.title') }}
|
||||
</h3>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.savePlaylistAsCompilation" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.savePlaylistAsCompilation') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.useNullSeparator" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.useNullSeparator') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.saveID3v1" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.saveID3v1') }}</span>
|
||||
</label>
|
||||
|
@ -488,22 +488,22 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.singleAlbumArtist" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.singleAlbumArtist') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.albumVariousArtists" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.albumVariousArtists') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.removeAlbumVersion" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.removeAlbumVersion') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<label class="with-checkbox">
|
||||
<input type="checkbox" v-model="settings.removeDuplicateArtists" />
|
||||
<span class="checkbox_text">{{ $t('settings.other.removeDuplicateArtists') }}</span>
|
||||
</label>
|
||||
|
@ -643,7 +643,7 @@
|
|||
|
||||
.locale-flag {
|
||||
width: 60px;
|
||||
display: inline-flex;
|
||||
display: flex items-center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
@ -724,10 +724,6 @@ export default {
|
|||
set(wantSlimSidebar) {
|
||||
this.slimSidebar = wantSlimSidebar
|
||||
document.getElementById('sidebar').classList.toggle('slim', wantSlimSidebar)
|
||||
// Moves all toast messages when the option changes
|
||||
Array.from(document.getElementsByClassName('toastify')).forEach((toast)=>{
|
||||
toast.style.transform = `translate(${wantSlimSidebar ? '3rem' : '14rem'}, 0)`;
|
||||
})
|
||||
localStorage.setItem('slimSidebar', wantSlimSidebar)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
'linear-gradient(to bottom, transparent 0%, var(--main-background) 100%), url(\'' + image + '\')'
|
||||
}"
|
||||
>
|
||||
<h1 class="inline-flex m-0 text-5xl">
|
||||
{{ title }} <i v-if="explicit" class="material-icons explicit_icon explicit_icon--right">explicit</i>
|
||||
<h1 class="flex items-center m-0 text-5xl">
|
||||
{{ title }} <i v-if="explicit" class="material-icons explicit-icon explicit-icon--right">explicit</i>
|
||||
</h1>
|
||||
|
||||
<h2 class="m-0 mb-3 text-lg">
|
||||
|
@ -60,7 +60,7 @@
|
|||
</td>
|
||||
<td class="table__cell--large table__cell--with-icon">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit-icon"> explicit </i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
|
@ -126,8 +126,8 @@
|
|||
<i v-else class="material-icons disabled">play_arrow</i>
|
||||
</td>
|
||||
<td>{{ i + 1 }}</td>
|
||||
<td class="inline-flex">
|
||||
<i v-if="track.explicit" class="material-icons explicit_icon">explicit</i>
|
||||
<td class="flex items-center">
|
||||
<i v-if="track.explicit" class="material-icons explicit-icon">explicit</i>
|
||||
{{ track.name }}
|
||||
</td>
|
||||
<td>{{ track.artists[0].name }}</td>
|
||||
|
|
|
@ -1,51 +1,79 @@
|
|||
<template>
|
||||
<div id="album_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.albumTab.loaded" />
|
||||
<div v-else-if="results.albumTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsAlbum') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.albumTab.data.length > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.albumTab.data"
|
||||
:key="release.id"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Album', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
||||
{{ release.title }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
$t('globals.by', { artist: release.artist.name }) +
|
||||
' - ' +
|
||||
$tc('globals.listTabs.trackN', release.nb_tracks)
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<BaseLoadingPlaceholder v-if="isLoading" />
|
||||
|
||||
<template v-else>
|
||||
<div v-if="viewInfo.data.length === 0">
|
||||
<h1>{{ $t('search.noResultsAlbum') }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="release_grid" v-else>
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in viewInfo.data.slice(0, itemsToShow)"
|
||||
:key="release.albumID"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Album', params: { id: release.albumID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.albumCoverMedium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.albumLink"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text flex items-center">
|
||||
<i v-if="release.isAlbumExplicit" class="material-icons explicit-icon">explicit</i>
|
||||
{{ release.albumTitle }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
$t('globals.by', { artist: release.artistName }) +
|
||||
' - ' +
|
||||
$tc('globals.listTabs.trackN', release.albumTracks)
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
props: {
|
||||
viewInfo: {
|
||||
validator: function (value) {
|
||||
let isNull = Object.is(value, null)
|
||||
let isObject = Object.prototype.toString.call(value) === '[object Object]'
|
||||
|
||||
return isNull || isObject
|
||||
},
|
||||
required: true
|
||||
},
|
||||
itemsToShow: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
wantHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return !this.viewInfo || !this.viewInfo.hasLoaded
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
|
|
|
@ -1,221 +1,66 @@
|
|||
<template>
|
||||
<div id="main_search" class="search_tabcontent">
|
||||
<template v-for="section in results.allTab.ORDER">
|
||||
<section>
|
||||
<div v-if="!thereAreResults">
|
||||
<h1>{{ $t('search.noResults') }}</h1>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<section
|
||||
v-if="
|
||||
(section != 'TOP_RESULT' && results.allTab[section].data.length > 0) || results.allTab[section].length > 0
|
||||
"
|
||||
class="search_section"
|
||||
v-for="section in viewInfo.ORDER"
|
||||
:key="section"
|
||||
class="float-none py-5 border-grayscale-500 border-t first:border-t-0"
|
||||
>
|
||||
<h2
|
||||
@click="$emit('change-search-tab', section)"
|
||||
class="search_header"
|
||||
:class="{ top_result_header: section === 'TOP_RESULT' }"
|
||||
class="mb-6 capitalize"
|
||||
:class="{
|
||||
'text-4xl text-center': section === 'TOP_RESULT',
|
||||
'inline-block cursor-pointer text-3xl hover:text-primary transition-colors duration-200 ease-in-out':
|
||||
section !== 'TOP_RESULT'
|
||||
}"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${section.toLowerCase()}`, 2) }}
|
||||
</h2>
|
||||
<!-- Top result -->
|
||||
<router-link
|
||||
tag="div"
|
||||
v-if="section == 'TOP_RESULT'"
|
||||
class="top_result clickable"
|
||||
:to="{ name: upperCaseFirstLowerCaseRest(topResultType), params: { id: results.allTab.TOP_RESULT[0].id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
:src="results.allTab.TOP_RESULT[0].picture"
|
||||
:class="(results.allTab.TOP_RESULT[0].type == 'artist' ? 'circle' : 'rounded') + ' coverart'"
|
||||
/>
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="results.allTab.TOP_RESULT[0].link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="info_box">
|
||||
<p class="primary-text">{{ results.allTab.TOP_RESULT[0].title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ fansNumber }}
|
||||
</p>
|
||||
<span class="tag">{{ $tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1) }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<div v-else-if="section == 'TRACK'">
|
||||
<table class="table table--tracks">
|
||||
<tbody>
|
||||
<tr v-for="track in results.allTab.TRACK.data.slice(0, 6)" :key="track.SNG_ID">
|
||||
<td class="table__icon" aria-hidden="true">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' + track.ALB_PICTURE + '/32x32-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.EXPLICIT_LYRICS == 1" class="material-icons explicit_icon"> explicit </i>
|
||||
{{ track.SNG_TITLE + (track.VERSION ? ' ' + track.VERSION : '') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell table__cell--medium table__cell--center breakline">
|
||||
<router-link
|
||||
tag="span"
|
||||
v-for="artist in track.ARTISTS"
|
||||
:key="artist.ART_ID"
|
||||
class="clickable"
|
||||
:to="{
|
||||
name: 'Artist',
|
||||
params: { id: artist.ART_ID }
|
||||
}"
|
||||
>
|
||||
{{ artist.ART_NAME }}
|
||||
</router-link>
|
||||
</td>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Album', params: { id: track.ALB_ID } }"
|
||||
>
|
||||
{{ track.ALB_TITLE }}
|
||||
</router-link>
|
||||
<td class="table__cell table__cell--center">
|
||||
{{ convertDuration(track.DURATION) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> get_app </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="section == 'ARTIST'" class="release_grid firstrow_only">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.allTab.ARTIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
:key="release.ART_ID"
|
||||
:to="{ name: 'Artist', params: { id: release.ART_ID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="circle coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/artist/' + release.ART_PICTURE + '/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.ART_NAME }}</p>
|
||||
<p class="secondary-text">{{ $t('search.fans', { n: $n(release.NB_FAN) }) }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-else-if="section == 'ALBUM'" class="release_grid firstrow_only">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.allTab.ALBUM.data.slice(0, 10)"
|
||||
:key="release.ALB_ID"
|
||||
class="release clickable"
|
||||
:to="{ name: 'Album', params: { id: release.ALB_ID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' + release.ALB_PICTURE + '/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i
|
||||
v-if="[1, 4].indexOf(release.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS) != -1"
|
||||
class="material-icons explicit_icon"
|
||||
>explicit</i
|
||||
>
|
||||
{{ release.ALB_TITLE }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.allTab.PLAYLIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
:key="release.PLAYLIST_ID"
|
||||
:to="{ name: 'Playlist', params: { id: release.PLAYLIST_ID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/' +
|
||||
release.PICTURE_TYPE +
|
||||
'/' +
|
||||
release.PLAYLIST_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.TITLE }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.trackN', release.NB_SONG) }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<TopResult
|
||||
v-if="section === 'TOP_RESULT'"
|
||||
:info="viewInfo.TOP_RESULT[0]"
|
||||
@add-to-queue="$emit('add-to-queue', $event)"
|
||||
/>
|
||||
|
||||
<ResultsTracks
|
||||
v-else-if="section === 'TRACK'"
|
||||
:viewInfo="reduceSearchResults(viewInfo.TRACK, formatSingleTrack)"
|
||||
:itemsToShow="6"
|
||||
@add-to-queue="$emit('add-to-queue', $event)"
|
||||
/>
|
||||
|
||||
<ResultsAlbums
|
||||
v-else-if="section == 'ALBUM'"
|
||||
:viewInfo="reduceSearchResults(viewInfo.ALBUM, formatAlbums)"
|
||||
:itemsToShow="6"
|
||||
@add-to-queue="$emit('add-to-queue', $event)"
|
||||
/>
|
||||
|
||||
<ResultsPlaylists
|
||||
v-else-if="section == 'PLAYLIST'"
|
||||
:viewInfo="reduceSearchResults(viewInfo.PLAYLIST, formatPlaylist)"
|
||||
:itemsToShow="6"
|
||||
@add-to-queue="$emit('add-to-queue', $event)"
|
||||
/>
|
||||
|
||||
<ResultsArtists
|
||||
v-else-if="section === 'ARTIST'"
|
||||
:viewInfo="reduceSearchResults(viewInfo.ARTIST, formatArtist)"
|
||||
:itemsToShow="6"
|
||||
@add-to-queue="$emit('add-to-queue', $event)"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
<div v-if="noResults">
|
||||
<h1>{{ $t('search.noResults') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style scoped>
|
||||
.tag {
|
||||
background-color: var(--tag-background);
|
||||
border-radius: 2px;
|
||||
|
@ -230,39 +75,66 @@
|
|||
<script>
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
import { upperCaseFirstLowerCaseRest } from '@/utils/texts'
|
||||
import TopResult from '@/components/search/TopResult.vue'
|
||||
import ResultsTracks from '@components/search/ResultsTracks.vue'
|
||||
import ResultsAlbums from '@components/search/ResultsAlbums.vue'
|
||||
import ResultsArtists from '@components/search/ResultsArtists.vue'
|
||||
import ResultsPlaylists from '@components/search/ResultsPlaylists.vue'
|
||||
|
||||
import { reduceSearchResults, formatSingleTrack, formatAlbums, formatArtist, formatPlaylist } from '@/data/search'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
TopResult,
|
||||
ResultsTracks,
|
||||
ResultsAlbums,
|
||||
ResultsArtists,
|
||||
ResultsPlaylists
|
||||
},
|
||||
props: {
|
||||
viewInfo: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
topResultType() {
|
||||
return this.results.allTab.TOP_RESULT[0].type
|
||||
},
|
||||
noResults() {
|
||||
return this.results.allTab.ORDER.every(section =>
|
||||
section == 'TOP_RESULT'
|
||||
? this.results.allTab[section].length == 0
|
||||
: this.results.allTab[section].data.length == 0
|
||||
thereAreResults() {
|
||||
let areInfosLoaded = !!this.viewInfo
|
||||
|
||||
if (!areInfosLoaded) {
|
||||
return false
|
||||
}
|
||||
|
||||
let noResultsPresent = this.viewInfo.ORDER.every(section =>
|
||||
section === 'TOP_RESULT' ? this.viewInfo[section].length === 0 : this.viewInfo[section].data.length === 0
|
||||
)
|
||||
|
||||
return !noResultsPresent
|
||||
},
|
||||
fansNumber() {
|
||||
let number
|
||||
|
||||
try {
|
||||
number = this.$n(this.results.allTab.TOP_RESULT[0].nb_fan)
|
||||
number = this.$n(this.viewInfo.TOP_RESULT[0].nb_fan)
|
||||
} catch (error) {
|
||||
number = this.$n(this.results.allTab.TOP_RESULT[0].nb_fan, { locale: 'en' })
|
||||
number = this.$n(this.viewInfo.TOP_RESULT[0].nb_fan, { locale: 'en' })
|
||||
}
|
||||
|
||||
return this.results.allTab.TOP_RESULT[0].type == 'artist'
|
||||
return this.viewInfo.TOP_RESULT[0].type == 'artist'
|
||||
? this.$t('search.fans', { n: number })
|
||||
: this.$t('globals.by', { artist: this.results.allTab.TOP_RESULT[0].artist }) +
|
||||
: this.$t('globals.by', { artist: this.viewInfo.TOP_RESULT[0].artist }) +
|
||||
' - ' +
|
||||
this.$tc('globals.listTabs.trackN', this.results.allTab.TOP_RESULT[0].nb_song)
|
||||
this.$tc('globals.listTabs.trackN', this.viewInfo.TOP_RESULT[0].nb_song)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
upperCaseFirstLowerCaseRest
|
||||
upperCaseFirstLowerCaseRest,
|
||||
reduceSearchResults,
|
||||
formatSingleTrack,
|
||||
formatAlbums,
|
||||
formatArtist,
|
||||
formatPlaylist
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,44 +1,72 @@
|
|||
<template>
|
||||
<div id="artist_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.artistTab.loaded"></BaseLoadingPlaceholder>
|
||||
<div v-else-if="results.artistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsArtist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.artistTab.data.length > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.artistTab.data"
|
||||
class="release clickable"
|
||||
:key="release.id"
|
||||
:to="{ name: 'Artist', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.releaseN', release.nb_album) }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<BaseLoadingPlaceholder v-if="isLoading" />
|
||||
|
||||
<template v-else>
|
||||
<div v-if="viewInfo.data.length === 0">
|
||||
<h1>{{ $t('search.noResultsArtist') }}</h1>
|
||||
</div>
|
||||
|
||||
<div v-else class="release_grid">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in viewInfo.data.slice(0, itemsToShow)"
|
||||
class="release clickable"
|
||||
:key="release.artistID"
|
||||
:to="{ name: 'Artist', params: { id: release.artistID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.artistPictureMedium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.artistLink"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.artistName }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.releaseN', release.artistAlbumsNumber) }}</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
props: {
|
||||
viewInfo: {
|
||||
validator: function (value) {
|
||||
let isNull = Object.is(value, null)
|
||||
let isObject = Object.prototype.toString.call(value) === '[object Object]'
|
||||
|
||||
return isNull || isObject
|
||||
},
|
||||
required: true
|
||||
},
|
||||
itemsToShow: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
wantHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return !this.viewInfo || !this.viewInfo.hasLoaded
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,48 +1,78 @@
|
|||
<template>
|
||||
<div id="playlist_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.playlistTab.loaded" />
|
||||
<div v-else-if="results.playlistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsPlaylist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.playlistTab.data.length > 0">
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="release in results.playlistTab.data"
|
||||
class="release clickable"
|
||||
:key="release.id"
|
||||
:to="{ name: 'Playlist', params: { id: release.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.user.name })} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}`
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<BaseLoadingPlaceholder v-if="isLoading" />
|
||||
|
||||
<template v-else>
|
||||
<div v-if="viewInfo.data.length === 0">
|
||||
<h1>{{ $t('search.noResultsPlaylist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-else>
|
||||
<router-link
|
||||
tag="div"
|
||||
v-for="playlist in viewInfo.data.slice(0, itemsToShow)"
|
||||
class="release clickable"
|
||||
:key="playlist.playlistID"
|
||||
:to="{ name: 'Playlist', params: { id: playlist.playlistID } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="playlist.playlistPictureMedium" />
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="playlist.playlistLink"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="primary-text">{{ playlist.playlistTitle }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: playlist.artistName })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
playlist.playlistTracksNumber
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
props: {
|
||||
viewInfo: {
|
||||
validator: function (value) {
|
||||
let isNull = Object.is(value, null)
|
||||
let isObject = Object.prototype.toString.call(value) === '[object Object]'
|
||||
|
||||
return isNull || isObject
|
||||
},
|
||||
required: true
|
||||
},
|
||||
itemsToShow: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
wantHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return !this.viewInfo || !this.viewInfo.hasLoaded
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,75 +1,77 @@
|
|||
<template>
|
||||
<div id="track_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.trackTab.loaded" />
|
||||
<div v-else-if="results.trackTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsTrack') }}</h1>
|
||||
</div>
|
||||
<table class="table table--tracks" v-if="results.trackTab.data.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.album', 1) }}</th>
|
||||
<th>
|
||||
<i class="material-icons"> timer </i>
|
||||
</th>
|
||||
<th style="width: 56px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="track in results.trackTab.data">
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
class="rounded"
|
||||
:class="{ 'single-cover': !!track.preview }"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<PreviewControls v-if="track.preview" />
|
||||
<section>
|
||||
<BaseLoadingPlaceholder v-if="isLoading" />
|
||||
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Artist', params: { id: track.artist.id } }"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Album', params: { id: track.album.id } }"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</router-link>
|
||||
<td class="table__cell table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> get_app </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="viewInfo.data.length === 0">
|
||||
<h1>{{ $t('search.noResultsTrack') }}</h1>
|
||||
</div>
|
||||
|
||||
<table v-else class="table table--tracks">
|
||||
<thead v-if="wantHeaders">
|
||||
<tr class="capitalize">
|
||||
<th colspan="2">{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.album', 1) }}</th>
|
||||
<th>
|
||||
<i class="material-icons">timer</i>
|
||||
</th>
|
||||
<th style="width: 3.5rem"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="track in viewInfo.data.slice(0, itemsToShow)" :key="track.trackLink">
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
class="rounded"
|
||||
:class="{ 'single-cover': !!track.trackPreview }"
|
||||
:data-preview="track.trackPreview"
|
||||
>
|
||||
<PreviewControls v-if="track.trackPreview" />
|
||||
|
||||
<img class="rounded coverart" :src="track.albumPicture" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.isTrackExplicit" class="material-icons explicit-icon">explicit</i>
|
||||
{{ getTitle(track) }}
|
||||
</div>
|
||||
</td>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Artist', params: { id: track.artistID } }"
|
||||
>
|
||||
{{ track.artistName }}
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="td"
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
:to="{ name: 'Album', params: { id: track.albumID } }"
|
||||
>
|
||||
{{ track.albumTitle }}
|
||||
</router-link>
|
||||
<td class="table__cell table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.trackDuration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="track.trackLink"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -80,15 +82,44 @@ import EventBus from '@/utils/EventBus'
|
|||
import { convertDuration } from '@/utils/utils'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder,
|
||||
PreviewControls
|
||||
},
|
||||
props: {
|
||||
viewInfo: {
|
||||
validator: function (value) {
|
||||
let isNull = Object.is(value, null)
|
||||
let isObject = Object.prototype.toString.call(value) === '[object Object]'
|
||||
|
||||
return isNull || isObject
|
||||
},
|
||||
required: true
|
||||
},
|
||||
itemsToShow: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
wantHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return !this.viewInfo || !this.viewInfo.hasLoaded
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
getTitle(track) {
|
||||
const hasTitleVersion = track.trackTitleVersion && track.trackTitle.indexOf(track.trackTitleVersion) === -1
|
||||
|
||||
return `${track.trackTitle}${hasTitleVersion ? ` ${track.trackTitleVersion}` : ''}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
src/components/search/TopResult.vue
Normal file
73
src/components/search/TopResult.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<router-link
|
||||
tag="div"
|
||||
class="top_result cursor-pointer flex items-center flex-col"
|
||||
:to="{ name: upperCaseFirstLowerCaseRest($attrs.info.type), params: { id: $attrs.info.id } }"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="coverart"
|
||||
:src="$attrs.info.picture"
|
||||
:class="$attrs.info.type == 'artist' ? 'circle' : 'rounded'"
|
||||
/>
|
||||
|
||||
<button
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="$attrs.info.link"
|
||||
class="download_overlay"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="info_box">
|
||||
<p class="primary-text">{{ $attrs.info.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ fansNumber }}
|
||||
</p>
|
||||
<span class="tag">{{ $tc(`globals.listTabs.${$attrs.info.type}`, 1) }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tag {
|
||||
background-color: var(--tag-background);
|
||||
border-radius: 2px;
|
||||
color: var(--tag-text);
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
padding: 3px 6px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { upperCaseFirstLowerCaseRest } from '@/utils/texts'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
upperCaseFirstLowerCaseRest
|
||||
},
|
||||
computed: {
|
||||
fansNumber() {
|
||||
let number
|
||||
|
||||
try {
|
||||
number = this.$n(this.$attrs.info.nb_fan)
|
||||
} catch (error) {
|
||||
number = this.$n(this.$attrs.info.nb_fan, { locale: 'en' })
|
||||
}
|
||||
|
||||
return this.$attrs.info.type == 'artist'
|
||||
? this.$t('search.fans', { n: number })
|
||||
: this.$t('globals.by', { artist: this.$attrs.info.artist }) +
|
||||
' - ' +
|
||||
this.$tc('globals.listTabs.trackN', this.$attrs.info.nb_song)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
117
src/data/search.js
Normal file
117
src/data/search.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { getProperty } from '@/utils/utils'
|
||||
|
||||
/**
|
||||
* @typedef {object} ReducedSearchResult
|
||||
* @property {FormattedData} data
|
||||
* @property {boolean} hasLoaded
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} FormattedData
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {function} Formatter
|
||||
* @returns {FormattedData} formattedData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reduces passed data to a specific format decied by the formatter passed.
|
||||
*
|
||||
* @param {object} rawObj
|
||||
* @param {Formatter} formatFunc
|
||||
* @returns {null|ReducedSearchResult}
|
||||
*/
|
||||
export function reduceSearchResults(rawObj, formatFunc) {
|
||||
if (!rawObj.hasLoaded) {
|
||||
return null
|
||||
} else {
|
||||
const { data: rawData } = rawObj
|
||||
const formattedData = []
|
||||
|
||||
for (const dataElement of rawData) {
|
||||
let formatted = formatFunc(dataElement)
|
||||
|
||||
formattedData.push(formatted)
|
||||
}
|
||||
|
||||
return {
|
||||
data: formattedData,
|
||||
hasLoaded: rawObj.hasLoaded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FormattedData} track
|
||||
*/
|
||||
export function formatSingleTrack(track) {
|
||||
return {
|
||||
/* Track */
|
||||
trackTitle: getProperty(track, 'title', 'SNG_TITLE'),
|
||||
trackTitleVersion: getProperty(track, 'title_version', 'VERSION'),
|
||||
trackPreview: getProperty(track, 'preview'),
|
||||
trackDuration: getProperty(track, 'duration', 'DURATION'),
|
||||
trackLink: getProperty(track, 'link') || `https://www.deezer.com/track/${track.SNG_ID}`,
|
||||
isTrackExplicit: getProperty(track, 'explicit_lyrics', 'EXPLICIT_LYRICS'),
|
||||
|
||||
/* Artist */
|
||||
artistID: getProperty(track, 'artist.id', 'ART_ID'),
|
||||
artistName: getProperty(track, 'artist.name', 'ART_NAME'),
|
||||
|
||||
/* Album */
|
||||
albumID: getProperty(track, 'album.id', 'ALB_ID'),
|
||||
albumTitle: getProperty(track, 'album.title', 'ALB_TITLE'),
|
||||
albumPicture:
|
||||
getProperty(track, 'album.cover_small') ||
|
||||
`https://e-cdns-images.dzcdn.net/images/cover/${track.ALB_PICTURE}/32x32-000000-80-0-0.jpg`
|
||||
}
|
||||
}
|
||||
|
||||
export function formatAlbums(album) {
|
||||
return {
|
||||
/* Album */
|
||||
albumID: getProperty(album, 'id', 'ALB_ID'),
|
||||
albumTitle: getProperty(album, 'title', 'ALB_TITLE'),
|
||||
albumCoverMedium:
|
||||
getProperty(album, 'cover_medium') ||
|
||||
`https://e-cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/156x156-000000-80-0-0.jpg`,
|
||||
albumLink: getProperty(album, 'link') || `https://deezer.com/album/${album.ALB_ID}`,
|
||||
albumTracks: getProperty(album, 'nb_tracks', 'NUMBER_TRACK'),
|
||||
isAlbumExplicit: getProperty(album, 'explicit_lyrics', 'EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS'),
|
||||
|
||||
/* Artist */
|
||||
artistName: getProperty(album, 'artist.name', 'ART_NAME')
|
||||
}
|
||||
}
|
||||
|
||||
export function formatArtist(artist) {
|
||||
return {
|
||||
/* Artist */
|
||||
artistID: getProperty(artist, 'id', 'ART_ID'),
|
||||
artistName: getProperty(artist, 'name', 'ART_NAME'),
|
||||
artistPictureMedium:
|
||||
getProperty(artist, 'picture_medium') ||
|
||||
`https://e-cdns-images.dzcdn.net/images/artist/${artist.ART_PICTURE}/156x156-000000-80-0-0.jpg`,
|
||||
artistLink: getProperty(artist, 'link') || `https://deezer.com/artist/${artist.ART_ID}`,
|
||||
artistAlbumsNumber: getProperty(artist, 'nb_album', 'NB_FAN')
|
||||
}
|
||||
}
|
||||
|
||||
export function formatPlaylist(playlist) {
|
||||
return {
|
||||
/* Playlist */
|
||||
playlistID: getProperty(playlist, 'id', 'PLAYLIST_ID'),
|
||||
playlistTitle: getProperty(playlist, 'title', 'TITLE'),
|
||||
playlistPictureMedium:
|
||||
getProperty(playlist, 'picture_medium') ||
|
||||
`https://e-cdns-images.dzcdn.net/images/${playlist.PICTURE_TYPE}/${
|
||||
playlist.PLAYLIST_PICTURE
|
||||
}/156x156-000000-80-0-0.jpg`,
|
||||
playlistLink: getProperty(playlist, 'link') || `https://deezer.com/playlist/${playlist.PLAYLIST_ID}`,
|
||||
playlistTracksNumber: getProperty(playlist, 'nb_tracks', 'NB_SONG'),
|
||||
|
||||
/* Artist */
|
||||
artistName: getProperty(playlist, 'user.name')
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ const en = {
|
|||
back: 'back',
|
||||
loading: 'loading',
|
||||
download: 'Download {thing}',
|
||||
downloadAll: 'Download all {thing}',
|
||||
by: 'by {artist}',
|
||||
in: 'in {album}',
|
||||
download_hint: 'Download',
|
||||
|
@ -27,6 +28,7 @@ const en = {
|
|||
single: 'single | singles',
|
||||
title: 'title | titles',
|
||||
track: 'track | tracks',
|
||||
trackN: '0 tracks | {n} track | {n} tracks',
|
||||
releaseN: '0 releases | {n} release | {n} releases',
|
||||
playlist: 'playlist | playlists',
|
||||
compile: 'compilation | compilations',
|
||||
|
@ -36,11 +38,7 @@ const en = {
|
|||
featured: 'Featured in',
|
||||
spotifyPlaylist: 'spotify playlist | spotify playlists',
|
||||
releaseDate: 'release date',
|
||||
error: 'error',
|
||||
trackN: '0 tracks | {n} track | {n} tracks',
|
||||
albumN: '0 albums | {n} album | {n} albums',
|
||||
artistN: '0 artists | {n} artist | {n} artists',
|
||||
playlistN: '0 playlists | {n} playlist | {n} playlists',
|
||||
error: 'error'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
|
|
|
@ -4,6 +4,7 @@ const fr = {
|
|||
back: 'retour',
|
||||
loading: 'chargement en cours',
|
||||
download: 'Télécharger {thing}',
|
||||
downloadAll: "Télécharger l'intégralité des {thing}",
|
||||
by: 'par {artist}',
|
||||
in: 'dans {album}',
|
||||
download_hint: 'Télécharger',
|
||||
|
@ -27,6 +28,7 @@ const fr = {
|
|||
single: 'single | singles',
|
||||
title: 'titre | titres',
|
||||
track: 'piste | pistes',
|
||||
trackN: '0 piste | {n} piste | {n} pistes',
|
||||
releaseN: '0 sortie | {n} sortie | {n} sorties',
|
||||
playlist: 'playlist | playlists',
|
||||
compile: 'compilation | compilations',
|
||||
|
@ -36,11 +38,7 @@ const fr = {
|
|||
featured: 'Apparaît dans',
|
||||
spotifyPlaylist: 'playlist spotify | playlists spotify',
|
||||
releaseDate: 'date de sortie',
|
||||
error: 'erreur',
|
||||
trackN: '0 piste | {n} piste | {n} pistes',
|
||||
albumN: '0 album | {n} album | {n} albums',
|
||||
artistN: '0 artiste | {n} artiste | {n} artistes',
|
||||
playlistN: '0 playlist | {n} playlist | {n} playlists'
|
||||
error: 'erreur'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
|
@ -201,8 +199,7 @@ const fr = {
|
|||
},
|
||||
appearance: {
|
||||
title: 'Apparence',
|
||||
slimDownloadTab: 'Onglet de téléchargement compact',
|
||||
slimSidebar: 'Barre latérale compacte'
|
||||
slimDownloadTab: 'Onglet de téléchargement compact'
|
||||
},
|
||||
downloadPath: {
|
||||
title: 'Emplacement De Téléchargement'
|
||||
|
@ -302,8 +299,7 @@ const fr = {
|
|||
syncedLyrics: 'Paroles Synchronisées',
|
||||
copyright: "Droits d'Auteur (Copyright)",
|
||||
composer: 'Compositeur',
|
||||
involvedPeople: 'Personnes Impliquées',
|
||||
source: 'ID de la source et de la piste'
|
||||
involvedPeople: 'Personnes Impliquées'
|
||||
},
|
||||
other: {
|
||||
title: 'Autre',
|
||||
|
|
|
@ -4,6 +4,7 @@ const it = {
|
|||
back: 'indietro',
|
||||
loading: 'caricamento',
|
||||
download: 'Scarica {thing}',
|
||||
downloadAll: 'Scarica ogni {thing}',
|
||||
by: 'di {artist}',
|
||||
in: 'in {album}',
|
||||
download_hint: 'Scarica',
|
||||
|
@ -26,6 +27,7 @@ const it = {
|
|||
single: 'singolo | singoli',
|
||||
title: 'titolo | titoli',
|
||||
track: 'brano | brani',
|
||||
trackN: '0 brani | {n} brano | {n} brani',
|
||||
releaseN: '0 dischi | {n} disco | {n} dischi',
|
||||
playlist: 'playlist',
|
||||
compile: 'compilation',
|
||||
|
@ -36,11 +38,7 @@ const it = {
|
|||
spotifyPlaylist: 'playlist spotify',
|
||||
releaseDate: 'data di uscita',
|
||||
error: 'errore',
|
||||
empty: '',
|
||||
trackN: '0 brani | {n} brano | {n} brani',
|
||||
albumN: '{n} album',
|
||||
artistN: '0 artisti | {n} artista | {n} artisti',
|
||||
playlistN: '{n} playlist',
|
||||
empty: ''
|
||||
}
|
||||
},
|
||||
about: {
|
||||
|
|
7
src/styles/css/helpers.css
Normal file
7
src/styles/css/helpers.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.changing-theme {
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
|
@ -25,3 +25,22 @@
|
|||
font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.material-icons.explicit-icon {
|
||||
margin-right: 0.3125em;
|
||||
margin-left: -3px;
|
||||
color: hsl(240, 5%, 59%);
|
||||
}
|
||||
|
||||
.material-icons.explicit-icon.explicit-icon--right {
|
||||
margin-right: 0px;
|
||||
margin-left: 0.3125em;
|
||||
}
|
||||
|
||||
.material-icons.disabled {
|
||||
@apply opacity-50 cursor-default;
|
||||
}
|
||||
|
||||
.material-icons.mirrored {
|
||||
transform: scaleX(-1);
|
||||
}
|
|
@ -6,33 +6,3 @@
|
|||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate {
|
||||
0% {
|
||||
left: -35%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate-short {
|
||||
0% {
|
||||
left: -200%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
100% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
// Breakpoints
|
||||
// TODO Change them in more proper values
|
||||
$small: 601px;
|
||||
$medium: 993px;
|
||||
|
||||
// Static variables (not an oxymoron)
|
||||
$explicit-separator: 0.3125em;
|
|
@ -2,55 +2,55 @@ input[type='text'],
|
|||
input[type='password'],
|
||||
input[type='number'] {
|
||||
appearance: none;
|
||||
width: calc(100% - 16px);
|
||||
margin-bottom: 8px;
|
||||
border: 0px solid black;
|
||||
line-height: 36px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--secondary-background);
|
||||
padding: 0px 8px;
|
||||
width: calc(100% - 16px);
|
||||
line-height: 36px;
|
||||
color: var(--foreground);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
appearance: none;
|
||||
background-color: none;
|
||||
border: 2px solid gray;
|
||||
opacity: 0.5;
|
||||
border-radius: 2px;
|
||||
padding: 7px;
|
||||
margin: 3px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
margin: 3px;
|
||||
border: 2px solid gray;
|
||||
border-radius: 2px;
|
||||
background-color: none;
|
||||
padding: 7px;
|
||||
|
||||
&:checked {
|
||||
opacity: 1;
|
||||
margin: 3px;
|
||||
border: 0px solid var(--primary-color);
|
||||
border-radius: 2px;
|
||||
background-color: var(--primary-color);
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='18' viewBox='3 3 18 18' width='18'%3E%3Cpath fill='%23ffffff' d='M 10,17 5,12 6.41,10.59 10,14.17 17.59,6.58 19,8 Z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");
|
||||
background-position: center center;
|
||||
border: 0px solid var(--primary-color);
|
||||
border-radius: 2px;
|
||||
padding: 9px;
|
||||
margin: 3px;
|
||||
color: var(--primary-text);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
border: 0px solid black;
|
||||
line-height: 36px;
|
||||
padding: 0px 40px 0px 8px;
|
||||
border-radius: 4px;
|
||||
background-clip: border-box;
|
||||
background-color: var(--secondary-background);
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 0 24 24' width='24'%3E%3Cpath style='fill%3A%23000000%3Bfill-opacity%3A0.25' d='M7 10l5 5 5-5z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");
|
||||
background-position: calc(100% - 8px) center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 24px;
|
||||
background-position: calc(100% - 8px) center;
|
||||
background-clip: border-box;
|
||||
padding: 0px 40px 0px 8px;
|
||||
width: 100%;
|
||||
line-height: 36px;
|
||||
color: var(--foreground);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -67,27 +67,9 @@ img {
|
|||
}
|
||||
}
|
||||
|
||||
i {
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.explicit_icon {
|
||||
color: hsl(240, 5%, 59%);
|
||||
margin-right: $explicit-separator;
|
||||
margin-left: -3px;
|
||||
|
||||
&.explicit_icon--right {
|
||||
margin-left: $explicit-separator;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.single-cover {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
@ -105,61 +87,42 @@ i {
|
|||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.table--tracklist .clickable:hover,
|
||||
.table--charts .clickable:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.with_checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox_text {
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.coverart {
|
||||
background-color: var(--secondary-background);
|
||||
}
|
||||
|
||||
// ? Maybe make a component?
|
||||
.cover_container {
|
||||
position: relative;
|
||||
|
||||
.coverart {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
backface-visibility: hidden;
|
||||
transition: 0.5s ease;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
transition: 0.5s ease;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.download_overlay {
|
||||
transition: 0.5s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
background-color: #000000;
|
||||
border-radius: 50%;
|
||||
min-width: 32px;
|
||||
padding: 0px;
|
||||
height: 44px;
|
||||
transition: 0.5s ease;
|
||||
opacity: 0;
|
||||
border: 0px;
|
||||
border-radius: 50%;
|
||||
background-color: #000000;
|
||||
padding: 0px;
|
||||
min-width: 32px;
|
||||
height: 44px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
color: white;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
|
@ -178,45 +141,3 @@ i {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
.inline-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.right {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.changing-theme {
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
@apply select-none;
|
||||
$sizes: 18, 24, 36, 48;
|
||||
|
||||
@each $size in $sizes {
|
||||
&.md-#{$size} {
|
||||
font-size: $size * 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.mirrored {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,9 +228,7 @@ $table-border-radius: 3px;
|
|||
}
|
||||
}
|
||||
|
||||
// @todo Remove
|
||||
.top-tracks-position {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
.table--tracklist .clickable:hover,
|
||||
.table--charts .clickable:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
@import '~tailwindcss/utilities';
|
||||
|
||||
@import './base/base';
|
||||
@import './base/variables';
|
||||
|
||||
html {
|
||||
height: 100vh;
|
||||
|
|
|
@ -1,47 +1,13 @@
|
|||
#main_search {
|
||||
.search_section {
|
||||
float: none;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid theme('colors.grayscale.500');
|
||||
}
|
||||
}
|
||||
|
||||
.top_result_header {
|
||||
display: block;
|
||||
cursor: default;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.search_header {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 25px;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:not(.top_result_header) {
|
||||
transition: color 200ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Top Result */
|
||||
.top_result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
@apply flex items-center flex-col;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// flex-direction: column;
|
||||
|
||||
> .cover_container {
|
||||
width: 156px;
|
||||
height: 156px;
|
||||
width: 9.75rem;
|
||||
height: 9.75rem;
|
||||
}
|
||||
|
||||
.info_box {
|
||||
|
@ -49,24 +15,24 @@
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
margin-top: 0.9375rem;
|
||||
|
||||
.primary-text,
|
||||
.secondary-text {
|
||||
font-size: 18px;
|
||||
font-size: 1.125rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-text {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 0.3125rem;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
width: 40px;
|
||||
width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
@ -76,17 +42,17 @@
|
|||
.release {
|
||||
.primary-text,
|
||||
.secondary-text {
|
||||
margin: 0px;
|
||||
margin-bottom: 4px;
|
||||
margin: 0rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
opacity: 0.75;
|
||||
font-size: 14px;
|
||||
font-size: 0.875rem;
|
||||
|
||||
.material-icons {
|
||||
font-size: 17px !important;
|
||||
margin-left: 4px;
|
||||
font-size: 1.0625rem !important;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +65,7 @@
|
|||
&.firstrow_only {
|
||||
grid-template-rows: 1fr;
|
||||
grid-auto-rows: 0;
|
||||
grid-row-gap: 0px;
|
||||
grid-row-gap: 0rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
.with-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox_text {
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Input group */
|
||||
.input_group {
|
||||
margin-bottom: 25px;
|
||||
|
@ -86,7 +101,7 @@
|
|||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.with_checkbox + & {
|
||||
.with-checkbox + & {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ import { socket } from '@/utils/socket'
|
|||
|
||||
const sharedOptions = {
|
||||
gravity: 'bottom',
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
offset: {
|
||||
x: '14rem'
|
||||
}
|
||||
}
|
||||
|
||||
let toastsWithId = {}
|
||||
|
@ -84,9 +87,6 @@ export const toast = function(msg, icon = null, dismiss = true, id = null) {
|
|||
delete toastsWithId[id]
|
||||
}
|
||||
}
|
||||
},
|
||||
offset: {
|
||||
x: 'true' === localStorage.getItem('slimSidebar') ? '3rem': '14rem'
|
||||
}
|
||||
}).showToast()
|
||||
if (id) {
|
||||
|
|
|
@ -95,6 +95,28 @@ export function copyToClipboard(text) {
|
|||
ghostInput.remove()
|
||||
}
|
||||
|
||||
export function getProperty(obj, ...props) {
|
||||
for (const prop of props) {
|
||||
// Example: this.is.an.example
|
||||
let hasDotNotation = /\./.test(prop)
|
||||
|
||||
// Searching the properties in the object
|
||||
let valueToTest = hasDotNotation
|
||||
? prop.split('.').reduce((o, i) => {
|
||||
if (o) {
|
||||
return o[i]
|
||||
}
|
||||
}, obj)
|
||||
: obj[prop]
|
||||
|
||||
if (!!valueToTest) {
|
||||
return valueToTest
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidURL,
|
||||
convertDuration,
|
||||
|
|
|
@ -48,7 +48,8 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
variants: {
|
||||
textColor: ({ after }) => after(['group-hover'])
|
||||
textColor: ({ after }) => after(['group-hover']),
|
||||
borderWidth: ['responsive', 'first', 'hover', 'focus']
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false
|
||||
|
|
Loading…
Reference in a new issue