Files
daydream/src/components/SettingsPanel.vue
T
2026-09-04 01:21:19 +02:00

198 lines
5.5 KiB
Vue

<script>
import appIcon from '@components/AppIcon';
import { applyHand, loadHand } from '@scripts/hands';
/**
* 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 !== '');
}
},
hands() {
return this.consts.hands || [];
},
languageNames() {
const oNames = new Intl.DisplayNames([this.lang.locale], {type: 'language'});
return this.languages.map((sCode) => ({
code: sCode,
label: oNames.of(sCode) || sCode
}));
}
},
methods: {
/**
* Show the hand on the page as soon as it is picked, before the save
* comes back - choosing a hand is a visual decision, and waiting on a
* round trip to see it makes the picker feel broken.
*/
async pickHand(sHandId) {
if(sHandId === this.user.hand) return;
await loadHand(this.hands, sHandId);
applyHand(this.hands, sHandId);
await this.set('hand', sHandId);
//The save is what settles it; if it failed, go back to the one the
//account actually has rather than leaving a lie on screen.
if(this.sError !== '') applyHand(this.hands, this.user.hand);
},
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">
<span class="paper-label">{{ lang.get('account.hand') }}</span>
<div class="hand-picker" role="radiogroup" :aria-label="lang.get('account.hand')">
<button
v-for="oHand in hands"
:key="oHand.id"
type="button"
class="hand-picker__option"
:class="(oHand.id === user.hand) ? 'hand-picker__option--on' : null"
role="radio"
:aria-checked="oHand.id === user.hand"
:disabled="bBusy"
:style="{fontFamily: oHand.family + ', cursive'}"
@click="pickHand(oHand.id)"
>
<!-- Written in itself: the only thing worth knowing about
a hand is what your words look like in it. -->
<span class="hand-picker__sample">{{ lang.get('account.hand_sample') }}</span>
<span class="hand-picker__name">{{ oHand.name }}</span>
</button>
</div>
</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>