Files
daydream/src/components/SettingsPanel.vue
T
2026-09-03 18:28:33 +02:00

154 lines
3.9 KiB
Vue

<script>
import appIcon from '@components/AppIcon';
/**
* Account settings, saved one field at a time.
*
* There is no Save button on purpose: each field commits on change, the same
* way the book itself autosaves. A language change is the one setting that
* needs the page back to pick up the new dictionary.
*/
export default {
components: {
appIcon
},
emits: ['close', 'signed-out'],
inject: ['api', 'consts', 'lang', 'user'],
data() {
return {
sName: this.user.name,
sError: '',
sSaved: '',
bBusy: false
};
},
computed: {
languages() {
return this.consts.languages || ['en'];
},
//Intl knows the zone list; older engines only know the one in use.
timezones() {
try {
return Intl.supportedValuesOf('timeZone');
}
catch{
return [this.user.timezone].filter((sZone) => sZone !== '');
}
},
languageNames() {
const oNames = new Intl.DisplayNames([this.lang.locale], {type: 'language'});
return this.languages.map((sCode) => ({
code: sCode,
label: oNames.of(sCode) || sCode
}));
}
},
methods: {
async set(sField, sValue) {
this.bBusy = true;
this.sError = '';
this.sSaved = '';
try {
const asData = await this.api.post('account', {field: sField, value: sValue});
Object.assign(this.user, asData.user);
this.sSaved = sField;
//The dictionary is baked into the page, so a new language means
//a new page - and nothing is lost, the book is already saved.
if(sField === 'language') window.location.reload();
}
catch(oError) {
this.sError = oError.desc_lang_text || oError.message || this.lang.get('error.unexpected');
this.sName = this.user.name;
}
finally {
this.bBusy = false;
}
},
saveName() {
const sName = this.sName.trim();
if((sName !== '') && (sName !== this.user.name)) this.set('name', sName);
},
async signOut() {
this.bBusy = true;
try {
await this.api.post('logout');
this.$emit('signed-out');
}
catch(oError) {
this.sError = oError.desc_lang_text || oError.message;
}
finally {
this.bBusy = false;
}
}
}
};
</script>
<template>
<div class="veil" @click.self="$emit('close')">
<div class="leaf leaf--wide" role="dialog" aria-modal="true">
<div class="leaf__head">
<h2 class="leaf__title">{{ lang.get('account.settings') }}</h2>
<p class="leaf__sub">{{ user.email }}</p>
</div>
<p v-if="sError" class="leaf__error" role="alert">{{ sError }}</p>
<div class="leaf__row">
<label class="paper-label" for="set-name">{{ lang.get('account.name') }}</label>
<input
id="set-name"
v-model="sName"
class="paper-field"
type="text"
maxlength="100"
:disabled="bBusy"
@change="saveName"
@keyup.enter="saveName"
/>
</div>
<div class="leaf__row">
<label class="paper-label" for="set-lang">{{ lang.get('account.language') }}</label>
<select
id="set-lang"
class="paper-field"
:value="user.language"
:disabled="bBusy"
@change="set('language', $event.target.value)"
>
<option v-for="oLanguage in languageNames" :key="oLanguage.code" :value="oLanguage.code">
{{ oLanguage.label }}
</option>
</select>
</div>
<div class="leaf__row">
<label class="paper-label" for="set-tz">{{ lang.get('account.timezone') }}</label>
<select
id="set-tz"
class="paper-field"
:value="user.timezone"
:disabled="bBusy"
@change="set('timezone', $event.target.value)"
>
<option v-for="sZone in timezones" :key="sZone" :value="sZone">{{ sZone }}</option>
</select>
</div>
<div class="leaf__actions">
<button type="button" class="ink-button" @click="$emit('close')">
<appIcon icon="check" />
<span>{{ lang.get('action.close') }}</span>
</button>
<button type="button" class="text-button text-button--bad" :disabled="bBusy" @click="signOut">
<span>{{ lang.get('account.sign_out') }}</span>
</button>
</div>
</div>
</div>
</template>