From 29c09cb10ab4a0120ec3d2df9e5064ba7070a699 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Tue, 14 Jan 2025 23:36:31 +0800 Subject: [PATCH] migrate unlock page to composition API and typescript --- src/lib/webauthn.ts | 4 +- src/locales/helper.js | 62 ++----- src/locales/helpers.ts | 71 ++++++-- src/locales/index.ts | 7 +- src/views/base/UnlockPageBase.ts | 91 ++++++++++ src/views/desktop/UnlockPage.vue | 275 ++++++++++++------------------- src/views/mobile/UnlockPage.vue | 273 ++++++++++++------------------ 7 files changed, 387 insertions(+), 396 deletions(-) create mode 100644 src/views/base/UnlockPageBase.ts diff --git a/src/lib/webauthn.ts b/src/lib/webauthn.ts index aa10b822..2f70a21f 100644 --- a/src/lib/webauthn.ts +++ b/src/lib/webauthn.ts @@ -35,7 +35,7 @@ interface WebAuthnRegisterResponse { } interface WebAuthnVerifyResponse { - readonly id: string | null; + readonly id: string; readonly userName: string; readonly userSecret: string; readonly clientData: ClientData; @@ -198,7 +198,7 @@ export function verifyWebAuthnCredential(userInfo: UserBasicInfo, credentialId: clientData && clientData.type === 'webauthn.get' && challengeFromClientData === challenge && userIdParts && userIdParts.length === 2 && userIdParts[0] === userInfo.username) { const ret: WebAuthnVerifyResponse = { - id: base64encode(rawCredential.rawId), + id: base64encode(rawCredential.rawId) as string, userName: userIdParts[0], userSecret: userIdParts[1], clientData: clientData, diff --git a/src/locales/helper.js b/src/locales/helper.js index ed9e57ce..743decc6 100644 --- a/src/locales/helper.js +++ b/src/locales/helper.js @@ -1,6 +1,6 @@ import moment from 'moment-timezone'; -import { DEFAULT_LANGUAGE, allLanguages } from '@/locales/index.ts'; +import { DEFAULT_LANGUAGE, ALL_LANGUAGES } from '@/locales/index.ts'; import { Month, WeekDay, MeridiemIndicator, LongDateFormat, ShortDateFormat, LongTimeFormat, ShortTimeFormat, DateRange, LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE } from '@/core/datetime.ts'; import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; @@ -70,12 +70,12 @@ function getLanguageDisplayName(translateFn, languageName) { function getAllLanguageInfoArray(translateFn, includeSystemDefault) { const ret = []; - for (const languageTag in allLanguages) { - if (!Object.prototype.hasOwnProperty.call(allLanguages, languageTag)) { + for (const languageTag in ALL_LANGUAGES) { + if (!Object.prototype.hasOwnProperty.call(ALL_LANGUAGES, languageTag)) { continue; } - const languageInfo = allLanguages[languageTag]; + const languageInfo = ALL_LANGUAGES[languageTag]; let displayName = languageInfo.displayName; let languageNameInCurrentLanguage = getLanguageDisplayName(translateFn, languageInfo.name); @@ -104,7 +104,7 @@ function getAllLanguageInfoArray(translateFn, includeSystemDefault) { } function getLanguageInfo(locale) { - return allLanguages[locale]; + return ALL_LANGUAGES[locale]; } function getDefaultLanguage() { @@ -118,7 +118,7 @@ function getDefaultLanguage() { return DEFAULT_LANGUAGE; } - if (!allLanguages[browserLocale]) { + if (!ALL_LANGUAGES[browserLocale]) { const locale = getLocaleFromLanguageAlias(browserLocale); if (locale) { @@ -126,11 +126,11 @@ function getDefaultLanguage() { } } - if (!allLanguages[browserLocale] && browserLocale.split('-').length > 1) { // maybe language-script-region + if (!ALL_LANGUAGES[browserLocale] && browserLocale.split('-').length > 1) { // maybe language-script-region const localeParts = browserLocale.split('-'); browserLocale = localeParts[0] + '-' + localeParts[1]; - if (!allLanguages[browserLocale]) { + if (!ALL_LANGUAGES[browserLocale]) { const locale = getLocaleFromLanguageAlias(browserLocale); if (locale) { @@ -138,7 +138,7 @@ function getDefaultLanguage() { } } - if (!allLanguages[browserLocale]) { + if (!ALL_LANGUAGES[browserLocale]) { browserLocale = localeParts[0]; const locale = getLocaleFromLanguageAlias(browserLocale); @@ -148,7 +148,7 @@ function getDefaultLanguage() { } } - if (!allLanguages[browserLocale]) { + if (!ALL_LANGUAGES[browserLocale]) { return DEFAULT_LANGUAGE; } @@ -156,8 +156,8 @@ function getDefaultLanguage() { } function getLocaleFromLanguageAlias(alias) { - for (let locale in allLanguages) { - if (!Object.prototype.hasOwnProperty.call(allLanguages, locale)) { + for (let locale in ALL_LANGUAGES) { + if (!Object.prototype.hasOwnProperty.call(ALL_LANGUAGES, locale)) { continue; } @@ -165,7 +165,7 @@ function getLocaleFromLanguageAlias(alias) { return locale; } - const lang = allLanguages[locale]; + const lang = ALL_LANGUAGES[locale]; const aliases = lang.aliases; if (!aliases || aliases.length < 1) { @@ -1122,11 +1122,11 @@ function getAllSupportedImportFileTypes(i18nGlobal, translateFn) { if (fileType.document.supportMultiLanguages === true) { document.language = getCurrentLanguageTag(i18nGlobal); document.anchor = translateFn(`document.anchor.export_and_import.${fileType.document.anchor}`); - } else if (isString(fileType.document.supportMultiLanguages) && allLanguages[fileType.document.supportMultiLanguages]) { + } else if (isString(fileType.document.supportMultiLanguages) && ALL_LANGUAGES[fileType.document.supportMultiLanguages]) { document.language = fileType.document.supportMultiLanguages; if (document.language !== getCurrentLanguageTag(i18nGlobal)) { - document.displayLanguageName = getLanguageDisplayName(translateFn, allLanguages[fileType.document.supportMultiLanguages].name); + document.displayLanguageName = getLanguageDisplayName(translateFn, ALL_LANGUAGES[fileType.document.supportMultiLanguages].name); } document.anchor = fileType.document.anchor; @@ -1373,34 +1373,6 @@ function setLanguage(i18nGlobal, locale, force) { }; } -function setTimeZone(timezone) { - if (timezone) { - moment.tz.setDefault(timezone); - } else { - moment.tz.setDefault(); - } -} - -function initLocale(i18nGlobal, lastUserLanguage, timezone) { - let localeDefaultSettings = null; - - if (lastUserLanguage && getLanguageInfo(lastUserLanguage)) { - logger.info(`Last user language is ${lastUserLanguage}`); - localeDefaultSettings = setLanguage(i18nGlobal, lastUserLanguage, true); - } else { - localeDefaultSettings = setLanguage(i18nGlobal, null, true); - } - - if (timezone) { - logger.info(`Current timezone is ${timezone}`); - setTimeZone(timezone); - } else { - logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`); - } - - return localeDefaultSettings; -} - export function translateError(message, translateFn) { let parameters = {}; @@ -1487,8 +1459,6 @@ export function i18nFunctions(i18nGlobal) { getCategorizedAccountsWithDisplayBalance: (allVisibleAccounts, showAccountBalance, defaultCurrency, settingsStore, userStore, exchangeRatesStore) => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts, showAccountBalance, defaultCurrency, userStore, settingsStore, exchangeRatesStore, i18nGlobal.t), getServerTipContent: (tipConfig) => getServerTipContent(tipConfig, i18nGlobal), joinMultiText: (textArray) => joinMultiText(textArray, i18nGlobal.t), - setLanguage: (locale, force) => setLanguage(i18nGlobal, locale, force), - setTimeZone: (timezone) => setTimeZone(timezone), - initLocale: (lastUserLanguage, timezone) => initLocale(i18nGlobal, lastUserLanguage, timezone) + setLanguage: (locale, force) => setLanguage(i18nGlobal, locale, force) }; } diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index 9f5c808a..1614d4a3 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -1,9 +1,9 @@ import { useI18n as useVueI18n } from 'vue-i18n'; import moment from 'moment-timezone'; -import type {TypeAndName, TypeAndDisplayName, LocalizedSwitchOption } from '@/core/base.ts'; +import type { TypeAndName, TypeAndDisplayName, LocalizedSwitchOption } from '@/core/base.ts'; -import { type LanguageInfo, allLanguages, DEFAULT_LANGUAGE } from '@/locales/index.ts'; +import { type LanguageInfo, type LanguageOption, ALL_LANGUAGES, DEFAULT_LANGUAGE } from '@/locales/index.ts'; import { type DateFormat, @@ -136,12 +136,12 @@ export function getI18nOptions(): object { messages: (function () { const messages: Record = {}; - for (const languageKey in allLanguages) { - if (!Object.prototype.hasOwnProperty.call(allLanguages, languageKey)) { + for (const languageKey in ALL_LANGUAGES) { + if (!Object.prototype.hasOwnProperty.call(ALL_LANGUAGES, languageKey)) { continue; } - const languageInfo = allLanguages[languageKey]; + const languageInfo = ALL_LANGUAGES[languageKey]; messages[languageKey] = languageInfo.content; } @@ -158,7 +158,11 @@ export function useI18n() { // private functions function getLanguageInfo(languageKey: string): LanguageInfo | undefined { - return allLanguages[languageKey]; + return ALL_LANGUAGES[languageKey]; + } + + function getLanguageDisplayName(languageName: string): string { + return t(`language.${languageName}`); } function getDefaultLanguage(): string { @@ -172,7 +176,7 @@ export function useI18n() { return DEFAULT_LANGUAGE; } - if (!allLanguages[browserLanguage]) { + if (!ALL_LANGUAGES[browserLanguage]) { const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage); if (languageKey) { @@ -180,11 +184,11 @@ export function useI18n() { } } - if (!allLanguages[browserLanguage] && browserLanguage.split('-').length > 1) { // maybe language-script-region + if (!ALL_LANGUAGES[browserLanguage] && browserLanguage.split('-').length > 1) { // maybe language-script-region const languageTagParts = browserLanguage.split('-'); browserLanguage = languageTagParts[0] + '-' + languageTagParts[1]; - if (!allLanguages[browserLanguage]) { + if (!ALL_LANGUAGES[browserLanguage]) { const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage); if (languageKey) { @@ -192,7 +196,7 @@ export function useI18n() { } } - if (!allLanguages[browserLanguage]) { + if (!ALL_LANGUAGES[browserLanguage]) { browserLanguage = languageTagParts[0]; const languageKey = getLanguageKeyFromLanguageAlias(browserLanguage); @@ -202,7 +206,7 @@ export function useI18n() { } } - if (!allLanguages[browserLanguage]) { + if (!ALL_LANGUAGES[browserLanguage]) { return DEFAULT_LANGUAGE; } @@ -210,8 +214,8 @@ export function useI18n() { } function getLanguageKeyFromLanguageAlias(alias: string): string | null { - for (const languageKey in allLanguages) { - if (!Object.prototype.hasOwnProperty.call(allLanguages, languageKey)) { + for (const languageKey in ALL_LANGUAGES) { + if (!Object.prototype.hasOwnProperty.call(ALL_LANGUAGES, languageKey)) { continue; } @@ -219,7 +223,7 @@ export function useI18n() { return languageKey; } - const langInfo = allLanguages[languageKey]; + const langInfo = ALL_LANGUAGES[languageKey]; const aliases = langInfo.aliases; if (!aliases || aliases.length < 1) { @@ -545,6 +549,42 @@ export function useI18n() { return t('default.firstDayOfWeek'); } + function getAllLanguageOptions(includeSystemDefault: boolean): LanguageOption[] { + const ret: LanguageOption[] = []; + + for (const languageTag in ALL_LANGUAGES) { + if (!Object.prototype.hasOwnProperty.call(ALL_LANGUAGES, languageTag)) { + continue; + } + + const languageInfo = ALL_LANGUAGES[languageTag]; + let displayName = languageInfo.displayName; + const languageNameInCurrentLanguage = getLanguageDisplayName(languageInfo.name); + + if (languageNameInCurrentLanguage && languageNameInCurrentLanguage !== displayName) { + displayName = `${languageNameInCurrentLanguage} (${displayName})`; + } + + ret.push({ + languageTag: languageTag, + displayName: displayName + }); + } + + ret.sort(function (lang1, lang2) { + return lang1.languageTag.localeCompare(lang2.languageTag); + }); + + if (includeSystemDefault) { + ret.splice(0, 0, { + languageTag: '', + displayName: t('System Default') + }); + } + + return ret; + } + function getAllEnableDisableOptions(): LocalizedSwitchOption[] { return [{ value: true, @@ -1167,7 +1207,7 @@ export function useI18n() { } } - function initLocale(lastUserLanguage: string, timezone: string): LocaleDefaultSettings | null { + function initLocale(lastUserLanguage?: string, timezone?: string): LocaleDefaultSettings | null { let localeDefaultSettings: LocaleDefaultSettings | null = null; if (lastUserLanguage && getLanguageInfo(lastUserLanguage)) { @@ -1201,6 +1241,7 @@ export function useI18n() { getDefaultCurrency, getDefaultFirstDayOfWeek, // get all localized info of specified type + getAllLanguageOptions, getAllEnableDisableOptions, getAllMeridiemIndicators, getAllLongMonthNames, diff --git a/src/locales/index.ts b/src/locales/index.ts index 8fa9d2c2..e3dc20dd 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -10,10 +10,15 @@ export interface LanguageInfo { readonly content: object; } +export interface LanguageOption { + readonly languageTag: string; + readonly displayName: string; +} + export const DEFAULT_LANGUAGE: string = 'en'; // To add new languages, please refer to https://ezbookkeeping.mayswind.net/translating -export const allLanguages: Record = { +export const ALL_LANGUAGES: Record = { 'en': { name: 'English', displayName: 'English', diff --git a/src/views/base/UnlockPageBase.ts b/src/views/base/UnlockPageBase.ts new file mode 100644 index 00000000..5791a51d --- /dev/null +++ b/src/views/base/UnlockPageBase.ts @@ -0,0 +1,91 @@ +import { ref, computed } from 'vue'; + +import { useI18n } from '@/locales/helpers.ts'; + +// @ts-expect-error the above file is migrating to ts +import { useRootStore } from '@/stores/index.js'; +import { useSettingsStore } from '@/stores/setting.ts'; +import { useUserStore } from '@/stores/user.ts'; +import { useTokensStore } from '@/stores/token.ts'; +// @ts-expect-error the above file is migrating to ts +import { useTransactionsStore } from '@/stores/transaction.js'; +import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; + +import { isWebAuthnSupported } from '@/lib/webauthn.ts'; +import { hasWebAuthnConfig } from '@/lib/userstate.ts'; +import { getVersion } from '@/lib/version.ts'; +import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts'; + +export function useUnlockPageBase() { + const { setLanguage, initLocale } = useI18n(); + + const rootStore = useRootStore(); + const settingsStore = useSettingsStore(); + const userStore = useUserStore(); + const tokensStore = useTokensStore(); + const transactionsStore = useTransactionsStore(); + const exchangeRatesStore = useExchangeRatesStore(); + + const version: string = `v${getVersion()}`; + + const pinCode = ref(''); + + const isWebAuthnAvailable = computed(() => { + return settingsStore.appSettings.applicationLockWebAuthn + && hasWebAuthnConfig() + && isWebAuthnSupported(); + }); + + function isPinCodeValid(pinCode: string): boolean { + return !!pinCode && pinCode.length === 6; + } + + function changeLanguage(locale: string): void { + const localeDefaultSettings = setLanguage(locale); + settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); + } + + function doAfterUnlocked(): void { + transactionsStore.initTransactionDraft(); + tokensStore.refreshTokenAndRevokeOldToken().then(response => { + if (response.user) { + const localeDefaultSettings = setLanguage(response.user.language); + settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); + + setExpenseAndIncomeAmountColor(response.user.expenseAmountColor, response.user.incomeAmountColor); + } + + if (response.notificationContent) { + rootStore.setNotificationContent(response.notificationContent); + } + }); + + if (settingsStore.appSettings.autoUpdateExchangeRatesData) { + exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false }); + } + } + + function doRelogin(): void { + rootStore.forceLogout(); + settingsStore.clearAppSettings(); + + const localeDefaultSettings = initLocale(userStore.currentUserLanguage, settingsStore.appSettings.timeZone); + settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings); + + setExpenseAndIncomeAmountColor(userStore.currentUserExpenseAmountColor, userStore.currentUserIncomeAmountColor); + } + + return { + // constants + version, + // states + pinCode, + // computed states + isWebAuthnAvailable, + // methods + isPinCodeValid, + changeLanguage, + doAfterUnlocked, + doRelogin + }; +} diff --git a/src/views/desktop/UnlockPage.vue b/src/views/desktop/UnlockPage.vue index 60899e1f..a8705b89 100644 --- a/src/views/desktop/UnlockPage.vue +++ b/src/views/desktop/UnlockPage.vue @@ -2,8 +2,8 @@
@@ -22,9 +22,9 @@
-

{{ $t('Unlock Application') }}

-

{{ $t('Please enter your PIN code or use WebAuthn to unlock application') }}

-

{{ $t('Please enter your PIN code to unlock application') }}

+

{{ tt('Unlock Application') }}

+

{{ tt('Please enter your PIN code or use WebAuthn to unlock application') }}

+

{{ tt('Please enter your PIN code to unlock application') }}

@@ -39,22 +39,22 @@ - {{ $t('Unlock with PIN Code') }} + {{ tt('Unlock with PIN Code') }} - {{ $t('Unlock with WebAuthn') }} + {{ tt('Unlock with WebAuthn') }} - {{ $t('Can\'t Unlock?') }} + {{ tt('Can\'t Unlock?') }} - {{ $t('Re-login') }} + {{ tt('Re-login') }} @@ -106,16 +106,21 @@
- diff --git a/src/views/mobile/UnlockPage.vue b/src/views/mobile/UnlockPage.vue index 6c893b4f..02a1a6ac 100644 --- a/src/views/mobile/UnlockPage.vue +++ b/src/views/mobile/UnlockPage.vue @@ -1,14 +1,14 @@ -