freezerpc/app/client/src/views/Settings.vue

487 lines
20 KiB
Vue

<template>
<div>
<h1 class='pb-2'>{{$t('Settings')}}</h1>
<v-list>
<v-select
class='px-4 mx-2'
:label='$t("Streaming Quality")'
persistent-hint
:items='qualities'
@change='updateStreamingQuality'
v-model='streamingQuality'
></v-select>
<v-select
class='px-4 mx-2'
:label='$t("Download Quality")'
persistent-hint
:items='qualities'
@change='updateDownloadQuality'
v-model='downloadQuality'
></v-select>
<!-- Download path -->
<v-text-field
class='px-4 mx-2'
:label='$t("Downloads Directory")'
v-model='$root.settings.downloadsPath'
append-icon='mdi-open-in-app'
@click:append='selectDownloadPath'
></v-text-field>
<!-- Download threads -->
<v-slider
:label='$t("Simultaneous downloads")'
min='1'
max='16'
thumb-label
step='1'
ticks
dense
class='px-4 mx-2'
v-model='$root.settings.downloadThreads'
></v-slider>
<!-- Download dialog -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.downloadDialog' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Show download dialog")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("Always show download confirm dialog before downloading.")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Create playlist folder -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.playlistFolder' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Create folders for playlists")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Create artist folder -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.createArtistFolder' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Create folders for artists")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Create album folder -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.createAlbumFolder' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Create folders for albums")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Download Cover -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.downloadCover' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Download album cover")}}</v-list-item-title>
</v-list-item-content>
<!-- Resolution -->
<v-select :items='artResolutions' :label='$t("Art Resolution")' v-model='$root.settings.coverResolution' style='max-width: 150px;'></v-select>
</v-list-item>
<!-- Download lyrics -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.downloadLyrics' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Download lyrics")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Download naming -->
<v-text-field
class='px-4 my-2'
:label='$t("Download Filename")'
persistent-hint
v-model='$root.settings.downloadFilename'
:hint='$t("Variables") + ": %title%, %artists%, %artist%, %feats%, %trackNumber%, %0trackNumber%, %album%, %year%, %label%"'
></v-text-field>
<!-- Crossfade -->
<v-slider
:label='$t("Crossfade (ms)")'
min='0'
max='10000'
thumb-label
step='500'
ticks
class='px-4 mt-4 mx-2'
v-model='$root.settings.crossfadeDuration'
></v-slider>
<!-- UI -->
<v-subheader>{{$t("UI")}}</v-subheader>
<v-divider></v-divider>
<!-- Language -->
<v-select
class='mt-2 px-4 mx-2'
:label='$t("Language")'
persistent-hint
:items='languageNames'
@change='updateLanguage'
></v-select>
<!-- Primary color -->
<v-list-item @click='colorPicker = true'>
<v-list-item-avatar>
<v-icon>mdi-palette</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class='pl-2'>{{$t("Select primary color")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Autocomplete -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.showAutocomplete' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Show autocomplete in search")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Keep sidebar open -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.sidebarOpen' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Keep sidebar open")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("WARNING: Might require reload to work properly!")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Accounts -->
<v-subheader>{{$t("Integrations")}}</v-subheader>
<v-divider></v-divider>
<!-- Log listening -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.logListen' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Log track listens to Deezer")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("This allows listening history, flow and recommendations to work properly.")}}</v-list-item-subtitle>
</v-list-item-content>
</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 class='pl-2'>{{$t("Login with LastFM")}}</v-list-item-title>
<v-list-item-subtitle class='pl-2'>{{$t("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'>{{$t("Disconnect LastFM")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Discord -->
<v-list-item>
<v-list-item-action>
<v-checkbox class='pl-2' v-model='$root.settings.enableDiscord' @click='snackbarText = $t("Requires restart to apply!"); snackbar = true'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Discord Rich Presence")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("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 class='pl-2' v-model='$root.settings.discordJoin' @click='snackbarText = $t("Requires restart to apply!"); snackbar = true'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Discord Join Button")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("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>{{$t("Other")}}</v-subheader>
<v-divider></v-divider>
<div class='d-flex mx-4 pt-2'>
<v-select
v-model='$root.settings.contentLanguage'
:items='languageList'
:label='$t("Content language")'
class='mr-4'
></v-select>
<v-select
v-model='$root.settings.contentCountry'
:items='countryList'
:label='$t("Content country")'
class='ml-4'
></v-select>
</div>
<!-- Minimize to tray -->
<v-list-item v-if='$root.settings.electron'>
<v-list-item-action>
<v-checkbox v-model='$root.settings.minimizeToTray' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Minimize to tray")}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Close on exit -->
<v-list-item v-if='$root.settings.electron'>
<v-list-item-action>
<v-checkbox v-model='$root.settings.closeOnExit' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Close on exit")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("Don't minimize to tray")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Force white tray icon -->
<v-list-item v-if='$root.settings.electron'>
<v-list-item-action>
<v-checkbox v-model='$root.settings.forceWhiteTrayIcon' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Force white tray icon")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("Force default (white) tray icon if theme incorrectly detected. Requires restart.")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Logout -->
<v-btn block color='red' class='mt-4' @click='logout'>
<v-icon>mdi-logout</v-icon>
{{$t("Logout")}}
</v-btn>
</v-list>
<v-btn fab color='primary' absolute bottom right class='mb-12' @click='save' :loading='saving'>
<v-icon>mdi-content-save</v-icon>
</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"
>
{{$t("Dismiss")}}
</v-btn>
</template>
</v-snackbar>
<!-- Color picker overlay -->
<v-overlay :value='colorPicker' elevation='2'>
<v-card>
<v-color-picker v-model='$root.settings.primaryColor' mode='hexa'></v-color-picker>
<v-btn :color='$root.settings.primaryColor' block class='my-1 px-2' @click='saveColor'>
{{$t("Save")}}
</v-btn>
</v-card>
</v-overlay>
</div>
</template>
<script>
export default {
name: 'Settings',
data() {
return {
saving: false,
qualities: [
'MP3 128kbps',
'MP3 320kbps',
'FLAC ~1441kbps'
],
streamingQuality: null,
downloadQuality: null,
devToolsCounter: 0,
snackbarText: null,
snackbar: false,
language: 'en',
languages: [
{code: 'en', name: 'English'},
{code: 'ar', name: 'Arabic'},
{code: 'ast', name: 'Asturian'},
{code: 'hr', name: 'Croatian'},
{code: 'fil', name: 'Filipino'},
{code: 'fr', name: 'French'},
{code: 'de', name: 'German'},
{code: 'el', name: 'Greek'},
{code: 'id', name: 'Indonesian'},
{code: 'it', name: 'Italian'},
{code: 'pl', name: 'Polish'},
{code: 'pt', name: 'Portuguese'},
{code: 'ro', name: 'Romanian'},
{code: 'ru', name: 'Russian'},
{code: 'sk', name: 'Slovak'},
{code: 'es', name: 'Spanish'},
{code: 'tr', name: 'Turkish'},
{code: 'uk', name: 'Ukrainian'},
{code: 'vi', name: 'Vietnamese'},
{code: 'uwu', name: 'Furry'}
],
artResolutions: [256, 512, 600, 800, 1000, 1200, 1400, 1600, 1800],
colorPicker: false,
primaryColorIndex: 0,
primaries: ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4',
'#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722',
'#795548', '#607D8B', '#9E9E9E'],
//Lists from Deezer website
languageList: ["me", "da", "de", "en", "us", "es", "mx", "fr", "hr", "id", "it", "hu", "ms", "nl", "no", "pl", "br", "pt", "ru", "ro", "sq", "sk", "sl", "sr", "fi", "sv", "tr", "cs", "bg", "uk", "he", "ar", "th", "cn", "ja", "ko"],
countryList: ["AF", "AL", "DZ", "AO", "AI", "AG", "AR", "AM", "AU", "AT", "AZ", "BH", "BD", "BB", "BY", "BE", "BJ", "BT", "BO", "BA", "BW", "BR", "IO", "VG", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CX", "CC", "CO", "CK", "CR", "HR", "CY", "CZ", "CD", "DK", "DJ", "DM", "TL", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FM", "FJ", "FI", "FR", "GA", "GM", "GE", "DE", "GH", "GR", "GD", "GT", "GN", "GW", "HN", "HU", "IS", "ID", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LT", "LU", "MK", "MG", "MW", "MY", "ML", "MT", "MH", "MR", "MU", "MX", "MD", "MN", "ME", "MS", "MA", "MZ", "NA", "NR", "NP", "NZ", "NI", "NE", "NG", "NU", "NF", "NO", "OM", "PK", "PW", "PA", "PG", "PY", "PE", "PL", "PT", "QA", "CG", "RO", "RU", "RW", "KN", "LC", "VC", "WS", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "SI", "SO", "ZA", "ES", "LK", "SJ", "SZ", "SE", "CH", "TJ", "TZ", "TH", "KM", "FK", "CI", "MV", "NL", "PH", "PN", "SB", "TG", "TK", "TO", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UY", "UZ", "VU", "VE", "VN", "YE", "ZM", "ZW"],
}
},
methods: {
//Save settings
save() {
this.saving = true;
this.$root.saveSettings();
//Artificial wait to make it seem like something happened.
setTimeout(() => {this.saving = false;}, 500);
this.snackbarText = this.$t("Settings saved!");
this.snackbar = true;
},
getQuality(v) {
let i = this.qualities.indexOf(v);
if (i == 0) return 1;
if (i == 1) return 3;
if (i == 2) return 9;
return 3;
},
//Update streaming quality
updateStreamingQuality(v) {
this.$root.settings.streamQuality = this.getQuality(v);
},
updateDownloadQuality(v) {
this.$root.settings.downloadsQuality = this.getQuality(v);
},
//Quality to show currently selected quality
getPresetQuality(q) {
if (q == 9) return this.qualities[2];
if (q == 3) return this.qualities[1];
if (q == 1) return this.qualities[0];
return this.qualities[1];
},
//Select download path, electron only
selectDownloadPath() {
//Electron check
if (!this.$root.settings.electron) {
alert(this.$t("Available only in Electron version!"));
return;
}
const {ipcRenderer} = window.require('electron');
ipcRenderer.on('selectDownloadPath', (event, newPath) => {
if (newPath) this.$root.settings.downloadsPath = newPath;
});
ipcRenderer.send('selectDownloadPath');
},
async logout() {
this.$root.settings.arl = null;
await this.$root.saveSettings();
location.reload();
},
//Redirect to lastfm login
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();
},
saveColor() {
this.colorPicker = false;
this.$vuetify.theme.themes.dark.primary = this.$root.settings.primaryColor;
this.$vuetify.theme.themes.light.primary = this.$root.settings.primaryColor;
this.$root.saveSettings();
},
updateLanguage(l) {
let code = this.languages.filter(lang => lang.name == l)[0].code;
this.language = code;
this.$root.updateLanguage(code);
this.$root.settings.language = code;
},
//Update light theme
changeLightTheme(v) {
this.$root.settings.lightTheme = v;
if (v) {
this.$vuetify.theme.dark = false;
this.$vuetify.theme.light = true;
} else {
this.$vuetify.theme.dark = true;
this.$vuetify.theme.light = false;
}
}
},
computed: {
languageNames() {
return this.languages.map(l => l.name);
}
},
mounted() {
this.streamingQuality = this.getPresetQuality(this.$root.settings.streamQuality);
this.downloadQuality = this.getPresetQuality(this.$root.settings.downloadsQuality);
//Press 'f' 10 times, to open dev tools
document.addEventListener('keyup', (event) => {
if (event.keyCode === 70) {
this.devToolsCounter += 1;
} else {
this.devToolsCounter = 0;
}
if (this.devToolsCounter == 10) {
this.devToolsCounter = 0;
if (this.$root.settings.electron) {
const {remote} = window.require('electron');
remote.getCurrentWindow().toggleDevTools();
}
}
//RGB
if (event.code == 'KeyG' && event.ctrlKey && event.altKey) {
setInterval(() => {
this.$vuetify.theme.themes.dark.primary = this.primaries[this.primaryColorIndex];
this.$vuetify.theme.themes.light.primary = this.primaries[this.primaryColorIndex];
this.$root.settings.primaryColor = this.primaries[this.primaryColorIndex];
this.primaryColorIndex++;
if (this.primaryColorIndex == this.primaries.length)
this.primaryColorIndex = 0;
}, 400);
}
});
}
}
</script>