Merge pull request 'update' (#2) from RemixDev/deemix-webui:main into main

Reviewed-on: https://codeberg.org/wolfwork/deemix-webui/pulls/2
This commit is contained in:
wolfwork 2020-10-05 16:18:04 +02:00
commit bf9706c2c1
52 changed files with 1002 additions and 646 deletions

View File

@ -1,19 +0,0 @@
{
"css": {
"allowed_file_extensions": [
"css",
"scss",
"sass",
"less"
],
"end_with_newline": true,
"indent_char": " ",
"indent_size": 2,
"indent_with_tabs": true,
"newline_between_rules": true,
"selector_separator": " ",
"selector_separator_newline": false,
"preserve_newlines": true,
"max_preserve_newlines": 3
}
}

View File

@ -11,6 +11,15 @@ You can find more informations about deemix at https://deemix.app/
- [deemix-pyweb](https://codeberg.org/RemixDev/deemix-pyweb)
- [deemix-tools](https://codeberg.org/RemixDev/deemix-tools)
# "Hidden" features
- `CTRL+SHIFT+Backspace` deletes all the search bar content
- `CTRL+F` focuses the search bar
- `CTRL+B` toggles the download bar
- `ALT+Left` goes back to the previous page, if present (like would happen in the browser)
- `ALT+Right` goes forward to the next page, if present (like would happen in the browser)
- Custom context menu: on certain elements, like download buttons or album covers, when opening the context menu, a custom one with more options will appear instead of the default one
# License
This program is free software: you can redistribute it and/or modify

5
package-lock.json generated
View File

@ -796,6 +796,11 @@
"pinkie-promise": "^2.0.0"
}
},
"flag-icon-css": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-3.5.0.tgz",
"integrity": "sha512-pgJnJLrtb0tcDgU1fzGaQXmR8h++nXvILJ+r5SmOXaaL/2pocunQo2a8TAXhjQnBpRLPtZ1KCz/TYpqeNuE2ew=="
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",

View File

@ -19,6 +19,7 @@
"build": "npm-run-all --sequential clean build:js build:styles"
},
"dependencies": {
"flag-icon-css": "^3.5.0",
"lodash-es": "^4.17.15",
"svg-country-flags": "^1.2.7",
"toastify-js": "^1.8.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,10 +31,6 @@ export default {
find: '@',
replacement: __dirname + '/src'
},
{
find: '@js',
replacement: __dirname + '/src/js'
},
{
find: '@components',
replacement: __dirname + '/src/components'

View File

@ -5,9 +5,9 @@
<div class="app-container">
<div class="content-container">
<TheSearchBar />
<TheMiddleSection />
<TheContent />
</div>
<TheDownloadTab class="downlaods" />
<TheDownloadBar />
</div>
<BaseLoadingPlaceholder id="start_app_placeholder" text="Connecting to the server..." />
@ -30,32 +30,30 @@
flex-direction: column;
margin-left: 48px;
}
.downlaods {
flex-basis: 32px;
}
</style>
<script>
import TheDownloadBar from '@components/downloads/TheDownloadBar.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
import TheContextMenu from '@components/globals/TheContextMenu.vue'
import TheTrackPreview from '@components/globals/TheTrackPreview.vue'
import TheQualityModal from '@components/globals/TheQualityModal.vue'
import TheSidebar from '@components/TheSidebar.vue'
import TheMiddleSection from '@components/TheMiddleSection.vue'
import TheTrackPreview from '@components/TheTrackPreview.vue'
import TheQualityModal from '@components/TheQualityModal.vue'
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import TheContextMenu from '@components/TheContextMenu.vue'
import TheDownloadTab from '@components/TheDownloadTab.vue'
import TheSearchBar from '@components/TheSearchBar.vue'
import TheContent from '@components/TheContent.vue'
export default {
components: {
TheSidebar,
TheSearchBar,
TheMiddleSection,
TheDownloadTab,
TheDownloadBar,
TheTrackPreview,
TheQualityModal,
BaseLoadingPlaceholder,
TheContextMenu
TheContextMenu,
TheContent
}
}
</script>

View File

