1.0.6 - Discord integration, logging, bugfixes
This commit is contained in:
@ -124,18 +124,24 @@
|
||||
<v-icon>mdi-arrow-right</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-text-field
|
||||
hide-details
|
||||
flat
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
<!-- Search -->
|
||||
<v-autocomplete
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
flat
|
||||
single-line
|
||||
solo
|
||||
clearable
|
||||
hide-no-data
|
||||
placeholder='Search or paste Deezer URL. Use "/" to quickly focus.'
|
||||
v-model="searchQuery"
|
||||
ref='searchBar'
|
||||
:loading='searchLoading'
|
||||
@keyup='search'>
|
||||
</v-text-field>
|
||||
@keyup='search'
|
||||
ref='searchBar'
|
||||
v-model='searchQuery'
|
||||
:search-input.sync='searchInput'
|
||||
:items='suggestions'
|
||||
></v-autocomplete>
|
||||
|
||||
</v-app-bar>
|
||||
|
||||
<!-- Main -->
|
||||
@ -269,7 +275,10 @@ export default {
|
||||
showPlayer: false,
|
||||
position: '0.00',
|
||||
searchQuery: '',
|
||||
searchLoading: false
|
||||
searchLoading: false,
|
||||
searchInput: null,
|
||||
suggestions: [],
|
||||
preventDoubleEnter: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -288,19 +297,24 @@ export default {
|
||||
},
|
||||
async search(event) {
|
||||
//KeyUp event, enter
|
||||
if (event.keyCode !== 13) return;
|
||||
if (event && event.keyCode !== 13) return;
|
||||
//Prevent double navigation
|
||||
if (this.preventDoubleEnter) return;
|
||||
this.preventDoubleEnter = true;
|
||||
setInterval(() => {this.preventDoubleEnter = false}, 50);
|
||||
|
||||
//Check if url
|
||||
if (this.searchQuery.startsWith('http')) {
|
||||
let query = this.searchInput;
|
||||
if (query.startsWith('http')) {
|
||||
this.searchLoading = true;
|
||||
let url = new URL(this.searchQuery);
|
||||
let url = new URL(query);
|
||||
|
||||
//Normal link
|
||||
if (url.hostname == 'www.deezer.com' || url.hostname == 'deezer.com' || url.hostname == 'deezer.page.link') {
|
||||
|
||||
//Share link
|
||||
if (url.hostname == 'deezer.page.link') {
|
||||
let res = await this.$axios.get('/fullurl?url=' + encodeURIComponent(this.searchQuery));
|
||||
let res = await this.$axios.get('/fullurl?url=' + encodeURIComponent(query));
|
||||
url = new URL(res.data.url);
|
||||
}
|
||||
|
||||
@ -332,7 +346,7 @@ export default {
|
||||
this.searchLoading = false;
|
||||
} else {
|
||||
//Normal search
|
||||
this.$router.push({path: '/search', query: {q: this.searchQuery}});
|
||||
this.$router.push({path: '/search', query: {q: query}});
|
||||
}
|
||||
},
|
||||
seek(val) {
|
||||
@ -383,6 +397,33 @@ export default {
|
||||
//Update position
|
||||
'$root.position'() {
|
||||
this.position = (this.$root.position / this.$root.duration()) * 100;
|
||||
},
|
||||
//Autofill
|
||||
searchInput(query) {
|
||||
//Filters
|
||||
if (query && query.startsWith('/')) {
|
||||
query = query.substring(1);
|
||||
this.searchInput = query;
|
||||
}
|
||||
if (!query || (query && query.startsWith('http'))) {
|
||||
this.searchLoading = false;
|
||||
this.suggestions = [];
|
||||
return;
|
||||
}
|
||||
this.searchLoading = true;
|
||||
//Prevent spam
|
||||
setTimeout(() => {
|
||||
if (query != this.searchInput) return;
|
||||
this.$axios.get('/suggestions/' + encodeURIComponent(query)).then((res) => {
|
||||
if (query != this.searchInput) return;
|
||||
this.suggestions = res.data;
|
||||
this.searchLoading = false;
|
||||
});
|
||||
}, 300);
|
||||
},
|
||||
searchQuery(q) {
|
||||
this.searchInput = q;
|
||||
this.search(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-list-item @click='click' v-if='!card' :class='{dense: tiny}'>
|
||||
<v-list-item @click='click' v-if='!card'>
|
||||
<v-list-item-avatar>
|
||||
<v-img :src='artist.picture.thumb'></v-img>
|
||||
</v-list-item-avatar>
|
||||
|
@ -108,6 +108,17 @@ export default {
|
||||
dShow() {
|
||||
if (!this.dShow) this.$emit('close');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//Auto download
|
||||
if (!this.$root.settings.downloadDialog) {
|
||||
this.download();
|
||||
setInterval(() => {
|
||||
this.$emit('close');
|
||||
this.dShow = false;
|
||||
}, 50);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -74,10 +74,6 @@ export default {
|
||||
},
|
||||
//Play track
|
||||
async play(index) {
|
||||
if (this.tracks.length < this.count) {
|
||||
await this.loadAll();
|
||||
}
|
||||
|
||||
this.$root.queue.source = {
|
||||
text: 'Loved tracks',
|
||||
source: 'playlist',
|
||||
@ -85,6 +81,15 @@ export default {
|
||||
};
|
||||
this.$root.replaceQueue(this.tracks);
|
||||
this.$root.playIndex(index);
|
||||
|
||||
//Load all tracks
|
||||
if (this.tracks.length < this.count) {
|
||||
this.loadAll().then(() => {
|
||||
this.$root.replaceQueue(this.tracks);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
removedTrack(index) {
|
||||
this.tracks.splice(index, 1);
|
||||
|
@ -85,7 +85,7 @@ export default {
|
||||
},
|
||||
//Scroll to currently playing lyric
|
||||
scrollLyric() {
|
||||
if (!this.lyrics) return;
|
||||
if (!this.lyrics || !this.lyrics.lyrics || this.lyrics.lyrics.length == 0) return;
|
||||
|
||||
//Prevent janky scrolling
|
||||
if (this.currentLyricIndex == this.currentLyric()) return;
|
||||
|
@ -109,14 +109,12 @@ export default {
|
||||
methods: {
|
||||
async play() {
|
||||
let playlist = this.playlist;
|
||||
//Load playlist tracks
|
||||
if (playlist.tracks.length != playlist.trackCount) {
|
||||
let data = await this.$axios.get(`/playlist/${playlist.id}?full=iguess`);
|
||||
playlist = data.data;
|
||||
}
|
||||
//Error handling
|
||||
//Load if no tracks
|
||||
if (!playlist || playlist.tracks.length == 0)
|
||||
playlist = (await this.$axios.get(`/playlist/${playlist.id}?full=iguess`)).data;
|
||||
if (!playlist) return;
|
||||
|
||||
|
||||
//Play
|
||||
this.$root.queue.source = {
|
||||
text: playlist.title,
|
||||
source: 'playlist',
|
||||
@ -124,6 +122,12 @@ export default {
|
||||
};
|
||||
this.$root.replaceQueue(playlist.tracks);
|
||||
this.$root.playIndex(0);
|
||||
|
||||
//Load all tracks
|
||||
if (playlist.tracks.length != playlist.trackCount) {
|
||||
let data = await this.$axios.get(`/playlist/${playlist.id}?full=iguess`);
|
||||
playlist = data.data;
|
||||
}
|
||||
},
|
||||
//On click navigate to details
|
||||
click() {
|
||||
|
@ -180,12 +180,14 @@ export default {
|
||||
this.$axios.put(`/library/tracks?id=${this.track.id}`);
|
||||
},
|
||||
goAlbum() {
|
||||
this.$emit('redirect')
|
||||
this.$router.push({
|
||||
path: '/album',
|
||||
query: {album: JSON.stringify(this.track.album)}
|
||||
});
|
||||
},
|
||||
goArtist(a) {
|
||||
this.$emit('redirect');
|
||||
this.$router.push({
|
||||
path: '/artist',
|
||||
query: {artist: JSON.stringify(a)}
|
||||
|
@ -131,9 +131,11 @@ new Vue({
|
||||
this.play();
|
||||
},
|
||||
seek(t) {
|
||||
if (!this.audio) return;
|
||||
if (!this.audio || isNaN(t) || !t) return;
|
||||
//ms -> s
|
||||
this.audio.currentTime = (t / 1000);
|
||||
|
||||
this.updateState();
|
||||
},
|
||||
|
||||
//Current track duration
|
||||
@ -161,11 +163,11 @@ new Vue({
|
||||
this.savePlaybackInfo();
|
||||
},
|
||||
//Skip n tracks, can be negative
|
||||
skip(n) {
|
||||
async skip(n) {
|
||||
let newIndex = this.queue.index + n;
|
||||
//Out of bounds
|
||||
if (newIndex < 0 || newIndex >= this.queue.data.length) return;
|
||||
this.playIndex(newIndex);
|
||||
await this.playIndex(newIndex);
|
||||
},
|
||||
//Skip wrapper with shuffle
|
||||
skipNext() {
|
||||
@ -253,7 +255,7 @@ new Vue({
|
||||
}
|
||||
|
||||
//Repeat list
|
||||
if (this.queue.index == this.queue.data.length - 1) {
|
||||
if (this.repeat == 1 && this.queue.index == this.queue.data.length - 1) {
|
||||
this.skip(-(this.queue.data.length - 1));
|
||||
return;
|
||||
}
|
||||
@ -403,6 +405,18 @@ new Vue({
|
||||
|
||||
this.logListenId = this.track.id;
|
||||
await this.$axios.post(`/log`, this.track);
|
||||
},
|
||||
//Send state update to integrations
|
||||
async updateState() {
|
||||
//Wait for duration
|
||||
if (this.state == 2 && (this.duration() == null || isNaN(this.duration())))
|
||||
await new Promise((res) => setTimeout(res, 1000));
|
||||
this.$socket.emit('stateChange', {
|
||||
position: this.position,
|
||||
duration: this.duration(),
|
||||
state: this.state,
|
||||
track: this.track
|
||||
});
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@ -471,6 +485,12 @@ new Vue({
|
||||
this.sockets.subscribe('download', (data) => {
|
||||
this.download = data;
|
||||
});
|
||||
//Play at offset (for integrations)
|
||||
this.sockets.subscribe('playOffset', async (data) => {
|
||||
this.queue.data.splice(this.queue.index + 1, 0, data.track);
|
||||
await this.skip(1);
|
||||
this.seek(data.position);
|
||||
});
|
||||
|
||||
r();
|
||||
},
|
||||
@ -499,6 +519,12 @@ new Vue({
|
||||
if (e.keyCode === 106 || e.keyCode === 74) this.$root.seek((this.position - 10000));
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
//Watch state for integrations
|
||||
state() {
|
||||
this.updateState();
|
||||
}
|
||||
},
|
||||
|
||||
router,
|
||||
vuetify,
|
||||
|
@ -144,6 +144,7 @@
|
||||
><TrackTile
|
||||
:track='track'
|
||||
@click='$root.playIndex(index)'
|
||||
@redirect='close'
|
||||
></TrackTile>
|
||||
</v-lazy>
|
||||
|
||||
@ -161,14 +162,13 @@
|
||||
></AlbumTile>
|
||||
<!-- Artists -->
|
||||
<h3>Artists:</h3>
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<ArtistTile
|
||||
v-for='(artist, index) in $root.track.artists'
|
||||
:artist='artist'
|
||||
:key="index + 'a' + artist.id"
|
||||
@clicked='$emit("close")'
|
||||
tiny
|
||||
class='text-left'
|
||||
></ArtistTile>
|
||||
</v-list>
|
||||
<!-- Meta -->
|
||||
@ -179,6 +179,7 @@
|
||||
<h3>Source: {{$root.playbackInfo.source}}</h3>
|
||||
<h3>Format: {{$root.playbackInfo.format}}</h3>
|
||||
<h3>Quality: {{$root.playbackInfo.quality}}</h3>
|
||||
<h3>ID: {{$root.track.id}}</h3>
|
||||
</v-list>
|
||||
</v-tab-item>
|
||||
<!-- Lyrics -->
|
||||
|
@ -87,9 +87,8 @@ export default {
|
||||
methods: {
|
||||
async playIndex(index) {
|
||||
//Load tracks
|
||||
if (this.playlist.tracks.length < this.playlist.trackCount) {
|
||||
if (this.playlist.tracks.length == 0)
|
||||
await this.loadAllTracks();
|
||||
}
|
||||
|
||||
this.$root.queue.source = {
|
||||
text: this.playlist.title,
|
||||
@ -98,6 +97,13 @@ export default {
|
||||
};
|
||||
this.$root.replaceQueue(this.playlist.tracks);
|
||||
this.$root.playIndex(index);
|
||||
|
||||
//Load rest of tracks on background
|
||||
if (this.playlist.tracks.length < this.playlist.trackCount) {
|
||||
this.loadAllTracks().then(() => {
|
||||
this.$root.replaceQueue(this.playlist.tracks);
|
||||
});
|
||||
}
|
||||
},
|
||||
play() {
|
||||
this.playIndex(0);
|
||||
|
@ -28,6 +28,17 @@
|
||||
append-icon='mdi-open-in-app'
|
||||
@click:append='selectDownloadPath'
|
||||
></v-text-field>
|
||||
|
||||
<!-- Download dialog -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
<v-checkbox v-model='$root.settings.downloadDialog'></v-checkbox>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Show download dialog</v-list-item-title>
|
||||
<v-list-item-subtitle>Always show download confirm dialog before downloading.</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Create artist folder -->
|
||||
<v-list-item>
|
||||
@ -57,6 +68,10 @@
|
||||
hint='Variables: %title%, %artists%, %artist%, %feats%, %trackNumber%, %0trackNumber%, %album%'
|
||||
></v-text-field>
|
||||
|
||||
<!-- Accounts -->
|
||||
<v-subheader>Integrations</v-subheader>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<!-- Log listening -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
@ -69,11 +84,46 @@
|
||||
</v-list-item>
|
||||
<!-- LastFM -->
|
||||
<v-list-item @click='connectLastFM' v-if='!$root.settings.lastFM'>
|
||||
<v-list-item-avatar>
|
||||
<v-img src='lastfm.svg'></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Login with LastFM</v-list-item-title>
|
||||
<v-list-item-subtitle>Connect your LastFM account to allow scrobbling.</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item v-if='$root.settings.lastFM' @click='disconnectLastFM'>
|
||||
<v-list-item-avatar>
|
||||
<v-icon>mdi-logout</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class='red--text'>Disconnect LastFM</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<!-- Discord -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
<v-checkbox v-model='$root.settings.enableDiscord' @click='snackbarText = "Requires restart to apply!"; snackbar = true'></v-checkbox>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Discord Rich Presence</v-list-item-title>
|
||||
<v-list-item-subtitle>Enable Discord Rich Presence, requires restart to toggle!</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<!-- Discord Join Button -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
<v-checkbox v-model='$root.settings.discordJoin' @click='snackbarText = "Requires restart to apply!"; snackbar = true'></v-checkbox>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Discord Join Button</v-list-item-title>
|
||||
<v-list-item-subtitle>Enable Discord join button for syncing tracks, requires restart to toggle!</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Misc -->
|
||||
<v-subheader>Other</v-subheader>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<!-- Minimize to tray -->
|
||||
<v-list-item v-if='$root.settings.electron'>
|
||||
@ -108,6 +158,22 @@
|
||||
Save
|
||||
</v-btn>
|
||||
|
||||
<!-- Info snackbar -->
|
||||
<v-snackbar v-model="snackbar">
|
||||
{{ snackbarText }}
|
||||
|
||||
<template v-slot:action="{ attrs }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
text
|
||||
v-bind="attrs"
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Dismiss
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -124,7 +190,9 @@ export default {
|
||||
],
|
||||
streamingQuality: null,
|
||||
downloadQuality: null,
|
||||
devToolsCounter: 0
|
||||
devToolsCounter: 0,
|
||||
snackbarText: null,
|
||||
snackbar: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -178,6 +246,12 @@ export default {
|
||||
async connectLastFM() {
|
||||
let res = await this.$axios.get('/lastfm');
|
||||
window.location.replace(res.data.url);
|
||||
},
|
||||
//Disconnect LastFM
|
||||
async disconnectLastFM() {
|
||||
this.$root.settings.lastFM = null;
|
||||
await this.$root.saveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
Reference in New Issue
Block a user