Hyperpipe/src/components/Prefs.vue
Shiny Nematoda 0218c2ca43
Minor fixes
- Avoid queue when fetching lyrics id
- Better error handling for logout
2023-07-19 10:37:40 +02:00

438 lines
10 KiB
Vue

<script setup>
import { ref, computed, onMounted } from 'vue';
import {
PIPED_INSTANCE,
HYPERPIPE_INSTANCE,
getJson,
useAuthLogout,
} from '@/scripts/fetch.js';
import { useStore } from '@/scripts/util.js';
import { SUPPORTED_LOCALES, useI18n } from '@/stores/misc.js';
const date = ref('unknown');
import('@/assets/version.json').then(v => {
date.value = v.date;
});
const { t, setupLocale } = useI18n(),
store = useStore(),
instances = ref([]),
hypInstances = ref([]),
next = ref(false),
compact = ref(false);
getJson('https://piped-instances.kavin.rocks')
.then(i => i || getJson('https://instances.tokhmi.xyz'))
.then(i => {
instances.value = i;
console.log(i);
});
getJson('https://raw.codeberg.page/Hyperpipe/pages/api/backend.json').then(
i => {
hypInstances.value = i;
console.log(i);
},
);
function getBool(val) {
return 'bi ' + (val ? 'bi-check2' : 'bi-x-lg');
}
function getStore(key) {
return store.getItem(key);
}
function setStore(key, value) {
store.setItem(key, value);
}
function getTheme() {
return document.body.getAttribute('data-theme') || 'dark';
}
function setTheme(theme) {
document.body.setAttribute('data-theme', theme);
setStore('theme', theme);
}
function setLang(locale) {
setupLocale(locale);
setStore('locale', locale);
}
function setCodec(codec) {
setStore('codec', codec);
if (window.audioPlayer)
window.audioPlayer.configure('preferredAudioCodecs', codec.split(':'));
}
function getStoreBool(key, ele, def) {
ele.value = getStore(key) || def;
}
async function setAuth(key) {
if (getStore('authapi')) {
if (!confirm('This requires a logout. Confirm logout?')) return;
const res = await useAuthLogout();
if (!res && res?.error)
if (!confirm(`Got Error ${res ? ': ' + res.error : ''}. Continue?`))
return;
}
store.removeItem('auth');
setStore('authapi', key);
}
const verifyApi = computed(() =>
hypInstances.value
.map(i => i.api_url.replace('https://', '').replace('http://', ''))
.includes(getStore('api') || HYPERPIPE_INSTANCE),
),
verifyPipedApi = computed(() =>
instances.value
.map(i => i.api_url.replace('https://', ''))
.includes(getStore('pipedapi') || PIPED_INSTANCE),
),
verifyAuthApi = computed(() =>
instances.value
.map(i => i.api_url.replace('https://', ''))
.includes(getStore('authapi') || PIPED_INSTANCE),
);
onMounted(() => {
getStoreBool('next', next, true);
getStoreBool('compact', compact, false);
});
</script>
<template>
<h2>{{ t('pref.theme') }}</h2>
<select
id="pref-theme"
class="input"
:value="getTheme()"
@change="setTheme($event.target.value)">
<option value="dark">{{ t('pref.dark') }}</option>
<option value="light">{{ t('pref.light') }}</option>
<option value="blur">{{ t('pref.blur') }}</option>
<option value="light blur">{{ t('pref.blur_light') }}</option>
<option value="dracula">{{ t('pref.dracula') }}</option>
<option value="nord">{{ t('pref.nord') }}</option>
</select>
<div class="left">
<input
type="checkbox"
name="pref-chk-compact"
id="pref-chk-compact"
class="input"
@change="setStore('compact', $event.target.checked)"
v-model="compact" />
<label for="pref-chk-compact">{{ t('pref.compact') }}</label>
</div>
<h2>{{ t('pref.language') }}</h2>
<select
id="pref-lang"
class="input"
:value="getStore('locale') || 'en'"
@change="setLang($event.target.value)">
<option v-for="i in SUPPORTED_LOCALES" :value="i.code" :key="i.code">
{{ i.name }}
</option>
</select>
<h2>{{ t('pref.tab') }}</h2>
<select
id="pref-page"
class="input"
:value="getStore('page') || 'home'"
@change="setStore('page', $event.target.value)">
<option value="home">{{ t('pref.home') }}</option>
<option value="explore">{{ t('pref.explore') }}</option>
<option value="charts">{{ t('pref.charts') }}</option>
<option value="library">{{ t('pref.library') }}</option>
</select>
<h2>{{ t('pref.player') }}</h2>
<div class="left">
<input
type="checkbox"
name="pref-chk-next"
id="pref-chk-next"
class="input"
@change="setStore('next', $event.target.checked)"
v-model="next" />
<label for="pref-chk-next">{{ t('pref.auto_queue') }}</label>
</div>
<div class="left">
<label for="pref-codec">{{ t('pref.codec') }}</label>
<select
id="pref-codec"
name="pref-codec"
class="input"
:value="getStore('codec') || 'opus:mp4a'"
@change="setCodec($event.target.value)">
<option value="opus:mp4a">opus, mp4a</option>
<option value="mp4a:opus">mp4a, opus</option>
<option value="opus">opus</option>
<option value="mp4a">mp4a</option>
</select>
</div>
<div class="left">
<label for="pref-quality">{{ t('pref.quality') }}</label>
<select
id="pref-quality"
name="pref-quality"
class="input"
:value="getStore('quality') || 'auto'"
@change="setStore('quality', $event.target.value)">
<option value="auto">{{ t('pref.auto') }}</option>
<option value="best">{{ t('pref.best') }}</option>
<option value="worst">{{ t('pref.worst') }}</option>
</select>
</div>
<div class="left">
<label for="pref-volume">{{ t('pref.volume') }}</label>
<input
type="number"
name="pref-volume"
id="pref-volume"
class="input"
max="100"
min="0"
:value="getStore('vol') || 100"
@change="setStore('vol', $event.target.value)" />
</div>
<h2>{{ t('instances.hyp') }}</h2>
<select
v-if="hypInstances"
class="input"
:value="getStore('api') || HYPERPIPE_INSTANCE"
@change="setStore('api', $event.target.value)">
<option
v-for="i in hypInstances"
:key="i.name"
:value="i.api_url.replace('https://', '').replace('http://', '')">
{{ i.name.replace('Official', t('instances.default')) }}
</option>
<option v-if="!verifyApi">
{{ getStore('api') || HYPERPIPE_INSTANCE }}
</option>
</select>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>{{ t('instances.name') }}</th>
<th>{{ t('instances.loc') }}</th>
</tr>
</thead>
<tbody v-for="i in hypInstances" :key="i.name">
<tr>
<td>
{{ i.name.replace('Official', t('instances.default')) }}
</td>
<td>
{{ i.locations.replaceAll(',', '') }}
</td>
</tr>
</tbody>
</table>
</div>
<h2>
{{ t('instances.piped') }}
<a
href="https://github.com/TeamPiped/Piped"
target="_blank"
rel="noreferrer noopener"
class="bi bi-info-circle"></a>
</h2>
<select
v-if="instances"
class="input"
:value="getStore('pipedapi') || PIPED_INSTANCE"
@change="setStore('pipedapi', $event.target.value)">
<option
v-for="i in instances"
:key="i.name"
:value="i.api_url.replace('https://', '')">
{{ i.name.replace('Official', t('instances.default')) }}
</option>
<option v-if="!verifyPipedApi">
{{ getStore('pipedapi') || PIPED_INSTANCE }}
</option>
</select>
<h3>{{ t('instances.auth') }}</h3>
<select
v-if="instances"
class="input"
:value="getStore('authapi') || PIPED_INSTANCE"
@change="setAuth($event.target.value)">
<option
v-for="i in instances"
:key="i.name"
:value="i.api_url.replace('https://', '')">
{{ i.name.replace('Official', t('instances.default')) }}
</option>
<option v-if="!verifyAuthApi">
{{ getStore('authapi') || PIPED_INSTANCE }}
</option>
</select>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>{{ t('instances.name') }}</th>
<th>{{ t('instances.loc') }}</th>
<th>{{ t('instances.cdn') }}</th>
<th>{{ t('instances.up_to_date') }}</th>
<th>{{ t('instances.version') }}</th>
</tr>
</thead>
<tbody v-for="i in instances" :key="i.name">
<tr>
<td>
{{ i.name.replace('Official', t('instances.default')) }}
</td>
<td>
{{ i.locations.replaceAll(',', '') }}
</td>
<td :class="getBool(i.cdn)" :data-active="i.cdn"></td>
<td :class="getBool(i.up_to_date)" :data-active="i.up_to_date"></td>
<td>
{{ i.version }}
</td>
</tr>
</tbody>
</table>
</div>
<footer>
{{ date }}
<a
class="bi bi-code-slash"
target="_blank"
rel="noreferrer noopener"
href="https://codeberg.org/Hyperpipe/Hyperpipe" />
<a
class="bi bi-eye"
target="_blank"
rel="noreferrer noopener"
href="https://codeberg.org/Hyperpipe/Hyperpipe/wiki/Privacy" />
</footer>
</template>
<style scoped>
h2 {
margin-top: 1rem;
}
h2,
h3,
label,
footer {
text-align: center;
}
input[type='number'] {
width: 4.5rem;
}
input[type='checkbox'] {
appearance: none;
border: 0.25rem solid var(--color-background-mute);
margin: 0;
padding: 0;
width: 1.5rem;
height: 1.5rem;
}
input[type='checkbox']:checked {
background: var(--color-foreground);
}
label {
margin: 0 0.5rem;
display: inline-block;
line-height: 1rem;
font-size: 1rem;
}
label[for*='pref-chk'],
label[for^='pref-chk'] {
vertical-align: 0.35rem;
}
.left {
float: left;
margin: 0 auto;
padding: 1rem;
}
.left + .left {
padding: 0;
}
.table-wrap {
overflow-x: auto;
margin-bottom: 2rem;
}
.table-wrap,
table {
width: 100%;
}
th,
td {
margin: 0.25rem 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 0.125rem;
background-color: var(--color-background-mute);
}
th {
font-weight: bolder;
}
td {
text-align: center;
max-width: 40vw;
overflow-wrap: break-word;
}
td.bi {
color: indianred;
font-size: 1.25rem;
}
td.bi[data-active='true'] {
color: var(--color-foreground);
}
footer {
margin-bottom: 2rem;
}
footer .bi::before {
font-size: 1.75rem;
vertical-align: -0.45rem;
}
footer .bi {
padding: 0.5rem;
margin: 0 0.25rem;
border-radius: 0.25rem;
}
@media (max-width: 500px) {
select,
input {
max-width: 100% !important;
}
}
</style>