@ -5,27 +5,18 @@ window.vol = {
preview_max_volume: 100
}
import App from '@components/App.vue'
import App from '@/App.vue'
import i18n from '@/plugins/i18n'
import router from '@/router'
import store from '@/store'
import { socket } from '@/utils/socket'
import { toast } from '@/utils/toasts'
import { init as initTabs } from '@js/tabs.js'
import { isValidURL } from '@/utils/utils'
import Downloads from '@/utils/downloads'
import EventBus from '@/utils/EventBus.js'
import { sendAddToQueue } from '@/utils/downloads'
/* ===== App initialization ===== */
function startApp() {
mountApp()
initTabs()
}
function mountApp() {
new Vue({
store,
router,
@ -36,6 +27,7 @@ function mountApp() {
function initClient() {
store.dispatch('setClientMode', true)
setClientModeKeyBindings()
}
document.addEventListener('DOMContentLoaded', startApp)
@ -44,23 +36,39 @@ window.addEventListener('pywebviewready', initClient)
/* ===== Global shortcuts ===== */
document.addEventListener('paste', pasteEvent => {
let pasteText = pasteEvent.clipboardData.getData('Text')
if (pasteEvent.target.localName === 'input') return
if (pasteEvent.target.localName != 'input') {
if (isValidURL(pasteText)) {
if (window.main_selected === 'analyzer_tab') {
EventBus.$emit('linkAnalyzerTab:reset')
socket.emit('analyzeLink', pasteText)
let pastedText = pasteEvent.clipboardData.getData('Text')
if (isValidURL(pastedText)) {
if (router.currentRoute.name === 'Link Analyzer') {
socket.emit('analyzeLink', pastedText)
} else {
Downloads.sendAddToQueue(pasteText)
sendAddToQueue(pastedText)
}
} else {
let searchbar = document.querySelector('#searchbar')
searchbar.select()
searchbar.setSelectionRange(0, 99999)
}
})
/**
* Sets up key bindings that already work in the browser (server mode)
*/
function setClientModeKeyBindings() {
document.addEventListener('keyup', keyEvent => {
// ALT + left
if (keyEvent.altKey && keyEvent.key === 'ArrowLeft') {
router.back()
}
// ALT + right
if (keyEvent.altKey && keyEvent.key === 'ArrowRight') {
router.forward()
}
})
}
/* ===== Socketio listeners ===== */
@ -113,11 +121,13 @@ socket.on('logged_in', function(data) {
break
case -1:
toast(i18n.t('toasts.deezerNotAvailable'), 'close', true, 'login-toast')
$('#open_login_prompt').show()
document.getElementById('logged_in_info').classList.add('hide')
$('#settings_username').text('Not Logged')
$('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
document.getElementById('home_not_logged_in').classList.remove('hide')
return
// TODO
// $('#open_login_prompt').show()
// document.getElementById('logged_in_info').classList.add('hide')
// $('#settings_username').text('Not Logged')
// $('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
// document.getElementById('home_not_logged_in').classList.remove('hide')
}
})

View File

@ -1,5 +1,10 @@
<template>
<section id="content" @scroll="$route.name === 'Search' ? handleContentScroll($event) : null" ref="content">
<main
id="content"
@scroll="$route.name === 'Search' ? handleContentScroll($event) : null"
ref="content"
aria-label="main content"
>
<div id="container">
<BaseLoadingPlaceholder id="search_placeholder" text="Searching..." :hidden="!loading" />
@ -21,7 +26,7 @@
exclude=""
></router-view>
</div>
</section>
</main>
</template>
<style lang="scss">
@ -31,8 +36,9 @@
width: var(--container-width);
}
#content {
main {
background-color: var(--main-background);
padding-right: 5px;
width: 100%;
height: calc(100vh - 93px);
overflow-y: scroll;
@ -57,8 +63,7 @@
<script>
import { debounce } from '@/utils/utils'
import EventBus from '@/utils/EventBus.js'
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
export default {
components: {
@ -72,6 +77,11 @@ export default {
this.$root.$on('updateSearchLoadingState', loading => {
this.loading = loading
})
this.$router.beforeEach((to, from, next) => {
this.$refs.content.scrollTo(0, 0)
next()
})
},
methods: {
handleContentScroll: debounce(async function () {

View File

@ -1,33 +0,0 @@
<template>
<main id="main_content">
<TheContent />
<!-- <BaseLoadingPlaceholder id="search_placeholder" text="Searching..." :hidden="true" /> -->
</main>
</template>
<style lang="scss" scoped>
#main_content {
background-color: var(--main-background);
min-width: 10px;
// margin-left: 48px; // $sidebar-width
// width: calc(100% - #{$sidebar-width});
// flex: 1;
width: 100%;
height: 100%;
}
</style>
<script>
import TheContent from '@components/TheContent.vue'
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
export default {
components: {
TheContent,
BaseLoadingPlaceholder
}
}
</script>
<style>
</style>

View File

@ -1,5 +1,5 @@
<template>
<header id="search">
<header id="search" aria-label="searchbar">
<div class="search__icon">
<i class="material-icons">search</i>
</div>
@ -19,11 +19,66 @@
</header>
</template>
<style lang="scss">
$icon-dimension: 2rem;
$searchbar-height: 45px;
#search {
background-color: var(--secondary-background);
padding: 0 1em;
display: flex;
align-items: center;
border: 1px solid transparent;
transition: border 200ms ease-in-out;
border-radius: 15px;
margin: 10px 10px 20px 10px;
&:focus-within {
border: 1px solid var(--foreground);
}
.search__icon {
width: $icon-dimension;
height: $icon-dimension;
i {
font-size: $icon-dimension;
color: var(--foreground);
}
}
#searchbar {
height: $searchbar-height;
padding-left: 0.5em;
border: 0px;
border-radius: 0px;
background-color: var(--secondary-background);
color: var(--foreground);
font-size: 1.2rem;
font-family: 'Open Sans';
font-weight: 300;
margin-bottom: 0;
&:focus {
outline: none;
}
// Removing Chrome autofill color
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 $searchbar-height var(--secondary-background) inset !important;
box-shadow: 0 0 0 $searchbar-height var(--secondary-background) inset !important;
}
}
}
</style>
<script>
import { isValidURL } from '@/utils/utils'
import Downloads from '@/utils/downloads'
import EventBus from '@/utils/EventBus.js'
import { sendAddToQueue } from '@/utils/downloads'
import EventBus from '@/utils/EventBus'
import { socket } from '@/utils/socket'
export default {
@ -32,12 +87,27 @@ export default {
lastTextSearch: ''
}
},
mounted() {
document.addEventListener('keyup', keyEvent => {
if (!(keyEvent.key == 'Backspace' && keyEvent.ctrlKey)) return
created() {
const focusSearchBar = keyEvent => {
if (keyEvent.keyCode === 70 && keyEvent.ctrlKey) {
keyEvent.preventDefault()
this.$refs.searchbar.focus()
}
}
const deleteSearchBarContent = keyEvent => {
if (!(keyEvent.key == 'Backspace' && keyEvent.ctrlKey && keyEvent.shiftKey)) return
this.$refs.searchbar.value = ''
this.$refs.searchbar.focus()
}
document.addEventListener('keydown', focusSearchBar)
document.addEventListener('keyup', deleteSearchBarContent)
this.$on('hook:destroyed', () => {
document.removeEventListener('keydown', focusSearchBar)
document.removeEventListener('keyup', deleteSearchBarContent)
})
},
methods: {
@ -71,11 +141,14 @@ export default {
socket.emit('analyzeLink', term)
} else {
// ? Open downloads tab ?
Downloads.sendAddToQueue(term)
sendAddToQueue(term)
}
}
} else {
if (isShowingSearch && sameAsLastSearch) return
if (isShowingSearch && sameAsLastSearch) {
this.$root.$emit('mainSearch:updateResults', term)
return
}
if (!isShowingSearch) {
await this.$router.push({
@ -88,12 +161,11 @@ export default {
this.lastTextSearch = term
}
this.$root.$emit('mainSearch:showNewResults', term, window.main_selected)
this.$root.$emit('mainSearch:showNewResults', term)
}
}
}
}
</script>
<style>
</style>

View File

@ -1,11 +1,10 @@
<template>
<aside id="sidebar" role="navigation">
<aside id="sidebar" role="navigation" aria-label="sidebar">
<router-link
v-for="link in links"
:key="link.id"
tag="span"
tag="a"
class="main_tablinks"
role="link"
:id="link.id"
:class="{ active: activeTablink === link.name }"
:aria-label="link.ariaLabel"
@ -13,7 +12,7 @@
@click.native="activeTablink = link.name"
>
<i class="material-icons side_icon">{{ link.icon }}</i>
<span class="main_tablinks_text">{{ link.label }}</span>
<span class="main_tablinks_text">{{ $t(link.label) }}</span>
</router-link>
<span id="theme_selector" class="main_tablinks" role="link" aria-label="theme selector">
@ -67,9 +66,6 @@
<script>
export default {
data() {
const $t = this.$t.bind(this)
const $tc = this.$tc.bind(this)
return {
appOnline: null,
activeTheme: 'light',
@ -82,7 +78,7 @@ export default {
ariaLabel: 'home',
routerName: 'Home',
icon: 'home',
label: $t('sidebar.home')
label: 'sidebar.home'
},
{
id: 'main_search_tablink',
@ -90,7 +86,7 @@ export default {
ariaLabel: 'search',
routerName: 'Search',
icon: 'search',
label: $t('sidebar.search')
label: 'sidebar.search'
},
{
id: 'main_charts_tablink',
@ -98,7 +94,7 @@ export default {
ariaLabel: 'charts',
routerName: 'Charts',
icon: 'show_chart',
label: $t('sidebar.charts')
label: 'sidebar.charts'
},
{
id: 'main_favorites_tablink',
@ -106,7 +102,7 @@ export default {
ariaLabel: 'favorites',
routerName: 'Favorites',
icon: 'star',
label: $t('sidebar.favorites')
label: 'sidebar.favorites'
},
{
id: 'main_analyzer_tablink',
@ -114,7 +110,7 @@ export default {
ariaLabel: 'link analyzer',
routerName: 'Link Analyzer',
icon: 'link',
label: $t('sidebar.linkAnalyzer')
label: 'sidebar.linkAnalyzer'
},
{
id: 'main_settings_tablink',
@ -122,7 +118,7 @@ export default {
ariaLabel: 'settings',
routerName: 'Settings',
icon: 'settings',
label: $t('sidebar.settings')
label: 'sidebar.settings'
},
{
id: 'main_about_tablink',
@ -130,7 +126,7 @@ export default {
ariaLabel: 'info',
routerName: 'About',
icon: 'info',
label: $t('sidebar.about')
label: 'sidebar.about'
}
]
}

View File

@ -5,6 +5,7 @@
@transitionend="$refs.container.style.transition = ''"
ref="container"
:data-label="$t('downloads')"
aria-label="downloads"
>
<div id="download_tab_drag_handler" @mousedown.prevent="startDrag" ref="dragHandler"></div>
<i
@ -16,7 +17,6 @@
></i>
<div id="queue_buttons">
<i
id="open_downloads_folder"
v-if="clientMode"
class="material-icons download_bar_icon"
:title="$t('globals.open_downloads_folder')"
@ -24,20 +24,10 @@
>
folder_open
</i>
<i
id="clean_queue"
class="material-icons download_bar_icon"
@click="cleanQueue"
:title="$t('globals.clean_queue_hint')"
>
<i class="material-icons download_bar_icon" @click="cleanQueue" :title="$t('globals.clean_queue_hint')">
clear_all
</i>
<i
id="cancel_queue"
class="material-icons download_bar_icon"
@click="cancelQueue"
:title="$t('globals.cancel_queue_hint')"
>
<i class="material-icons download_bar_icon" @click="cancelQueue" :title="$t('globals.cancel_queue_hint')">
delete_sweep
</i>
</div>
@ -87,6 +77,19 @@ export default {
clientMode: 'getClientMode'
})
},
created() {
const checkIfToggleBar = keyEvent => {
if (!(keyEvent.ctrlKey && keyEvent.key === 'b')) return
this.toggleDownloadTab()
}
document.addEventListener('keyup', checkIfToggleBar)
this.$on('hook:destroyed', () => {
document.removeEventListener('keyup', checkIfToggleBar)
})
},
mounted() {
socket.on('startDownload', this.startDownload)
socket.on('startConversion', this.startConversion)
@ -263,7 +266,7 @@ export default {
this.queueComplete = []
},
toggleDownloadTab(clickEvent) {
toggleDownloadTab() {
this.setTabWidth()
this.$refs.container.style.transition = 'all 250ms ease-in-out'
@ -337,4 +340,3 @@ export default {
}
}
</script>

View File

@ -13,9 +13,9 @@
</template>
<script>
import Downloads from '@/utils/downloads'
import downloadQualities from '@js/qualities'
import { sendAddToQueue } from '@/utils/downloads'
import { generatePath, copyToClipboard } from '@/utils/utils'
import { downloadQualities } from '@/data/qualities'
export default {
data() {
@ -98,7 +98,7 @@ export default {
label: `${this.$t('globals.download', { thing: quality.label })}`,
show: false,
position: nextValuePosition + index,
action: this.tryToDownloadTrack.bind(null, quality.value)
action: sendAddToQueue.bind(null, this.deezerHref, quality.value)
}
})
@ -123,7 +123,6 @@ export default {
},
methods: {
showMenu(contextMenuEvent) {
// contextMenuEvent.preventDefault()
const { pageX, pageY, target: elementClicked } = contextMenuEvent
const path = generatePath(elementClicked)
let deezerLink = null
@ -213,9 +212,6 @@ export default {
downloadQualities.forEach(quality => {
this.options[quality.objName].show = true
})
},
tryToDownloadTrack(qualityValue) {
Downloads.sendAddToQueue(this.deezerHref, qualityValue)
}
}
}

View File

@ -47,7 +47,7 @@
</thead>
<tbody>
<tr v-for="release in showTable" :key="release.id">
<td class="inline-flex clickable" @click="albumView" :data-id="release.id">
<router-link tag="td" class="inline-flex clickable" :to="{ name: 'Album', params: { id: release.id } }">
<img
class="rounded coverart"
:src="release.cover_small"
@ -58,7 +58,7 @@
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color: #ff7300">
fiber_new
</i>
</td>
</router-link>
<td>{{ release.release_date }}</td>
<td>{{ release.nb_song }}</td>
<td @click.stop="addToQueue" :data-link="release.link" class="clickable">
@ -69,7 +69,7 @@
</table>
<footer>
<button class="back-button" @click="backTab">{{ $t('globals.back') }}</button>
<button class="back-button" @click="$router.back()">{{ $t('globals.back') }}</button>
</footer>
</div>
</template>
@ -78,7 +78,6 @@
import { isEmpty, orderBy } from 'lodash-es'
import { socket } from '@/utils/socket'
import Downloads from '@/utils/downloads'
import { showView, backTab } from '@js/tabs'
import EventBus from '@/utils/EventBus'
export default {
@ -97,8 +96,6 @@ export default {
}
},
methods: {
backTab,
albumView: showView.bind(null, 'album'),
reset() {
this.title = 'Loading...'
this.image = ''
@ -125,11 +122,8 @@ export default {
changeTab(tab) {
this.currentTab = tab
},
getCurrentTab() {
return this.currentTab
},
updateSelected() {
window.currentStack.selected = this.currentTab
// Last tab opened logic
},
checkNewRelease(date) {
let g1 = new Date()

View File

@ -71,20 +71,20 @@
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
}}
</td>
<td
class="table__cell--medium table__cell--center breakline clickable"
@click="artistView"
:data-id="track.artist.id"
<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 }}
</td>
<td
</router-link>
<router-link
tag="td"
class="table__cell--medium table__cell--center breakline clickable"
@click="albumView"
:data-id="track.album.id"
:to="{ name: 'Album', params: { id: track.album.id } }"
>
{{ track.album.title }}
</td>
</router-link>
<td class="table__cell--small table__cell--center">
{{ convertDuration(track.duration) }}
</td>
@ -107,7 +107,6 @@
<script>
import { mapGetters } from 'vuex'
import { socket } from '@/utils/socket'
import { showView } from '@js/tabs.js'
import { sendAddToQueue } from '@/utils/downloads'
import { convertDuration } from '@/utils/utils'
@ -136,8 +135,6 @@ export default {
},
methods: {
convertDuration,
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playPausePreview(e) {
EventBus.$emit('trackPreview:playPausePreview', e)
},

View File

@ -29,46 +29,25 @@
<h1>{{ $t('favorites.noPlaylists') }}</h1>
</div>
<div class="release_grid" v-if="playlists.length > 0 || spotifyPlaylists > 0">
<div v-for="release in playlists" class="release clickable" @click="playlistView" :data-id="release.id">
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
<div
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">
{{
`${$t('globals.by', { artist: release.creator.name })} - ${$tc(
'globals.listTabs.trackN',
release.nb_tracks
)}`
}}
</p>
</div>
<div
v-for="release in spotifyPlaylists"
<router-link
tag="div"
v-for="release in playlists"
:key="release.id"
class="release clickable"
@click="spotifyPlaylistView"
:data-id="release.id"
:to="{ name: 'Playlist', params: { id: release.id } }"
>
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
<div
<button
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
tabindex="0"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</button>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">
@ -79,7 +58,37 @@
)}`
}}
</p>
</router-link>
<router-link
tag="div"
v-for="release in spotifyPlaylists"
:key="release.id"
class="release clickable"
:to="{ name: 'Spotify 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="addToQueue"
: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.creator.name })} - ${$tc(
'globals.listTabs.trackN',
release.nb_tracks
)}`
}}
</p>
</router-link>
</div>
</div>
@ -88,22 +97,29 @@
<h1>{{ $t('favorites.noAlbums') }}</h1>
</div>
<div class="release_grid" v-if="albums.length > 0">
<div v-for="release in albums" class="release clickable" @click="albumView" :data-id="release.id">
<router-link
tag="div"
class="release clickable"
v-for="release in albums"
:key="release.id"
:to="{ name: 'Album', params: { id: release.id } }"
>
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
<div
<button
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
tabindex="0"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</button>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
</div>
</router-link>
</div>
</div>
@ -112,21 +128,28 @@
<h1>{{ $t('favorites.noArtists') }}</h1>
</div>
<div class="release_grid" v-if="artists.length > 0">
<div v-for="release in artists" class="release clickable" @click="artistView" :data-id="release.id">
<router-link
tag="div"
class="release clickable"
v-for="release in artists"
: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" />
<div
<button
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
tabindex="0"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</button>
</div>
<p class="primary-text">{{ release.name }}</p>
</div>
</router-link>
</div>
</div>
@ -165,20 +188,20 @@
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
}}
</td>
<td
class="table__cell--medium table__cell--center breakline clickable"
@click="artistView"
:data-id="track.artist.id"
<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 }}
</td>
<td
</router-link>
<router-link
tag="td"
class="table__cell--medium table__cell--center breakline clickable"
@click="albumView"
:data-id="track.album.id"
:to="{ name: 'Album', params: { id: track.album.id } }"
>
{{ track.album.title }}
</td>
</router-link>
<td class="table__cell--small">
{{ convertDuration(track.duration) }}
</td>
@ -210,8 +233,6 @@
</style>
<script>
import { showView } from '@js/tabs'
import { socket } from '@/utils/socket'
import { sendAddToQueue } from '@/utils/downloads'
import { convertDuration } from '@/utils/utils'
@ -257,10 +278,6 @@ export default {
})
},
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playlistView: showView.bind(null, 'playlist'),
spotifyPlaylistView: showView.bind(null, 'spotifyplaylist'),
playPausePreview(e) {
EventBus.$emit('trackPreview:playPausePreview', e)
},

View File

@ -4,7 +4,6 @@
<section class="home_section" ref="notLogged" v-if="!isLoggedIn">
<p id="home_not_logged_text">{{ $t('home.needTologin') }}</p>
<!-- <button type="button" name="button" @click="openSettings">{{ $t('home.openSettings') }}</button> -->
<router-link tag="button" name="button" :to="{ name: 'Settings' }">
{{ $t('home.openSettings') }}
</router-link>
@ -13,24 +12,27 @@
<section v-if="playlists.length" class="home_section">
<h3 class="section_heading">{{ $t('home.sections.popularPlaylists') }}</h3>
<div class="release_grid">
<div
<router-link
tag="div"
v-for="release in playlists"
:key="release.id"
class="release clickable"
@click="playlistView"
:data-id="release.id"
:to="{ name: 'Playlist', params: { id: release.id } }"
@keyup.enter.native="$router.push({ name: 'Playlist', params: { id: release.id } })"
tabindex="0"
>
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
<div
<button
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
tabindex="0"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</button>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">
@ -41,35 +43,39 @@
)}`
}}
</p>
</div>
</router-link>
</div>
</section>
<section v-if="albums.length" class="home_section">
<h3 class="section_heading">{{ $t('home.sections.popularAlbums') }}</h3>
<div class="release_grid">
<div
<router-link
tag="div"
v-for="release in albums"
:key="release.id"
class="release clickable"
@click="albumView"
:to="{ name: 'Album', params: { id: release.id } }"
@keyup.enter.native="$router.push({ name: 'Album', params: { id: release.id } })"
:data-id="release.id"
tabindex="0"
>
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
<div
<button
role="button"
aria-label="download"
@click.stop="addToQueue"
:data-link="release.link"
class="download_overlay"
tabindex="0"
>
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
</div>
</button>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
</div>
</router-link>
</div>
</section>
</div>
@ -78,7 +84,6 @@
<script>
import { mapGetters } from 'vuex'
import { showView } from '@js/tabs'
import { sendAddToQueue } from '@/utils/downloads'
import { getHomeData } from '@/data/home'
@ -95,15 +100,9 @@ export default {
this.initHome(homeData)
},
computed: {
...mapGetters(['isLoggedIn']),
needToWait() {
return this.getHomeData.albums.data.length === 0 && this.getHomeData.playlists.data.length === 0
}
...mapGetters(['isLoggedIn'])
},
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playlistView: showView.bind(null, 'playlist'),
addToQueue(e) {
sendAddToQueue(e.currentTarget.dataset.link)
},

View File

@ -25,24 +25,39 @@
>
<div>
<h1>{{ title }}</h1>
<h2 v-if="type == 'track'">
<h2 v-if="type === 'track'">
<i18n path="globals.by" tag="span">
<span place="artist" class="clickable" @click="artistView" :data-id="data.artist.id">{{
data.artist.name
}}</span>
<router-link
tag="span"
place="artist"
class="clickable"
:to="{ name: 'Artist', params: { id: data.artist.id } }"
>
{{ data.artist.name }}
</router-link>
</i18n>
<i18n path="globals.in" tag="span">
<span place="album" class="clickable" @click="albumView" :data-id="data.album.id">{{
data.album.title
}}</span>
<router-link
tag="span"
place="album"
class="clickable"
:to="{ name: 'Album', params: { id: data.album.id } }"
>
{{ data.album.title }}
</router-link>
</i18n>
</h2>
<h2 v-else-if="type == 'album'">
<h2 v-else-if="type === 'album'">
<i18n path="globals.by" tag="span">
<span place="artist" class="clickable" @click="artistView" :data-id="data.artist.id">{{
data.artist.name
}}</span>
<router-link
tag="span"
place="artist"
class="clickable"
:to="{ name: 'Artist', params: { id: data.artist.id } }"
>
{{ data.artist.name }}
</router-link>
</i18n>
{{ `${$tc('globals.listTabs.trackN', data.nb_tracks)}` }}
</h2>
@ -106,7 +121,9 @@
</table>
<div v-if="type == 'album'">
<button @click="albumView" :data-id="id">{{ $t('linkAnalyzer.table.tracklist') }}</button>
<router-link tag="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>
@ -117,7 +134,6 @@
<script>
import { socket } from '@/utils/socket'
import { showView } from '@js/tabs'
import { convertDuration } from '@/utils/utils'
import { COUNTRIES } from '@/utils/countries'
import EventBus from '@/utils/EventBus'
@ -137,8 +153,6 @@ export default {
}
},
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
convertDuration,
reset() {
this.title = 'Loading...'

View File

@ -23,9 +23,6 @@
:is="currentTab.component"
:results="results"
@add-to-queue="addToQueue"
@artist-view="artistView"
@album-view="albumView"
@playlist-view="playlistView"
@change-search-tab="changeSearchTab"
></component>
</keep-alive>
@ -34,7 +31,7 @@
</template>
<script>
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
import ResultsAll from '@components/search/ResultsAll.vue'
import ResultsAlbums from '@components/search/ResultsAlbums.vue'
import ResultsArtists from '@components/search/ResultsArtists.vue'
@ -42,7 +39,6 @@ import ResultsPlaylists from '@components/search/ResultsPlaylists.vue'
import ResultsTracks from '@components/search/ResultsTracks.vue'
import { socket } from '@/utils/socket'
import { showView } from '@js/tabs'
import { sendAddToQueue } from '@/utils/downloads'
import { numberWithDots, convertDuration } from '@/utils/utils'
import EventBus from '@/utils/EventBus'
@ -156,14 +152,12 @@ export default {
mounted() {
EventBus.$on('mainSearch:checkLoadMoreContent', this.checkLoadMoreContent)
this.$root.$on('mainSearch:showNewResults', this.checkIfShowNewResults)
this.$root.$on('mainSearch:updateResults', this.checkIfUpdateResults)
socket.on('mainSearch', this.handleMainSearch)
socket.on('search', this.handleSearch)
},
methods: {
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playlistView: showView.bind(null, 'playlist'),
changeSearchTab(sectionName) {
sectionName = sectionName.toLowerCase()
@ -180,12 +174,21 @@ export default {
this.currentTab = newTab
},
checkIfShowNewResults(term, mainSelected) {
let needToPerformNewSearch = term !== this.results.query || mainSelected == 'search_tab'
let needToPerformNewSearch = term !== this.results.query /* || mainSelected == 'search_tab' */
if (needToPerformNewSearch) {
this.showNewResults(term)
}
},
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) {
socket.emit('mainSearch', { term })

View File

@ -56,6 +56,7 @@
:class="{ 'locale-flag--current': currentLocale === locale }"
@click="changeLocale(locale)"
v-html="flags[locale]"
:title="locale"
>
</span>
</div>
@ -187,7 +188,7 @@
<div class="input_group">
<p class="input_group_text">{{ $t('settings.downloads.queueConcurrency') }}</p>
<input type="number" v-model.number="settings.queueConcurrency" />
<input type="number" min="1" v-model.number="settings.queueConcurrency" />
</div>
<div class="input_group">
@ -643,8 +644,8 @@
}
svg {
width: 40px;
height: 40px;
width: 40px !important;
height: 40px !important;
filter: brightness(0.5);
}
}
@ -653,19 +654,18 @@
<script>
import { mapActions, mapGetters } from 'vuex'
import { getSettingsData } from '@/data/settings'
import { toast } from '@/utils/toasts'
import { socket } from '@/utils/socket'
import EventBus from '@/utils/EventBus'
import flags from '@/utils/flags'
import { getSettingsData } from '@/data/settings'
import { flags } from '@/utils/flags'
export default {
data() {
return {
flags,
currentLocale: 'en',
locales: [],
currentLocale: this.$i18n.locale,
locales: this.$i18n.availableLocales,
settings: {
tags: {}
},
@ -679,7 +679,6 @@ export default {
previewVolume: window.vol,
accountNum: 0,
accounts: []
// clientMode: window.clientMode
}
},
computed: {
@ -708,23 +707,11 @@ export default {
}
},
async mounted() {
this.locales = this.$i18n.availableLocales
const { settingsData, defaultSettingsData, spotifyCredentials } = await getSettingsData()
this.defaultSettings = defaultSettingsData
this.initSettings(settingsData, spotifyCredentials)
// this.revertSettings()
// this.revertCredentials()
let storedLocale = localStorage.getItem('locale')
if (storedLocale) {
this.$i18n.locale = storedLocale
this.currentLocale = storedLocale
}
let storedAccountNum = localStorage.getItem('accountNum')
if (storedAccountNum) {
@ -823,6 +810,11 @@ export default {
this.lastCredentials = JSON.parse(JSON.stringify(credentials))
this.spotifyFeatures = JSON.parse(JSON.stringify(credentials))
},
loggedInViaDeezer(arl) {
this.dispatchARL({ arl })
socket.emit('login', arl, true, this.accountNum)
// this.login()
},
login() {
let newArl = this.$refs.loginInput.value.trim()
@ -833,10 +825,6 @@ export default {
appLogin(e) {
socket.emit('applogin')
},
loggedInViaDeezer(arl) {
this.dispatchARL({ arl })
this.login()
},
changeAccount() {
socket.emit('changeAccount', this.accountNum)
},

View File

@ -64,21 +64,21 @@
}}
</div>
</td>
<td
<router-link
tag="td"
class="table__cell--medium table__cell--center clickable"
@click="artistView"
:data-id="track.artist.id"
:to="{ name: 'Artist', params: { id: track.artist.id } }"
>
{{ track.artist.name }}
</td>
<td
v-if="type == 'playlist'"
</router-link>
<router-link
tag="td"
v-if="type === 'playlist'"
class="table__cell--medium table__cell--center clickable"
@click="albumView"
:data-id="track.album.id"
:to="{ name: 'Album', params: { id: track.album.id } }"
>
{{ track.album.title }}
</td>
</router-link>
<td
class="table__cell--center"
:class="{ 'table__cell--small': type === 'album', 'table__cell--x-small': type === 'playlist' }"
@ -137,7 +137,7 @@
<button class="with_icon" @click.stop="addToQueue" :data-link="selectedLinks()">
{{ $t('tracklist.downloadSelection') }}<i class="material-icons">file_download</i>
</button>
<button class="back-button" @click="backTab">{{ $t('globals.back') }}</button>
<button class="back-button" @click="$router.back()">{{ $t('globals.back') }}</button>
</footer>
</div>
</template>
@ -145,14 +145,13 @@
<script>
import { isEmpty } from 'lodash-es'
import { socket } from '@/utils/socket'
import { showView, backTab } from '@js/tabs.js'
import Downloads from '@/utils/downloads'
import Utils from '@/utils/utils'
import EventBus from '@/utils/EventBus'
export default {
name: 'tracklist-tab',
data: () => ({
data() {
return {
title: '',
metadata: '',
release_date: '',
@ -162,11 +161,9 @@ export default {
type: 'empty',
link: '',
body: []
}),
}
},
methods: {
backTab,
artistView: showView.bind(null, 'artist'),
albumView: showView.bind(null, 'album'),
playPausePreview(e) {
EventBus.$emit('trackPreview:playPausePreview', e)
},

View File

@ -5,23 +5,25 @@
<h1>{{ $t('search.noResultsAlbum') }}</h1>
</div>
<div class="release_grid" v-if="results.albumTab.data.length > 0">
<div
<router-link
tag="div"
v-for="release in results.albumTab.data"
:key="release.id"
class="release clickable"
@click.stop="$emit('album-view', $event)"
:data-id="release.id"
:to="{ name: 'Album', params: { id: release.id } }"
>
<div class="cover_container">
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
<div
<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>
</div>
</button>
</div>
<p class="primary-text inline-flex">
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
@ -34,13 +36,13 @@
$tc('globals.listTabs.trackN', release.nb_tracks)
}}
</p>
</div>
</router-link>
</div>
</div>
</template>
<script>
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
export default {
props: ['results'],

View File

@ -15,11 +15,11 @@
{{ $tc(`globals.listTabs.${section.toLowerCase()}`, 2) }}
</h2>
<!-- Top result -->
<div
<router-link
tag="div"
v-if="section == 'TOP_RESULT'"
class="top_result clickable"
@click.stop="$emit(`${topResultType}-view`, $event)"
:data-id="results.allTab.TOP_RESULT[0].id"
:to="{ name: upperCaseFirstLowerCaseRest(topResultType), params: { id: results.allTab.TOP_RESULT[0].id } }"
>
<div class="cover_container">
<img
@ -27,34 +27,29 @@
:src="results.allTab.TOP_RESULT[0].picture"
:class="(results.allTab.TOP_RESULT[0].type == 'artist' ? 'circle' : 'rounded') + ' coverart'"
/>
<div
<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>
</div>
</button>
</div>
<div class="info_box">
<p class="primary-text">{{ results.allTab.TOP_RESULT[0].title }}</p>
<p class="secondary-text">
{{
results.allTab.TOP_RESULT[0].type == 'artist'
? $t('search.fans', { n: $n(results.allTab.TOP_RESULT[0].nb_fan) })
: $t('globals.by', { artist: results.allTab.TOP_RESULT[0].artist }) +
' - ' +
$tc('globals.listTabs.trackN', results.allTab.TOP_RESULT[0].nb_song)
}}
{{ fansNumber }}
</p>
<span class="tag">{{ $tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1) }}</span>
</div>
</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)">
<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"
@ -70,23 +65,26 @@
</div>
</td>
<td class="table__cell table__cell--medium table__cell--center breakline">
<span
class="clickable"
@click.stop="$emit('artist-view', $event)"
:data-id="artist.ART_ID"
<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 }}
</span>
</router-link>
</td>
<td
<router-link
tag="td"
class="table__cell--medium table__cell--center breakline clickable"
@click.stop="$emit('album-view', $event)"
:data-id="track.ALB_ID"
:to="{ name: 'Album', params: { id: track.ALB_ID } }"
>
{{ track.ALB_TITLE }}
</td>
</router-link>
<td class="table__cell table__cell--center">
{{ convertDuration(track.DURATION) }}
</td>
@ -104,11 +102,12 @@
</table>
</div>
<div v-else-if="section == 'ARTIST'" class="release_grid firstrow_only">
<div
<router-link
tag="div"
v-for="release in results.allTab.ARTIST.data.slice(0, 10)"
class="release clickable"
@click.stop="$emit('artist-view', $event)"
:data-id="release.ART_ID"
:key="release.ART_ID"
:to="{ name: 'Artist', params: { id: release.ART_ID } }"
>
<div class="cover_container">
<img
@ -118,26 +117,28 @@
'https://e-cdns-images.dzcdn.net/images/artist/' + release.ART_PICTURE + '/156x156-000000-80-0-0.jpg'
"
/>
<div
<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>
</div>
</button>
</div>
<p class="primary-text">{{ release.ART_NAME }}</p>
<p class="secondary-text">{{ $t('search.fans', { n: $n(release.NB_FAN) }) }}</p>
</div>
</router-link>
</div>
<div v-else-if="section == 'ALBUM'" class="release_grid firstrow_only">
<div
<router-link
tag="div"
v-for="release in results.allTab.ALBUM.data.slice(0, 10)"
:key="release.ALB_ID"
class="release clickable"
@click.stop="$emit('album-view', $event)"
:data-id="release.ALB_ID"
:to="{ name: 'Album', params: { id: release.ALB_ID } }"
>
<div class="cover_container">
<img
@ -147,15 +148,16 @@
'https://e-cdns-images.dzcdn.net/images/cover/' + release.ALB_PICTURE + '/156x156-000000-80-0-0.jpg'
"
/>
<div
<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>
</div>
</button>
</div>
<p class="primary-text inline-flex">
<i
@ -168,14 +170,15 @@
<p class="secondary-text">
{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}
</p>
</div>
</router-link>
</div>
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
<div
<router-link
tag="div"
v-for="release in results.allTab.PLAYLIST.data.slice(0, 10)"
class="release clickable"
@click.stop="$emit('playlist-view', $event)"
:data-id="release.PLAYLIST_ID"
:key="release.PLAYLIST_ID"
:to="{ name: 'Playlist', params: { id: release.PLAYLIST_ID } }"
>
<div class="cover_container">
<img
@ -189,19 +192,20 @@
'/156x156-000000-80-0-0.jpg'
"
/>
<div
<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>
</div>
</button>
</div>
<p class="primary-text">{{ release.TITLE }}</p>
<p class="secondary-text">{{ $tc('globals.listTabs.trackN', release.NB_SONG) }}</p>
</div>
</router-link>
</div>
</section>
</template>
@ -212,6 +216,7 @@
</template>
<script>
import { convertDuration } from '@/utils/utils'
import { upperCaseFirstLowerCaseRest } from '@/utils/texts'
export default {
props: ['results'],
@ -225,10 +230,26 @@ export default {
? this.results.allTab[section].length == 0
: this.results.allTab[section].data.length == 0
)
},
fansNumber() {
let number
try {
number = this.$n(this.results.allTab.TOP_RESULT[0].nb_fan)
} catch (error) {
number = this.$n(this.results.allTab.TOP_RESULT[0].nb_fan, { locale: 'en' })
}
return this.results.allTab.TOP_RESULT[0].type == 'artist'
? this.$t('search.fans', { n: number })
: this.$t('globals.by', { artist: this.results.allTab.TOP_RESULT[0].artist }) +
' - ' +
this.$tc('globals.listTabs.trackN', this.results.allTab.TOP_RESULT[0].nb_song)
}
},
methods: {
convertDuration
convertDuration,
upperCaseFirstLowerCaseRest
}
}
</script>

View File

@ -1,37 +1,39 @@
<template>
<div id="artist_search" class="search_tabcontent">
<base-loading-placeholder v-if="!results.artistTab.loaded"></base-loading-placeholder>
<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">
<div
<router-link
tag="div"
v-for="release in results.artistTab.data"
class="release clickable"
@click.stop="$emit('artist-view', $event)"
:data-id="release.id"
: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" />
<div
<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>
</div>
</button>
</div>
<p class="primary-text">{{ release.name }}</p>
<p class="secondary-text">{{ $tc('globals.listTabs.releaseN', release.nb_album) }}</p>
</div>
</router-link>
</div>
</div>
</template>
<script>
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
export default {
props: ['results'],

View File

@ -5,23 +5,25 @@
<h1>{{ $t('search.noResultsPlaylist') }}</h1>
</div>
<div class="release_grid" v-if="results.playlistTab.data.length > 0">
<div
<router-link
tag="div"
v-for="release in results.playlistTab.data"
class="release clickable"
@click.stop="$emit('playlist-view', $event)"
:data-id="release.id"
: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" />
<div
<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>
</div>
</button>
</div>
<p class="primary-text">{{ release.title }}</p>
<p class="secondary-text">
@ -29,13 +31,13 @@
`${$t('globals.by', { artist: release.user.name })} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}`
}}
</p>
</div>
</router-link>
</div>
</div>
</template>
<script>
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
export default {
props: ['results'],

View File

@ -46,20 +46,20 @@
}}
</div>
</td>
<td
<router-link
tag="td"
class="table__cell table__cell--medium table__cell--center breakline clickable"
@click.stop="artistView"
:data-id="track.artist.id"
:to="{ name: 'Artist', params: { id: track.artist.id } }"
>
{{ track.artist.name }}
</td>
<td
</router-link>
<router-link
tag="td"
class="table__cell table__cell--medium table__cell--center breakline clickable"
@click.stop="albumView"
:data-id="track.album.id"
:to="{ name: 'Album', params: { id: track.album.id } }"
>
{{ track.album.title }}
</td>
</router-link>
<td class="table__cell table__cell--small table__cell--center">
{{ convertDuration(track.duration) }}
</td>
@ -79,9 +79,9 @@
</template>
<script>
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
import EventBus from '@/utils/EventBus.js'
import EventBus from '@/utils/EventBus'
import { convertDuration } from '@/utils/utils'
export default {
@ -91,15 +91,6 @@ export default {
},
methods: {
convertDuration,
artistView(event) {
this.$emit('artist-view', event)
},
albumView(event) {
this.$emit('album-view', event)
},
playlistView(event) {
this.$emit('playlist-view', event)
},
playPausePreview(e) {
EventBus.$emit('trackPreview:playPausePreview', e)
},

View File

@ -1,4 +1,4 @@
export default [
export const downloadQualities = [
{
objName: 'flac',
label: 'FLAC',

View File

@ -1,93 +0,0 @@
import EventBus from '@/utils/EventBus'
import router from '@/router'
/* ===== Globals ====== */
window.search_selected = ''
window.main_selected = ''
window.windows_stack = []
window.currentStack = {}
// Used only in errors tab
export function changeTab(sidebarEl, section, tabName) {
window.windows_stack = []
window.currentStack = {}
// * Only in section search
updateTabLink(section)
// * Only when clicking the settings icon in the sidebar
// resetSettings(tabName)
// * Only in section search
setSelectedTab(section, tabName)
// * Only if window.main_selected === 'search_tab'
checkNeedToLoadMoreContent()
}
function setSelectedTab(section, tabName) {
if (section === 'main') {
window.main_selected = tabName
} else if (section === 'search') {
window.search_selected = tabName
}
}
function checkNeedToLoadMoreContent() {
// * Check if you need to load more content in the search tab
// * Happens when the user changes the tab in the main search
if (
window.main_selected === 'search_tab' &&
['track_search', 'album_search', 'artist_search', 'playlist_search'].indexOf(window.search_selected) !== -1
) {
EventBus.$emit('mainSearch:checkLoadMoreContent', window.search_selected)
}
}
function resetSettings(tabName) {
if (tabName === 'settings_tab' && window.main_selected !== 'settings_tab') {
EventBus.$emit('settingsTab:revertSettings')
EventBus.$emit('settingsTab:revertCredentials')
}
}
function updateTabLink(section) {
// * Tabs inside the actual tab (like albums, tracks, playlists...)
// * or sidebar links
if (section == 'main') return
const tabLinks = document.getElementsByClassName(`${section}_tablinks`)
for (let i = 0; i < tabLinks.length; i++) {
tabLinks[i].classList.remove('active')
}
}
export function showView(viewType, event) {
const {
currentTarget: {
dataset: { id }
}
} = event
const isArtist = viewType === 'artist'
const name = isArtist ? 'Artist' : 'Tracklist'
const params = isArtist ? { id } : { type: viewType, id }
router.push({
name,
params
})
}
/**
* Goes back to the previous tab according to the global window stack.
*/
export function backTab() {
// ! Need to implement the memory of the opened artist tab
router.back()
}
export function init() {
// Open default tab
changeTab(document.getElementById('main_home_tablink'), 'main', 'home_tab')
}

View File

@ -4,7 +4,6 @@
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@js/*": ["./js/*"],
"@components/*": ["./components/*"]
}
},

View File

@ -105,7 +105,8 @@ const en = {
no360RA: 'Track is not available in Reality Audio 360.',
notAvailable: "Track not available on Deezer's servers!",
notAvailableNoAlternative: "Track not available on Deezer's servers and no alternative found!",
noSpaceLeft: "No space left on the device!"
noSpaceLeft: "No space left on the device!",
albumDoesntExists: "Track's album doesn't exist, failed to gather info"
}
},
favorites: {

View File

@ -105,7 +105,8 @@ const fr = {
no360RA: 'La piste est indisponible au format Reality Audio 360.',
notAvailable: 'La piste est indisponible sur les serveurs de Deezer !',
notAvailableNoAlternative: "La piste est indisponible sur les serveurs de Deezer et aucune alternative n'a été trouvée !",
noSpaceLeft: "L'espace disponible sur cet appareil est insuffisant !"
noSpaceLeft: "L'espace disponible sur cet appareil est insuffisant !",
albumDoesntExists: "Aucun album n'existe pour cette piste, impossible de collecter les informations nécessaires"
}
},
favorites: {

33
src/lang/index.js Normal file
View File

@ -0,0 +1,33 @@
import it from '@/lang/it'
import en from '@/lang/en'
import es from '@/lang/es'
import de from '@/lang/de'
import fr from '@/lang/fr'
import id from '@/lang/id'
import pt from '@/lang/pt-pt'
import pt_br from '@/lang/pt-br'
import ru from '@/lang/ru'
import tr from '@/lang/tr'
import vn from '@/lang/vn'
import hr from '@/lang/hr'
import ar from '@/lang/ar'
import ko from '@/lang/ko'
import ph from '@/lang/ph'
export const locales = {
it,
en,
es,
de,
fr,
id,
pt,
pt_br,
ru,
tr,
vn,
hr,
ar,
ko,
ph
}

View File

@ -108,7 +108,8 @@ const it = {
no360RA: 'Brano non disponibile in Reality Audio 360.',
notAvailable: 'Brano non presente sui server di Deezer!',
notAvailableNoAlternative: 'Brano non presente sui server di Deezer e nessuna alternativa trovata!',
noSpaceLeft: "Spazio su disco esaurito!"
noSpaceLeft: "Spazio su disco esaurito!",
albumDoesntExists: "Il brano non ha nessun album, impossibile ottenere informazioni"
}
},
favorites: {

View File

@ -97,7 +97,7 @@ const ko = {
wrongBitrateNoAlternative: '요구하는 비트레이트를 찾을 수 없을 뿐더러 대체할 것을 찾지 못했습니다!',
no360RA: '해당 트랙은 360 리얼리티 오디오에 존재하지 않습니다.',
notAvailable: "해당 트랙은 Deezer 서버에 존재하지 않습니다!",
notAvailableNoAlternative: "해당 트랙은 Deezer 서버에 존재하지 않을 뿐더러 대체할 것을 찾지 못했습니다!"
notAvailableNoAlternative: "해당 트랙은 Deezer 서버에 존재하지 않을 뿐더러 대체할 것을 찾지 못했습니다!",
noSpaceLeft: "장치에 여유 공간이 없습니다!"
}
},
@ -168,7 +168,7 @@ const ko = {
finishAddingArtist: '{artist} 앨범이 대기열에 추가되었습니다',
startConvertingSpotifyPlaylist: '스포티파이 트랙을 Deezer 트랙으로 전환 중입니다',
finishConvertingSpotifyPlaylist: '스프토파이 재생 목록이 전환되었습니다',
loginNeededToDownload: '트랙을 다운로드하려면 로그인이 필요합니다!'
loginNeededToDownload: '트랙을 다운로드하려면 로그인이 필요합니다!',
deezerNotAvailable: 'Deezer 사이트는 현재 귀하의 국가에서 사용이 불가능합니다. VPN을 사용하세요.'
},
settings: {
@ -181,8 +181,8 @@ const ko = {
question: 'ARL을 어떻게 확인합니까?',
update: 'ARL 업데이트'
},
logout: '로그아웃'
question: '스포티파이 기능들을 쓰려면 어떻게 해야합니까?
logout: '로그아웃',
question: '스포티파이 기능들을 쓰려면 어떻게 해야합니까?'
},
appearance: {
title: '외관',
@ -258,7 +258,7 @@ const ko = {
jpegImageQuality: 'JPEG 이미지 품질',
embeddedArtworkPNG: '포함된 그림의 형식을 PNG로 저장합니다',
embeddedPNGWarning: 'PNG는 Deezer에서 공식적으로 지원하지 않기 때문에 버그가 있을 수 있습니다',
imageSizeWarning: 'x1200 크기를 초과해서는 Deezer에서 공식적으로 사용되지 않기 때문에 문제가 생길 수 있습니다'
imageSizeWarning: 'x1200 크기를 초과해서는 Deezer에서 공식적으로 사용되지 않기 때문에 문제가 생길 수 있습니다',
coverDescriptionUTF8: '커버 설명을 UTF8 포맷을 이용해 저장합니다 (iTunes 커버 오류 해결)'
},
tags: {

371
src/lang/ph.js Normal file
View File

@ -0,0 +1,371 @@
const ph = {
globals: {
welcome: 'Welcome sa deemix',
back: 'bumalik',
loading: 'kumakarga',
download: 'I-download {thing}',
by: 'ayon sa {artist}',
in: 'sa {album}',
download_hint: 'I-download',
play_hint: 'I-play',
toggle_download_tab_hint: 'Palakihin/Paliitan',
clean_queue_hint: 'Natapos na ang Pag-alis',
cancel_queue_hint: 'Ikansel Lahat',
open_downloads_folder: 'Buksan ang Polder ng Download',
cut: 'i-cut',
copy: 'kopyahin',
copyLink: 'kopyahin ang link',
copyImageLink: 'kopyahin ang imahe sa link',
copyDeezerLink: 'kopyahin ang link ng deezer',
paste: 'idikit',
listTabs: {
empty: '',
all: 'lahat',
top_result: 'nangungunang resulta',
album: 'album | mga album',
artist: 'artist | mga artist',
single: 'single | mga single',
title: 'pamagat | mga pamagat',
track: 'track | mga track',
trackN: '0 mga track | {n} track | {n} mga track',
releaseN: '0 mga release | {n} release | {n} mga release',
playlist: 'playlist | mga playlist',
compile: 'pinagsama | mga pinagsama',
ep: 'ep | mga ep',
bundle: 'bundle | mga bundle',
more: 'Iba pang mga album',
featured: 'Ibinida sa',
spotifyPlaylist: 'playlist sa spotify | mga playlist sa spotify',
releaseDate: 'petsa ng paglabas',
error: 'error'
}
},
about: {
updates: {
currentVersion: 'Kasalukuyang version',
versionNotAvailable: 'H/P',
updateAvailable: `Hindi mo ginagamit ang pinakabagong version: {version}`,
deemixVersion: 'deemix lib version'
},
titles: {
usefulLinks: 'Nakatutulong na mga Link',
bugReports: 'Report sa Bug',
contributing: 'Pagtulong',
donations: 'Mga donasiyon',
license: 'Lisensiya'
},
subtitles: {
bugReports: "Meron bang hindi gumagana sa deemix? Ipaalam mo sa amin!",
contributing: 'Gusto mo bang tumulong sa proyektong ito? Pwede mong gawin iyan sa maraming paraan!',
donations: 'Gusto mo bang tumulong sa pamamagitan ng pera? Pwede kang magbigay ng donasiyon!'
},
usesLibrary: 'Ang app na ito ay gumagamit ng library galing sa <strong>deemix</strong>, na kung saan ay pwede mong gamitin para gumawa ng sarili mong UI ng deemix.',
thanks: `Salamat kay <strong>rtonno</strong>, <strong>uhwot</strong> at <strong>lollilol</strong> sa pagtulong sa akin para sa proyektong ito at kay <strong>BasCurtiz</strong> at <strong>scarvimane</strong> sa paggawa ng icon.`,
upToDate: `Huwag magpapahuli sa mga update patungkol dito sa pamamagitan ng pagsali sa <a href="https://t.me/RemixDevNews" target="_blank">news channel</a> sa Telegram.`,
officialWebsite: 'Opisyal na Website',
officialRepo: 'Opisyal na Library Repository',
officialWebuiRepo: 'Opisyal na Repository ng WebUI',
officialSubreddit: 'Opisyal na Subreddit',
newsChannel: 'News Channel',
questions: `Kung may tanong ka o problema sa app, maghanap ka muna ng solusiyon sa <a href="https://www.reddit.com/r/deemix" target="_blank">subreddit</a>. Ngayon, kung wala ka talagang mahanap ay pwede kang mag-post patungkol sa iyong isyu doon sa subreddit.`,
beforeReporting: `Bago ka magreport ng bug, siguraduhing pinakabagong version ang ginagamit mo at ang ire-report mo ay talagang bug at hindi dahil sa pagkakamali mo lang ng paggamit.`,
beSure: `Siguraduhing nangyayari rin ang bug sa iba't ibang plataporma at tsaka <strong>HUWAG</strong> mo nang i-report ang bug kung ito ay naipa-alam na ng iba.`,
duplicateReports: 'Isasara namin ang mga magkaparehong report sa bug, kaya alamin mo muna.',
dontOpenIssues: `<strong>HUWAG</strong> kayong magbubukas ng isyu kung magtatanong lang kayo, meron tayong subreddit para diyan.`,
newUI: `Kung ikaw ay maraming alam sa python, subukan mong gumawa ng bagong UI gamit ng base library, o kaya ayusin ang mga bug sa library sa pamamagitan ng pag-pull ng request sa <a href="https://codeberg.org/RemixDev/deemix" target="_blank">repo</a>.`,
acceptFeatures: `Tumatangggap din ako ng mga feature, basta hindi komplikado, dahil diretso ko itong nilalagay sa app at hindi sa library.`,
otherLanguages: `Kung ikaw ay maraming alam sa ibang programming language, maaari mo ring subukan i-port ang deemix sa iba't ibang programming language!`,
understandingCode: `Kailangan mo ba ng tulong para maintindihan ang code? Bisitahin si RemixDev sa Telegram o sa Reddit.`,
contributeWebUI: `Kung may alam ka sa Vue.js (JavaScript), HTML o kaya CSS, maaari kang sumali at tumulong dito sa <a href="https://codeberg.org/RemixDev/deemix-webui" target="_blank">WebUI</a>.`,
itsFree: `Lagi mong tandaang <strong>ang proyektong ito ay libre</strong> at <strong>suportuhanmuna ang minamahal ninyong mga artist</strong> bago ang mga developer.`,
notObligated: `Huwag mong pilitin ang sarili para mag-donate, Naiintindihan ka namin!`,
lincensedUnder: `Ang aktibidad na ito ay lisensiyado sa
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GNU General Public License 3.0</a>.`
},
charts: {
title: 'Mga Chart',
changeCountry: 'Palitan ang Country',
download: 'I-download ang Chart'
},
errors: {
title: 'Mga error sa {name}',
ids: {
invalidURL: 'Hindi makilala ang URL',
unsupportedURL: 'Hindi pa suportado ang URL',
ISRCnotOnDeezer: 'Ang Track ISRC ay hindi pwede sa Deezer',
notYourPrivatePlaylist: "Hindi pwedeng i-download ang mga pribadong playlist ng iba.",
spotifyDisabled: 'Hindi mo nai-set nang tama ang Spotify Features.',
trackNotOnDeezer: 'Hindi mahanap ang track sa Deezer!',
albumNotOnDeezer: 'Hindi mahanap ang album sa Deezer!',
notOnDeezer: 'Hindi available ang track sa Deezer!',
notEncoded: 'Hindi pa nae-encode ang track!',
notEncodedNoAlternative: 'Hindi pa nae-encode ang track at walang mahanap na iba!',
wrongBitrate: 'Hindi mahanap ang track sa gusto mong bitrate.',
wrongBitrateNoAlternative: 'Hindi mahanap ang track sa gusto mong bitrate at walang mahanap na iba!',
no360RA: 'Hindi pwede ang track para sa Reality Audio 360.',
notAvailable: "Walang available na track sa server ng Deezer!",
notAvailableNoAlternative: "Walang available na track sa server ng Deezer at walang mahanap na iba!",
noSpaceLeft: "Wala nang natitirang space sa iyong device!"
}
},
favorites: {
title: 'Mga Paborito',
noPlaylists: 'Walang makitang mga Playlist',
noAlbums: 'Walang makitang mga Paboritong Album',
noArtists: 'Walang makitang mga Paboritong Artist',
noTracks: 'Walang makitang mga Paboritong Track'
},
home: {
needTologin: 'Kailangan mong mag-log in sa iyong Deezer account bago ka makasimulang magdownload.',
openSettings: 'Buksan ang Mga Setting',
sections: {
popularPlaylists: 'Mga sikat na playlist',
popularAlbums: 'Pinakamaraming pinakikinggang mga album'
}
},
linkAnalyzer: {
info: 'Pwede gamitin ang section na ito para sa iba pang impormasyon patungkol sa link na gusto mong i-download.',
useful: "Makatutulong ito kung meron kang gustong i-download na track na hindi available sa bansa mo at gusto mong malaman kung meron bang ganito kapag sa iba.",
linkNotSupported: 'Hindi pa suportado ang link',
linkNotSupportedYet: 'Mukhang hindi pa suportado itong link, iba na lang ang ilagay mo.',
table: {
id: 'ID',
isrc: 'ISRC',
upc: 'UPC',
duration: 'Haba',
diskNumber: 'Bilang ng Disk',
trackNumber: 'Bilang ng Track',
releaseDate: 'Petsa ng Release',
bpm: 'BPM',
label: 'Label',
recordType: 'Uri ng Rekord',
genres: 'Mga Genre',
tracklist: 'Listahan ng Track'
}
},
search: {
startSearching: 'Simulang Maghanap!',
description: 'Pwede kang maghanap ng track, buong album, artist, playlist.... kahit ano! Pwede ka ring mag-paste dito ng link na galing sa Deezer',
fans: '{n} mga fan',
noResults: 'Walang resulta',
noResultsTrack: 'Walang mahanap na mga Track',
noResultsAlbum: 'Walang mahanap na mga Album',
noResultsArtist: 'Walang mahanap na mga Artist',
noResultsPlaylist: 'Walang mahanap na mga Playlist'
},
searchbar: 'Maghanap ka ng gusto mo (o mag-paste ka ng link)',
downloads: 'mga download',
toasts: {
restoringQueue: 'Binabalik ang download queue...',
queueRestored: 'Naibalik na ang download queue!',
addedToQueue: '{item} ay naidagdag sa queue',
addedMoreToQueue: '{n} naidagdag rin sa queue',
alreadyInQueue: '{item} ay meron na sa queue!',
finishDownload: '{item} ay natapos nang i-download.',
allDownloaded: 'Nadownload na lahat!',
refreshFavs: 'Narefresh na!',
loggingIn: 'Nagla-log in...',
loggedIn: 'Na-login na',
alreadyLogged: 'Nakalogin ka na',
loginFailed: "Hindi maka-log in",
loggedOut: 'Na-logout na',
cancellingCurrentItem: 'Kinakansel ang item.',
currentItemCancelled: 'Nakansel na ang item.',
startAddingArtist: 'Idinadagdag si {artist} sa queue ng mga album',
finishAddingArtist: 'Naidagdag na si {artist} sa queue ng mga album',
startConvertingSpotifyPlaylist: 'Kino-convert ang mga track sa spotify papuntang Deezer',
finishConvertingSpotifyPlaylist: 'Naconvert na ang playlist sa Spotify',
loginNeededToDownload: 'Kailangan mong mag-login para madownload ang mga track!',
deezerNotAvailable: 'Hindi available ang Deezer sa iyong bansa. Kailangan mong gumamit ng VPN.'
},
settings: {
title: 'Mga Setting',
languages: 'Mga Wika',
login: {
title: 'Login',
loggedIn: 'Ikaw ay naka-login sa pangalang {username}',
arl: {
question: 'Paano ako makakuha ng sariling ARL?',
update: 'I-update ang ARL'
},
logout: 'Logout',
login: 'Mag-login gamit ng deezer.com'
},
appearance: {
title: 'Hitsura',
slimDownloadTab: 'Pinaliit na download tab'
},
downloadPath: {
title: 'Paglalagyan ng Download'
},
templates: {
title: 'Mga Template',
tracknameTemplate: 'Template sa pangalan ng Track',
albumTracknameTemplate: 'Template sa track ng Album',
playlistTracknameTemplate: 'Template sa track ng Playlist'
},
folders: {
title: 'Mga Folder',
createPlaylistFolder: 'Gumawa ng folder para sa mga playlist',
playlistNameTemplate: 'Template sa folder ng Playlist',
createArtistFolder: 'Gumawa ng folder para sa artist',
artistNameTemplate: 'Template sa folder ng Artist',
createAlbumFolder: 'Gumawa ng folder para sa album',
albumNameTemplate: 'Template sa folder ng Album',
createCDFolder: 'Gumawa ng folder para sa mga CD',
createStructurePlaylist: 'Gumawa ng istraktura ng folder para sa mga playlist',
createSingleFolder: 'Gumawa ng istraktura ng folder para sa mga single'
},
trackTitles: {
title: 'Pamagat sa mga track',
padTracks: 'Mga track ng Pad',
paddingSize: 'Patungan ang laki ng padding',
illegalCharacterReplacer: 'Pamalit sa ilegal na Karakter'
},
downloads: {
title: 'Mga Download',
queueConcurrency: 'Mga Kasabay na Download',
maxBitrate: {
title: 'Gustong Bitrate',
9: 'FLAC 1411kbps',
3: 'MP3 320kbps',
1: 'MP3 128kbps'
},
overwriteFile: {
title: 'Papatungan ko ba ang file?',
y: 'Oo, patungan mo ang file',
n: "Hindi, huwag mong patungan ang file",
t: 'Patungan mo lang ang mga tag',
b: 'Hindi, hayaan mo silang dalawa at lagyan mo lang ng numero sa kapareho niya',
e: "Hindi, at huwag mong tignan ang mga extension"
},
fallbackBitrate: 'Binabaang bitrate',
fallbackSearch: 'Maghanap para sa binabaan',
logErrors: 'Gumawa ng log file para sa mga error',
logSearched: 'Gumawa ng log file para sa mga hinanap na track',
createM3U8File: 'Gumawa ng file sa playlist',
syncedLyrics: 'Gumawa ng mga .lyr file (Mga Sync Lyric)',
playlistFilenameTemplate: 'Template sa pangalan ng Playlist file',
saveDownloadQueue: 'I-save ang download queue kapag isasara the app'
},
covers: {
title: 'Mga cover ng album',
saveArtwork: 'I-save ang mga Cover',
coverImageTemplate: 'Template ng pangalan ng cover',
saveArtworkArtist: 'I-save ang imahe ng artist',
artistImageTemplate: 'Template ng imahe ng artist',
localArtworkSize: 'Laki ng lokal na artwork',
embeddedArtworkSize: 'Laki ng Nakadikit na artwork',
localArtworkFormat: {
title: 'Anong gusto mong format para sa mga lokal na artwork?',
jpg: 'jpeg na imahe',
png: 'png na imahe',
both: 'Parehong jpeg at png'
},
jpegImageQuality: 'Kalidad ng JPEG na imahe',
embeddedArtworkPNG: 'I-save ang nakadikit na artwork bilang PNG',
embeddedPNGWarning: 'Ang mga PNG ay hindi opisyal na suportado ng Deezer at maaaring magkaroon ng bug',
imageSizeWarning: 'Lahat ng mas mataas sa x1200 ay hindi opisyal na ginagamit sa Deezer, at posibleng magkaroon ng isyu',
coverDescriptionUTF8: 'I-save ang deskripsyon ng cover gamit ng UTF8 (iTunes Cover Fix)'
},
tags: {
head: 'Aling tag ang ise-save',
title: 'Pamagat',
artist: 'Artist',
album: 'Album',
cover: 'Cover',
trackNumber: 'Bilang ng Track',
trackTotal: 'Kabuuang Track',
discNumber: 'Bilang ng Disk',
discTotal: 'Kabuuang Disk',
albumArtist: 'Album Artist',
genre: 'Genre',
year: 'Taon',
date: 'Petsa',
explicit: 'Mga Explicit na Lyric',
isrc: 'ISRC',
length: 'Haba ng Track',
barcode: 'Barcode ng Album (UPC)',
bpm: 'BPM',
replayGain: 'Replay Gain',
label: 'Label ng Album',
lyrics: 'Unsynchronized na mga Lyric',
syncedLyrics: 'Synchronized na mga Lyric',
copyright: 'Karapatang Ari',
composer: 'Komposer',
involvedPeople: 'Mga Kasamang Tao'
},
other: {
title: 'Iba pa',
savePlaylistAsCompilation: 'I-save ang mga playlist bilang compilation',
useNullSeparator: 'Gumamit ng panghiwalay sa null',
saveID3v1: 'I-save rin ang ID3v1',
multiArtistSeparator: {
title: 'Anong gusto mo para maihanay mga artist?',
nothing: 'I-save lang ang pangunahing artist',
default: 'Gamit ng standard na specification',
andFeat: 'Gamit ng & at feat.',
using: 'Gamit ng "{separator}"'
},
singleAlbumArtist: 'I-save lang ang pangunahing album ng artist',
albumVariousArtists: 'Isama ang "Various Artists" sa mga Album Artist',
removeAlbumVersion: 'Tanggalin ang "Album Version" sa pamagat ng track',
removeDuplicateArtists: 'Tanggalin ang kombinasyon ng mga artist',
dateFormat: {
title: 'Format ng petsa para sa mga FLAC file',
year: 'YYYY',
month: 'MM',
day: 'DD'
},
featuredToTitle: {
title: 'Anong gagawin ko sa mga itinampok na artist',
0: 'Wala',
1: 'Tanggalin mo sila sa Pamagat',
3: 'Tanggalin mo sila sa Pamagat mismo at Pamagat ng Album',
2: 'Ilipat mo sila sa pamagat'
},
titleCasing: 'Pagleletra sa Pamagat',
artistCasing: 'Pagleletra sa Artist',
casing: {
nothing: 'Walang babaguhin',
lower: 'maliliit',
upper: 'MALALAKI',
start: 'Simula Ng Bawata Salita',
sentence: 'Kagaya ng pangungusap'
},
previewVolume: 'Volume ng Preview',
executeCommand: {
title: 'Mga gagawin pagkatapos ng download',
description: 'Hayaan lang na blangko kung wala'
}
},
spotify: {
title: 'Spotify Features',
clientID: 'Spotify ClientID',
clientSecret: 'Spotify Client Secret',
username: 'Spotify Username',
question: 'Paano ma-enable ang Spotify Features?'
},
reset: 'Ibalik sa Dati',
save: 'I-save',
toasts: {
init: 'Ikinarga ang mga Setting!',
update: 'In-update ang mga Setting!',
ARLcopied: 'Kinopya ang ARL sa clipboard'
}
},
sidebar: {
home: 'tahanan',
search: 'maghanap',
charts: 'mga chart',
favorites: 'mga paborito',
linkAnalyzer: 'tagasuri ng link',
settings: 'mga setting',
about: 'tungkol sa'
},
tracklist: {
downloadSelection: 'Pagpipili ng download'
}
}
export default ph

View File

@ -39,11 +39,12 @@
license: 'Licença'
},
subtitles: {
bugReports: "Há algo não funcionando no deemix? Nos diga!",
bugReports: 'Há algo não funcionando no deemix? Nos diga!',
contributing: 'Você quer contribuir para este projeto? Você pode fazer isso de diferentes maneiras!',
donations: 'Você quer contribuir monetariamente? Você pode fazer uma doação!'
},
usesLibrary: 'Esse app usa a biblioteca do <strong>deemix</strong>, no qual você pode usar para criar sua própria UI para o deemix',
usesLibrary:
'Esse app usa a biblioteca do <strong>deemix</strong>, no qual você pode usar para criar sua própria UI para o deemix',
thanks: `Agradecimentos para <strong>rtonno</strong>, <strong>uhwot</strong> e <strong>lollilol</strong> por ajudar neste projeto, e para <strong>BasCurtiz</strong> e <strong>scarvimane</strong> por fazerem o ícone`,
upToDate: `Para mais novidades siga o <a href="https://t.me/RemixDevNews" target="_blank">news channel</a> no Telegram.`,
officialWebsite: 'Site Oficial',
@ -79,7 +80,7 @@
invalidURL: 'URL inválida',
unsupportedURL: 'URL não suportada ainda',
ISRCnotOnDeezer: 'Faixa ISRC não está disponível ainda no deezer',
notYourPrivatePlaylist: "Você não pode baixar playlists privadas.",
notYourPrivatePlaylist: 'Você não pode baixar playlists privadas.',
spotifyDisabled: 'Os Recursos do Spotify não foram configurados corretamente.',
trackNotOnDeezer: 'Faixa não encontrada no deezer!',
albumNotOnDeezer: 'Album not found on deezer! Álbum não encontrado no deezer!',
@ -89,8 +90,8 @@
wrongBitrate: 'Faixa não encontrada no bitrate desejado.',
wrongBitrateNoAlternative: 'Faixa não encontrada no bitrate desejado e nenhuma outra alternativa encontrada!',
no360RA: 'Faixa não disponível na qualidade Reality Audio 360.',
notAvailable: "Faixa não disponível nos servidores do deezer!",
notAvailableNoAlternative: "Faixa não disponível nos servidores do deezer e nenhuma outra alternativa encontrada!"
notAvailable: 'Faixa não disponível nos servidores do deezer!',
notAvailableNoAlternative: 'Faixa não disponível nos servidores do deezer e nenhuma outra alternativa encontrada!'
}
},
favorites: {
@ -111,7 +112,7 @@
linkAnalyzer: {
info: 'Você pode usar essa seção para encontrar mais informações sobre o link que você quer baixar.',
useful:
"Isso é útil se você está tentando baixar algumas faixas que não estão disponíveis no seu país, e quer saber onde elas estão disponíveis, por exemplo.",
'Isso é útil se você está tentando baixar algumas faixas que não estão disponíveis no seu país, e quer saber onde elas estão disponíveis, por exemplo.',
linkNotSupported: 'Esse link não é suportado ainda',
linkNotSupportedYet: 'Parece que esse link não é suportado ainda, tente analizar outro.',
table: {
@ -151,7 +152,7 @@
loggingIn: 'Logando',
loggedIn: 'Logado',
alreadyLogged: 'Você já está logado',
loginFailed: "Não foi possivel entrar",
loginFailed: 'Não foi possivel entrar',
loggedOut: 'Desconectando',
cancellingCurrentItem: 'Cancelando item atual.',
currentItemCancelled: 'Item atual cancelado.',
@ -215,7 +216,7 @@
overwriteFile: {
title: 'Sobrescrever arquivos?',
y: 'Sim, sobrescrever arquivos',
n: "Não, não sobrescrever arquivos",
n: 'Não, não sobrescrever arquivos',
t: 'Sobrescrever apenas as tags'
},
fallbackBitrate: 'Taxa de bits reserva',

View File

@ -1,54 +1,24 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// Languages
import it from '@/lang/it'
import en from '@/lang/en'
import es from '@/lang/es'
import de from '@/lang/de'
import fr from '@/lang/fr'
import id from '@/lang/id'
import pt from '@/lang/pt-pt'
import pt_br from '@/lang/pt-br'
import ru from '@/lang/ru'
import tr from '@/lang/tr'
import vn from '@/lang/vn'
import hr from '@/lang/hr'
import ar from '@/lang/ar'
import ko from '@/lang/ko'
import { locales } from '@/lang/index'
Vue.use(VueI18n)
const DEFAULT_LANG = 'en'
const storedLocale = localStorage.getItem('locale')
const DEFAULT_LANG = storedLocale || 'en'
document.querySelector('html').setAttribute('lang', DEFAULT_LANG)
const locales = {
it,
en,
es,
de,
fr,
id,
pt,
pt_br,
ru,
tr,
vn,
hr,
ar,
ko
}
const i18n = new VueI18n({
locale: DEFAULT_LANG,
fallbackLocale: DEFAULT_LANG,
fallbackLocale: 'en',
messages: locales,
pluralizationRules: {
/**
* @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
* @param choicesLength {number} an overall amount of available choices
* @returns a final choice index to select plural word by
* @param {number} choice A choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
* @param {number} choicesLength An overall amount of available choices
* @returns A final choice index to select plural word by
*/
ru: function(choice, choicesLength) {
var n = Math.abs(choice) % 100

View File

@ -3,17 +3,17 @@ import VueRouter from 'vue-router'
import { socket } from '@/utils/socket'
import EventBus from '@/utils/EventBus'
import ArtistTab from '@components/ArtistTab.vue'
import TracklistTab from '@components/TracklistTab.vue'
import TheHomeTab from '@components/TheHomeTab.vue'
import TheChartsTab from '@components/TheChartsTab.vue'
import TheFavoritesTab from '@components/TheFavoritesTab.vue'
import TheErrorsTab from '@components/TheErrorsTab.vue'
import TheLinkAnalyzerTab from '@components/TheLinkAnalyzerTab.vue'
import TheAboutTab from '@components/TheAboutTab.vue'
import TheSettingsTab from '@components/TheSettingsTab.vue'
import TheMainSearch from '@components/TheMainSearch.vue'
// Pages
import About from '@components/pages/About.vue'
import Artist from '@components/pages/Artist.vue'
import Charts from '@components/pages/Charts.vue'
import Errors from '@components/pages/Errors.vue'
import Favorites from '@components/pages/Favorites.vue'
import Home from '@components/pages/Home.vue'
import LinkAnalyzer from '@components/pages/LinkAnalyzer.vue'
import Search from '@components/pages/Search.vue'
import Settings from '@components/pages/Settings.vue'
import Tracklist from '@components/pages/Tracklist.vue'
Vue.use(VueRouter)
@ -21,7 +21,7 @@ const routes = [
{
path: '/',
name: 'Home',
component: TheHomeTab,
component: Home,
meta: {
notKeepAlive: true
}
@ -29,17 +29,32 @@ const routes = [
{
path: '/tracklist/:type/:id',
name: 'Tracklist',
component: TracklistTab
component: Tracklist
},
{
path: '/artist/:id',
name: 'Artist',
component: ArtistTab
component: Artist
},
{
path: '/album/:id',
name: 'Album',
component: Tracklist
},
{
path: '/tracklist/:id',
name: 'Playlist',
component: Tracklist
},
{
path: '/tracklist/:id',
name: 'Spotify Playlist',
component: Tracklist
},
{
path: '/charts',
name: 'Charts',
component: TheChartsTab,
component: Charts,
meta: {
notKeepAlive: true
}
@ -47,7 +62,7 @@ const routes = [
{
path: '/favorites',
name: 'Favorites',
component: TheFavoritesTab,
component: Favorites,
meta: {
notKeepAlive: true
}
@ -55,32 +70,32 @@ const routes = [
{
path: '/errors',
name: 'Errors',
component: TheErrorsTab
component: Errors
},
{
path: '/link-analyzer',
name: 'Link Analyzer',
component: TheLinkAnalyzerTab
component: LinkAnalyzer
},
{
path: '/about',
name: 'About',
component: TheAboutTab
component: About
},
{
path: '/settings',
name: 'Settings',
component: TheSettingsTab
component: Settings
},
{
path: '/search',
name: 'Search',
component: TheMainSearch
component: Search
},
// 404 client side
{
path: '*',
component: TheHomeTab
component: Home
}
]
@ -109,6 +124,24 @@ router.beforeEach((to, from, next) => {
id: to.params.id
}
break
case 'Album':
getTracklistParams = {
type: 'album',
id: to.params.id
}
break
case 'Playlist':
getTracklistParams = {
type: 'playlist',
id: to.params.id
}
break
case 'Spotify Playlist':
getTracklistParams = {
type: 'spotifyplaylist',
id: to.params.id
}
break
default:
break

View File

@ -72,7 +72,9 @@ const mutations = {
},
RESET_LOGIN(state) {
// Needed for reactivity
let clientMode = state.clientMode
Object.assign(state, getDefaultState())
state.clientMode = clientMode
}
}

View File

@ -1,58 +1,9 @@
/* Center section */
$icon-dimension: 2rem;
$searchbar-height: calc(2rem + 1em);
#search {
background-color: var(--secondary-background);
width: 100%;
padding: 0 1em;
margin-bottom: 20px;
margin-right: 32px;
display: flex;
align-items: center;
border: 1px solid transparent;
transition: border 200ms ease-in-out;
.search__icon {
width: $icon-dimension;
height: $icon-dimension;
i {
font-size: $icon-dimension;
color: var(--foreground);
@include remove-selection-background;
}
}
&:focus-within {
border: 1px solid var(--foreground);
}
#searchbar {
height: $searchbar-height;
padding-left: 0.5em;
border: 0px;
border-radius: 0px;
background-color: var(--secondary-background);
color: var(--foreground);
font-size: 2rem;
font-family: 'Open Sans';
font-weight: 300;
&:focus {
outline: none;
}
// Removing Chrome autofill color
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 $searchbar-height var(--secondary-background) inset !important;
box-shadow: 0 0 0 $searchbar-height var(--secondary-background) inset !important;
}
}
}
#container {

View File

@ -20,6 +20,8 @@ $sidebar-delay: 75ms;
.main_tablinks {
transition: all 500ms;
text-decoration: none;
color: inherit;
&.active {
background-color: var(--accent-color);

View File

@ -295,14 +295,22 @@ a {
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background-color: #000000;
border-radius: 50%;
min-width: 32px;
padding: 0px;
height: 44px;
border: 0px;
i {
background-color: #000000;
color: white;
border-radius: 50%;
padding: 10px;
cursor: pointer;
}
&:focus {
opacity: 1;
}
}
&:hover {
@ -312,6 +320,7 @@ a {
.download_overlay {
opacity: 1;
border: 0px;
}
}
}

View File

@ -14,7 +14,6 @@ export async function adjustVolume(element, newVolume, { duration = 1000, easing
return new Promise(resolve => {
const timer = setInterval(() => {
element.volume = originalVolume + easing(tick / ticks) * delta
// console.log(element.volume)
if (++tick === ticks) {
clearInterval(timer)
resolve()

View File

@ -1,19 +1,20 @@
import it from 'svg-country-flags/svg/it.svg'
import gb from 'svg-country-flags/svg/gb.svg'
import es from 'svg-country-flags/svg/es.svg'
import es from 'flag-icon-css/flags/4x3/es.svg'
import de from 'svg-country-flags/svg/de.svg'
import fr from 'svg-country-flags/svg/fr.svg'
import id from 'svg-country-flags/svg/id.svg'
import pt from 'svg-country-flags/svg/pt.svg'
import pt from 'flag-icon-css/flags/4x3/pt.svg'
import br from 'svg-country-flags/svg/br.svg'
import ru from 'svg-country-flags/svg/ru.svg'
import tr from 'svg-country-flags/svg/tr.svg'
import vn from 'svg-country-flags/svg/vn.svg'
import hr from 'svg-country-flags/svg/hr.svg'
import hr from 'flag-icon-css/flags/4x3/hr.svg'
import ar from '@/assets/ar.svg'
import ko from 'svg-country-flags/svg/kr.svg'
import ko from 'flag-icon-css/flags/4x3/kr.svg'
import ph from 'flag-icon-css/flags/4x3/ph.svg'
export default {
export const flags = {
it,
en: gb,
es,
@ -27,5 +28,6 @@ export default {
vn,
hr,
ar,
ko
ko,
ph
}

4
src/utils/texts.js Normal file
View File

@ -0,0 +1,4 @@
/**
* @param {string} text
*/
export const upperCaseFirstLowerCaseRest = text => text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()