diff options
author | Christopher Ng <chrng8@gmail.com> | 2022-08-11 05:54:08 +0000 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2022-09-16 19:02:18 +0000 |
commit | f922b2fd7072ace8211467e3627f2a44a5c09fe3 (patch) | |
tree | d218fb323c4445659576a70a5cfc8ea7af149a79 /apps/settings/src | |
parent | b9c002df0a3ab80c1612206cfb1f724d90e0da3a (diff) | |
download | nextcloud-server-f922b2fd7072ace8211467e3627f2a44a5c09fe3.tar.gz nextcloud-server-f922b2fd7072ace8211467e3627f2a44a5c09fe3.zip |
Remake locale saving with Vue
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Diffstat (limited to 'apps/settings/src')
5 files changed, 313 insertions, 0 deletions
diff --git a/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue b/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue new file mode 100644 index 00000000000..2e3ad64e769 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue @@ -0,0 +1,208 @@ +<!-- + - @copyright 2022 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - +--> + +<template> + <div class="locale"> + <select :id="inputId" + :placeholder="t('settings', 'Locale')" + @change="onLocaleChange"> + <option v-for="currentLocale in localesForLanguage" + :key="currentLocale.code" + :selected="locale.code === currentLocale.code" + :value="currentLocale.code"> + {{ currentLocale.name }} + </option> + <option disabled> + ────────── + </option> + <option v-for="currentLocale in otherLocales" + :key="currentLocale.code" + :selected="locale.code === currentLocale.code" + :value="currentLocale.code"> + {{ currentLocale.name }} + </option> + </select> + + <div class="example"> + <Web :size="20" /> + <div class="example__text"> + <p> + <span>{{ example.date }}</span> + <span>{{ example.time }}</span> + </p> + <p> + {{ t('settings', 'Week starts on {firstDayOfWeek}', { firstDayOfWeek: this.example.firstDayOfWeek }) }} + </p> + </div> + </div> + </div> +</template> + +<script> +import { showError } from '@nextcloud/dialogs' +import moment from '@nextcloud/moment' +import Web from 'vue-material-design-icons/Web' + +import { ACCOUNT_SETTING_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js' +import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js' +import { validateLocale } from '../../../utils/validate.js' +import logger from '../../../logger.js' + +export default { + name: 'Locale', + + components: { + Web, + }, + + props: { + inputId: { + type: String, + default: null, + }, + locale: { + type: Object, + required: true, + }, + localesForLanguage: { + type: Array, + required: true, + }, + otherLocales: { + type: Array, + required: true, + }, + }, + + data() { + return { + initialLocale: this.locale, + example: { + date: moment().format('L'), + time: moment().format('LTS'), + firstDayOfWeek: window.dayNames[window.firstDay], + }, + } + }, + + computed: { + allLocales() { + return Object.freeze( + [...this.localesForLanguage, ...this.otherLocales] + .reduce((acc, { code, name }) => ({ ...acc, [code]: name }), {}) + ) + }, + }, + + created() { + setInterval(this.refreshExample, 1000) + }, + + methods: { + async onLocaleChange(e) { + const locale = this.constructLocale(e.target.value) + this.$emit('update:locale', locale) + + if (validateLocale(locale)) { + await this.updateLocale(locale) + } + }, + + async updateLocale(locale) { + try { + const responseData = await savePrimaryAccountProperty(ACCOUNT_SETTING_PROPERTY_ENUM.LOCALE, locale.code) + this.handleResponse({ + locale, + status: responseData.ocs?.meta?.status, + }) + this.reloadPage() + } catch (e) { + this.handleResponse({ + errorMessage: t('settings', 'Unable to update locale'), + error: e, + }) + } + }, + + constructLocale(localeCode) { + return { + code: localeCode, + name: this.allLocales[localeCode], + } + }, + + handleResponse({ locale, status, errorMessage, error }) { + if (status === 'ok') { + this.initialLocale = locale + } else { + this.$emit('update:locale', this.initialLocale) + showError(errorMessage) + logger.error(errorMessage, error) + } + }, + + refreshExample() { + this.example = { + date: moment().format('L'), + time: moment().format('LTS'), + firstDayOfWeek: window.dayNames[window.firstDay], + } + }, + + reloadPage() { + location.reload() + }, + }, +} +</script> + +<style lang="scss" scoped> +.locale { + display: grid; + + select { + width: 100%; + height: 34px; + margin: 3px 3px 3px 0; + padding: 6px 16px; + color: var(--color-main-text); + border: 1px solid var(--color-border-dark); + border-radius: var(--border-radius); + background: var(--icon-triangle-s-dark) no-repeat right 4px center; + font-family: var(--font-face); + appearance: none; + cursor: pointer; + } +} + +.example { + margin: 10px 0; + display: flex; + gap: 0 10px; + color: var(--color-text-lighter); + + &::v-deep .material-design-icon { + align-self: flex-start; + margin-top: 2px; + } +} +</style> diff --git a/apps/settings/src/components/PersonalInfo/LocaleSection/LocaleSection.vue b/apps/settings/src/components/PersonalInfo/LocaleSection/LocaleSection.vue new file mode 100644 index 00000000000..61c98f3a27a --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/LocaleSection/LocaleSection.vue @@ -0,0 +1,88 @@ +<!-- + - @copyright 2022 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - +--> + +<template> + <section> + <HeaderBar :input-id="inputId" + :readable="propertyReadable" /> + + <template v-if="isEditable"> + <Locale :input-id="inputId" + :locales-for-language="localesForLanguage" + :other-locales="otherLocales" + :locale.sync="locale" /> + </template> + + <span v-else> + {{ t('settings', 'No locale set') }} + </span> + </section> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' + +import Locale from './Locale.vue' +import HeaderBar from '../shared/HeaderBar.vue' + +import { ACCOUNT_SETTING_PROPERTY_ENUM, ACCOUNT_SETTING_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' + +const { localeMap: { activeLocale, localesForLanguage, otherLocales } } = loadState('settings', 'personalInfoParameters', {}) + +export default { + name: 'LocaleSection', + + components: { + Locale, + HeaderBar, + }, + + data() { + return { + propertyReadable: ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.LOCALE, + localesForLanguage, + otherLocales, + locale: activeLocale, + } + }, + + computed: { + inputId() { + return `account-setting-${ACCOUNT_SETTING_PROPERTY_ENUM.LOCALE}` + }, + + isEditable() { + return Boolean(this.locale) + }, + }, +} +</script> + +<style lang="scss" scoped> +section { + padding: 10px 10px; + + &::v-deep button:disabled { + cursor: default; + } +} +</style> diff --git a/apps/settings/src/constants/AccountPropertyConstants.js b/apps/settings/src/constants/AccountPropertyConstants.js index 41dd7e023bd..17166d1305f 100644 --- a/apps/settings/src/constants/AccountPropertyConstants.js +++ b/apps/settings/src/constants/AccountPropertyConstants.js @@ -106,11 +106,13 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({ */ export const ACCOUNT_SETTING_PROPERTY_ENUM = Object.freeze({ LANGUAGE: 'language', + LOCALE: 'locale', }) /** Enum of account setting properties to human readable setting properties */ export const ACCOUNT_SETTING_PROPERTY_READABLE_ENUM = Object.freeze({ LANGUAGE: t('settings', 'Language'), + LOCALE: t('settings', 'Locale'), }) /** Enum of scopes */ diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js index 077bad3b5f7..8342015c560 100644 --- a/apps/settings/src/main-personal-info.js +++ b/apps/settings/src/main-personal-info.js @@ -35,6 +35,7 @@ import LocationSection from './components/PersonalInfo/LocationSection.vue' import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue' import TwitterSection from './components/PersonalInfo/TwitterSection.vue' import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection.vue' +import LocaleSection from './components/PersonalInfo/LocaleSection/LocaleSection.vue' import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection.vue' import OrganisationSection from './components/PersonalInfo/OrganisationSection.vue' import RoleSection from './components/PersonalInfo/RoleSection.vue' @@ -61,6 +62,7 @@ const LocationView = Vue.extend(LocationSection) const WebsiteView = Vue.extend(WebsiteSection) const TwitterView = Vue.extend(TwitterSection) const LanguageView = Vue.extend(LanguageSection) +const LocaleView = Vue.extend(LocaleSection) new AvatarView().$mount('#vue-avatar-section') new DetailsView().$mount('#vue-details-section') @@ -71,6 +73,7 @@ new LocationView().$mount('#vue-location-section') new WebsiteView().$mount('#vue-website-section') new TwitterView().$mount('#vue-twitter-section') new LanguageView().$mount('#vue-language-section') +new LocaleView().$mount('#vue-locale-section') if (profileEnabledGlobally) { const ProfileView = Vue.extend(ProfileSection) diff --git a/apps/settings/src/utils/validate.js b/apps/settings/src/utils/validate.js index 7d0cf1d9e33..4ea95593fbc 100644 --- a/apps/settings/src/utils/validate.js +++ b/apps/settings/src/utils/validate.js @@ -75,6 +75,18 @@ export function validateLanguage(input) { } /** + * Validate the locale input + * + * @param {object} input the input + * @return {boolean} + */ +export function validateLocale(input) { + return input.code !== '' + && input.name !== '' + && input.name !== undefined +} + +/** * Validate boolean input * * @param {boolean} input the input |