From 376f5b2650903d9ae5ced98f3c8a3246e99cc00f Mon Sep 17 00:00:00 2001 From: MaysWind Date: Tue, 4 Feb 2025 01:29:11 +0800 Subject: [PATCH] migrate importing transaction dialog to composition API and typescript --- src/consts/file.ts | 23 +- src/core/base.ts | 5 + src/core/file.ts | 41 + src/lib/file.ts | 16 + src/locales/helper.js | 78 - src/locales/helpers.ts | 87 + .../list/dialogs/BatchReplaceDialog.vue | 12 +- .../list/dialogs/ImportDialog.vue | 2642 +++++++++-------- 8 files changed, 1540 insertions(+), 1364 deletions(-) create mode 100644 src/core/file.ts diff --git a/src/consts/file.ts b/src/consts/file.ts index 287bf5a9..86598cd6 100644 --- a/src/consts/file.ts +++ b/src/consts/file.ts @@ -1,21 +1,12 @@ +import type { ImportFileType } from '@/core/file.ts'; + export const SUPPORTED_IMAGE_EXTENSIONS: string = '.jpg,.jpeg,.png,.gif,.webp'; -export interface ImportFileType { - readonly type: string; - readonly name: string; - readonly extensions: string; - readonly subTypes?: ImportFileTypeSubType[]; - readonly document?: { - readonly supportMultiLanguages: boolean | string; - readonly anchor: string; - }; -} - -export interface ImportFileTypeSubType { - readonly type: string; - readonly name: string; - readonly extensions?: string; -} +export const DEFAULT_DOCUMENT_LANGUAGE_FOR_IMPORT_FILE: string = 'en'; +export const SUPPORTED_DOCUMENT_LANGUAGES_FOR_IMPORT_FILE: Record = { + DEFAULT_DOCUMENT_LANGUAGE_FOR_IMPORT_FILE: true, + 'zh-Hans': true +}; export const SUPPORTED_IMPORT_FILE_TYPES: ImportFileType[] = [ { diff --git a/src/core/base.ts b/src/core/base.ts index df67340f..dc1acfb1 100644 --- a/src/core/base.ts +++ b/src/core/base.ts @@ -3,6 +3,11 @@ export type PartialRecord = { [P in K]?: T; } +export interface NameValue { + name: string; + value: string; +} + export interface TypeAndName { readonly type: number; readonly name: string; diff --git a/src/core/file.ts b/src/core/file.ts new file mode 100644 index 00000000..2ee13b8a --- /dev/null +++ b/src/core/file.ts @@ -0,0 +1,41 @@ +export interface ImportFileTypeAndExtensions { + readonly type: string; + readonly extensions?: string; +} + +export interface ImportFileType extends ImportFileTypeAndExtensions { + readonly type: string; + readonly name: string; + readonly extensions: string; + readonly subTypes?: ImportFileTypeSubType[]; + readonly document?: { + readonly supportMultiLanguages: boolean | string; + readonly anchor: string; + }; +} + +export interface ImportFileTypeSubType extends ImportFileTypeAndExtensions { + readonly type: string; + readonly name: string; + readonly extensions?: string; +} + +export interface LocalizedImportFileType extends ImportFileTypeAndExtensions { + readonly type: string; + readonly displayName: string; + readonly extensions: string; + readonly subTypes?: LocalizedImportFileTypeSubType[]; + readonly document?: LocalizedImportFileDocument; +} + +export interface LocalizedImportFileTypeSubType extends ImportFileTypeAndExtensions { + readonly type: string; + readonly displayName: string; + readonly extensions?: string; +} + +export interface LocalizedImportFileDocument { + readonly language: string; + readonly displayLanguageName: string; + readonly anchor: string; +} diff --git a/src/lib/file.ts b/src/lib/file.ts index 39ea4bb3..f7b149a1 100644 --- a/src/lib/file.ts +++ b/src/lib/file.ts @@ -1,3 +1,5 @@ +import type { ImportFileTypeAndExtensions } from '@/core/file.ts'; + import { isString } from './common.ts'; export function getFileExtension(filename: string): string { @@ -9,6 +11,20 @@ export function getFileExtension(filename: string): string { return parts[parts.length - 1]; } +export function findExtensionByType(items: ImportFileTypeAndExtensions[] | undefined, type: string): string | undefined { + if (!items || items.length < 1) { + return undefined; + } + + for (const item of items) { + if (item.type === type) { + return item.extensions; + } + } + + return undefined; +} + export function isFileExtensionSupported(filename: string, supportedExtensions: string): boolean { if (!supportedExtensions) { return false; diff --git a/src/locales/helper.js b/src/locales/helper.js index 758be445..f19b643f 100644 --- a/src/locales/helper.js +++ b/src/locales/helper.js @@ -1,5 +1,3 @@ -import { DEFAULT_LANGUAGE, ALL_LANGUAGES } from '@/locales/index.ts'; - import { WeekDay, LongDateFormat, ShortDateFormat, LongTimeFormat, ShortTimeFormat, DateRange } from '@/core/datetime.ts'; import { DecimalSeparator, DigitGroupingSymbol, DigitGroupingType } from '@/core/numeral.ts'; import { CurrencyDisplayType } from '@/core/currency.ts' @@ -8,7 +6,6 @@ import { TransactionTagFilterType } from '@/core/transaction.ts'; import { UTC_TIMEZONE, ALL_TIMEZONES } from '@/consts/timezone.ts'; import { ALL_CURRENCIES } from '@/consts/currency.ts'; -import { SUPPORTED_IMPORT_FILE_TYPES } from '@/consts/file.ts'; import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts'; import { @@ -48,14 +45,6 @@ import { getAllFilteredAccountsBalance } from '@/lib/account.ts'; -function getLanguageDisplayName(translateFn, languageName) { - return translateFn(`language.${languageName}`); -} - -function getCurrentLanguageTag(i18nGlobal) { - return i18nGlobal.locale; -} - function getLocalizedDisplayNameAndType(typeAndNames, translateFn) { const ret = []; @@ -550,72 +539,6 @@ function getAllTransactionTagFilterTypes(translateFn) { return getLocalizedDisplayNameAndType(TransactionTagFilterType.values(), translateFn); } -function getAllSupportedImportFileTypes(i18nGlobal, translateFn) { - const allSupportedImportFileTypes = []; - - for (let i = 0; i < SUPPORTED_IMPORT_FILE_TYPES.length; i++) { - const fileType = SUPPORTED_IMPORT_FILE_TYPES[i]; - let document = { - language: '', - displayLanguageName: '', - anchor: '' - }; - - if (fileType.document) { - 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) && ALL_LANGUAGES[fileType.document.supportMultiLanguages]) { - document.language = fileType.document.supportMultiLanguages; - - if (document.language !== getCurrentLanguageTag(i18nGlobal)) { - document.displayLanguageName = getLanguageDisplayName(translateFn, ALL_LANGUAGES[fileType.document.supportMultiLanguages].name); - } - - document.anchor = fileType.document.anchor; - } - - if (document.language) { - document.language = document.language.replace(/-/g, '_'); - } - - if (document.anchor) { - document.anchor = document.anchor.toLowerCase().replace(/ /g, '-'); - } - - if (document.language === DEFAULT_LANGUAGE) { - document.language = ''; - } - } else { - document = null; - } - - const subTypes = []; - - if (fileType.subTypes) { - for (let i = 0; i < fileType.subTypes.length; i++) { - const subType = fileType.subTypes[i]; - - subTypes.push({ - type: subType.type, - displayName: translateFn(subType.name), - extensions: subType.extensions - }); - } - } - - allSupportedImportFileTypes.push({ - type: fileType.type, - displayName: translateFn(fileType.name), - extensions: fileType.extensions, - subTypes: subTypes.length ? subTypes : undefined, - document: document - }); - } - - return allSupportedImportFileTypes; -} - function getCategorizedAccountsWithDisplayBalance(allVisibleAccounts, showAccountBalance, defaultCurrency, userStore, settingsStore, exchangeRatesStore, translateFn) { const ret = []; const allCategories = AccountCategory.values(); @@ -785,7 +708,6 @@ export function i18nFunctions(i18nGlobal) { formatAmountWithCurrency: (settingsStore, userStore, value, currencyCode) => getFormattedAmountWithCurrency(value, currencyCode, i18nGlobal.t, userStore, settingsStore), getAdaptiveAmountRate: (userStore, amount1, amount2, fromExchangeRate, toExchangeRate) => getAdaptiveAmountRate(amount1, amount2, fromExchangeRate, toExchangeRate, i18nGlobal.t, userStore), getAllTransactionTagFilterTypes: () => getAllTransactionTagFilterTypes(i18nGlobal.t), - getAllSupportedImportFileTypes: () => getAllSupportedImportFileTypes(i18nGlobal, i18nGlobal.t), getCategorizedAccountsWithDisplayBalance: (allVisibleAccounts, showAccountBalance, defaultCurrency, settingsStore, userStore, exchangeRatesStore) => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts, showAccountBalance, defaultCurrency, userStore, settingsStore, exchangeRatesStore, i18nGlobal.t) }; } diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index 6dfd297a..59f00f6d 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -82,6 +82,12 @@ import { ChartDateAggregationType } from '@/core/statistics.ts'; +import { + type LocalizedImportFileType, + type LocalizedImportFileTypeSubType, + type LocalizedImportFileDocument, +} from '@/core/file.ts'; + import type { LocaleDefaultSettings } from '@/core/setting.ts'; import type { ErrorResponse } from '@/core/api.ts'; @@ -89,6 +95,7 @@ import { UTC_TIMEZONE, ALL_TIMEZONES } from '@/consts/timezone.ts'; import { ALL_CURRENCIES } from '@/consts/currency.ts'; import { DEFAULT_EXPENSE_CATEGORIES, DEFAULT_INCOME_CATEGORIES, DEFAULT_TRANSFER_CATEGORIES } from '@/consts/category.ts'; import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts'; +import { DEFAULT_DOCUMENT_LANGUAGE_FOR_IMPORT_FILE, SUPPORTED_DOCUMENT_LANGUAGES_FOR_IMPORT_FILE, SUPPORTED_IMPORT_FILE_TYPES } from '@/consts/file.ts'; import { type CategorizedAccount, @@ -1066,6 +1073,85 @@ export function useI18n() { return availableExchangeRates; } + function getAllSupportedImportFileTypes(): LocalizedImportFileType[] { + const allSupportedImportFileTypes: LocalizedImportFileType[] = []; + + for (let i = 0; i < SUPPORTED_IMPORT_FILE_TYPES.length; i++) { + const fileType = SUPPORTED_IMPORT_FILE_TYPES[i]; + let document: LocalizedImportFileDocument | undefined; + + if (fileType.document) { + let documentLanguage = ''; + let documentDisplayLanguageName = ''; + let documentAnchor = ''; + + if (fileType.document.supportMultiLanguages === true) { + documentLanguage = getCurrentLanguageTag(); + + if (SUPPORTED_DOCUMENT_LANGUAGES_FOR_IMPORT_FILE[documentLanguage]) { + documentAnchor = t(`document.anchor.export_and_import.${fileType.document.anchor}`); + } else { + documentLanguage = DEFAULT_DOCUMENT_LANGUAGE_FOR_IMPORT_FILE; + documentAnchor = t(`document.anchor.export_and_import.${fileType.document.anchor}`, {}, { locale: documentLanguage }); + } + } else if (isString(fileType.document.supportMultiLanguages) && ALL_LANGUAGES[fileType.document.supportMultiLanguages]) { + documentLanguage = fileType.document.supportMultiLanguages; + documentAnchor = fileType.document.anchor; + } + + if (documentLanguage && documentLanguage !== getCurrentLanguageTag()) { + documentDisplayLanguageName = getLanguageDisplayName(ALL_LANGUAGES[documentLanguage].name); + } + + if (documentLanguage) { + documentLanguage = documentLanguage.replace(/-/g, '_'); + } + + if (documentAnchor) { + documentAnchor = documentAnchor.toLowerCase().replace(/ /g, '-'); + } + + if (documentLanguage === DEFAULT_LANGUAGE) { + documentLanguage = ''; + } + + document = { + language: documentLanguage, + displayLanguageName: documentDisplayLanguageName, + anchor: documentAnchor + }; + } else { + document = undefined; + } + + const subTypes: LocalizedImportFileTypeSubType[] = []; + + if (fileType.subTypes) { + for (let i = 0; i < fileType.subTypes.length; i++) { + const subType = fileType.subTypes[i]; + const localizedSubType: LocalizedImportFileTypeSubType = { + type: subType.type, + displayName: t(subType.name), + extensions: subType.extensions + }; + + subTypes.push(localizedSubType); + } + } + + const localizedFileType: LocalizedImportFileType = { + type: fileType.type, + displayName: t(fileType.name), + extensions: fileType.extensions, + subTypes: subTypes.length ? subTypes : undefined, + document: document + }; + allSupportedImportFileTypes.push(localizedFileType); + } + + return allSupportedImportFileTypes; + } + function getLanguageInfo(languageKey: string): LanguageInfo | undefined { return ALL_LANGUAGES[languageKey]; } @@ -1593,6 +1679,7 @@ export function useI18n() { getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()), getAllTransactionDefaultCategories, getAllDisplayExchangeRates, + getAllSupportedImportFileTypes, // get localized info getLanguageInfo, getMonthShortName, diff --git a/src/views/desktop/transactions/list/dialogs/BatchReplaceDialog.vue b/src/views/desktop/transactions/list/dialogs/BatchReplaceDialog.vue index 525b67ca..55a8d4c2 100644 --- a/src/views/desktop/transactions/list/dialogs/BatchReplaceDialog.vue +++ b/src/views/desktop/transactions/list/dialogs/BatchReplaceDialog.vue @@ -180,8 +180,9 @@ import { useAccountsStore } from '@/stores/account.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import { useTransactionTagsStore } from '@/stores/transactionTag.ts'; +import type { NameValue } from '@/core/base.ts'; import { CategoryType } from '@/core/category.ts'; -import { type CategorizedAccountWithDisplayBalance, Account } from '@/models/account.ts'; +import { Account, type CategorizedAccountWithDisplayBalance } from '@/models/account.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionTag } from '@/models/transaction_tag.ts'; @@ -195,11 +196,6 @@ import { mdiPound } from '@mdi/js'; -interface BatchReplaceDialogInvalidItem { - name: string; - value: string; -} - interface BatchReplaceDialogResponse { sourceItem?: string; targetItem?: string; @@ -223,7 +219,7 @@ const icons = { const showState = ref(false); const mode = ref(''); const type = ref(''); -const invalidItems = ref([]); +const invalidItems = ref([]); const sourceItem = ref(undefined); const targetItem = ref(undefined); @@ -272,7 +268,7 @@ function getAccountDisplayName(accountId?: string): string { } } -function open(options: { mode: string; type: string; invalidItems?: BatchReplaceDialogInvalidItem[] }): Promise { +function open(options: { mode: string; type: string; invalidItems?: NameValue[] }): Promise { mode.value = options.mode; type.value = options.type; sourceItem.value = undefined; diff --git a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue index ba4110c3..ac787ddc 100644 --- a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue +++ b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue @@ -4,7 +4,7 @@