sync application settings

This commit is contained in:
MaysWind
2025-06-29 20:25:21 +08:00
parent 1eb997d2c0
commit 90e862fbb1
42 changed files with 1773 additions and 81 deletions
+8
View File
@@ -141,5 +141,13 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] user custom exchange rate table maintained successfully")
err = datastore.Container.UserDataStore.SyncStructs(new(models.UserApplicationCloudSetting))
if err != nil {
return err
}
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] user application cloud settings table maintained successfully")
return nil
}
+5
View File
@@ -276,6 +276,11 @@ func startWebServer(c *core.CliContext) error {
apiV1Route.POST("/users/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByLoginedUserHandler))
}
// Application Cloud Settings
apiV1Route.GET("/users/settings/cloud/get.json", bindApi(api.UserApplicationCloudSettings.ApplicationSettingsGetHandler))
apiV1Route.POST("/users/settings/cloud/update.json", bindApi(api.UserApplicationCloudSettings.ApplicationSettingsUpdateHandler))
apiV1Route.POST("/users/settings/cloud/disable.json", bindApi(api.UserApplicationCloudSettings.ApplicationSettingsDisableHandler))
// Two-Factor Authorization
if config.EnableTwoFactor {
apiV1Route.GET("/users/2fa/status.json", bindApi(api.TwoFactorAuthorizations.TwoFactorStatusHandler))
+34 -4
View File
@@ -19,6 +19,7 @@ type AuthorizationsApi struct {
ApiUsingDuplicateChecker
ApiWithUserInfo
users *services.UserService
userAppCloudSettings *services.UserApplicationCloudSettingsService
tokens *services.TokenService
twoFactorAuthorizations *services.TwoFactorAuthorizationService
}
@@ -44,6 +45,7 @@ var (
},
},
users: services.Users,
userAppCloudSettings: services.UserApplicationCloudSettings,
tokens: services.Tokens,
twoFactorAuthorizations: services.TwoFactorAuthorizations,
}
@@ -140,9 +142,18 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.WebContext) (any, *errs.Err
c.SetTokenClaims(claims)
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[authorizations.AuthorizeHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logined, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt)
authResp := a.getAuthResponse(c, token, twoFactorEnable, user)
authResp := a.getAuthResponse(c, token, twoFactorEnable, user, applicationCloudSettingSlice)
return authResp, nil
}
@@ -218,9 +229,18 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any,
c.SetTextualToken(token)
c.SetTokenClaims(claims)
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" has authorized two-factor via passcode, token will be expired at %d", user.Uid, claims.ExpiresAt)
authResp := a.getAuthResponse(c, token, false, user)
authResp := a.getAuthResponse(c, token, false, user, applicationCloudSettingSlice)
return authResp, nil
}
@@ -303,17 +323,27 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebC
c.SetTextualToken(token)
c.SetTokenClaims(claims)
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" has authorized two-factor via recovery code \"%s\", token will be expired at %d", user.Uid, credential.RecoveryCode, claims.ExpiresAt)
authResp := a.getAuthResponse(c, token, false, user)
authResp := a.getAuthResponse(c, token, false, user, applicationCloudSettingSlice)
return authResp, nil
}
func (a *AuthorizationsApi) getAuthResponse(c *core.WebContext, token string, need2FA bool, user *models.User) *models.AuthResponse {
func (a *AuthorizationsApi) getAuthResponse(c *core.WebContext, token string, need2FA bool, user *models.User, applicationCloudSettings *models.ApplicationCloudSettingSlice) *models.AuthResponse {
return &models.AuthResponse{
Token: token,
Need2FA: need2FA,
User: a.GetUserBasicInfo(user),
ApplicationCloudSettings: applicationCloudSettings,
NotificationContent: a.GetAfterLoginNotificationContent(user.Language, c.GetClientLocale()),
}
}
+22
View File
@@ -20,6 +20,7 @@ type TokensApi struct {
ApiWithUserInfo
tokens *services.TokenService
users *services.UserService
userAppCloudSettings *services.UserApplicationCloudSettingsService
}
// Initialize a token api singleton instance
@@ -38,6 +39,7 @@ var (
},
tokens: services.Tokens,
users: services.Users,
userAppCloudSettings: services.UserApplicationCloudSettings,
}
)
@@ -251,8 +253,18 @@ func (a *TokensApi) TokenRefreshHandler(c *core.WebContext) (any, *errs.Error) {
}
}
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[tokens.TokenRefreshHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
refreshResp := &models.TokenRefreshResponse{
User: a.GetUserBasicInfo(user),
ApplicationCloudSettings: applicationCloudSettingSlice,
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
}
@@ -276,12 +288,22 @@ func (a *TokensApi) TokenRefreshHandler(c *core.WebContext) (any, *errs.Error) {
c.SetTextualToken(token)
c.SetTokenClaims(claims)
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[tokens.TokenRefreshHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[tokens.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt)
refreshResp := &models.TokenRefreshResponse{
NewToken: token,
OldTokenId: a.tokens.GenerateTokenId(oldTokenRecord),
User: a.GetUserBasicInfo(user),
ApplicationCloudSettings: applicationCloudSettingSlice,
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
}
+191
View File
@@ -0,0 +1,191 @@
package api
import (
"encoding/json"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/services"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// UserApplicationCloudSettingsApi represents user application cloud settings api
type UserApplicationCloudSettingsApi struct {
userAppCloudSettings *services.UserApplicationCloudSettingsService
}
// Initialize a user application cloud settings api singleton instance
var (
UserApplicationCloudSettings = &UserApplicationCloudSettingsApi{
userAppCloudSettings: services.UserApplicationCloudSettings,
}
)
// ApplicationSettingsGetHandler returns application cloud settings of current user
func (a *UserApplicationCloudSettingsApi) ApplicationSettingsGetHandler(c *core.WebContext) (any, *errs.Error) {
uid := c.GetCurrentUid()
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, uid)
if err != nil {
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsGetHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if userApplicationCloudSettings == nil {
return false, nil
}
applicationCloudSettingSlice := userApplicationCloudSettings.Settings
if len(applicationCloudSettingSlice) < 1 {
return false, nil
}
return applicationCloudSettingSlice, nil
}
// ApplicationSettingsUpdateHandler updates user application cloud settings by request parameters for current user
func (a *UserApplicationCloudSettingsApi) ApplicationSettingsUpdateHandler(c *core.WebContext) (any, *errs.Error) {
var userAppCloudSettingUpdateReq models.UserApplicationCloudSettingsUpdateRequest
err := c.ShouldBindJSON(&userAppCloudSettingUpdateReq)
if err != nil {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] parse request failed, because %s", err.Error())
return false, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
uid := c.GetCurrentUid()
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, uid)
if err != nil {
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
return false, errs.Or(err, errs.ErrOperationFailed)
}
oldApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
if userApplicationCloudSettings != nil {
for _, setting := range userApplicationCloudSettings.Settings {
oldApplicationCloudSettingsMap[setting.SettingKey] = setting
}
}
// Check if the full update settings are the same as the existing settings
if userAppCloudSettingUpdateReq.FullUpdate {
if len(userAppCloudSettingUpdateReq.Settings) == len(oldApplicationCloudSettingsMap) {
needUpdate := false
for _, setting := range userAppCloudSettingUpdateReq.Settings {
oldSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
if !exists || oldSetting.SettingValue != setting.SettingValue {
needUpdate = true
break
}
}
if !needUpdate {
return false, errs.ErrNothingWillBeUpdated
}
}
} else { // Check if the partial update settings are the same as the existing settings or the settings to update are not set to sync
needUpdate := true
for _, setting := range userAppCloudSettingUpdateReq.Settings {
cloudSetting, exists := oldApplicationCloudSettingsMap[setting.SettingKey]
if !exists {
needUpdate = false
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not set to sync", setting.SettingKey)
} else if cloudSetting.SettingValue == setting.SettingValue {
needUpdate = false
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" value \"%s\" is not changed, no need to update", setting.SettingKey, setting.SettingValue)
}
}
if !needUpdate {
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] no user application cloud settings need to update for user \"uid:%d\"", uid)
return false, nil
}
}
newApplicationCloudSettingsMap := make(map[string]models.ApplicationCloudSetting)
var newApplicationCloudSettingSlice models.ApplicationCloudSettingSlice
if userAppCloudSettingUpdateReq.FullUpdate {
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings force update, will overwrite all existing settings", uid)
} else {
if len(oldApplicationCloudSettingsMap) > 0 {
log.Infof(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user \"uid:%d\" application cloud settings exists, try to merge it with request settings", uid)
newApplicationCloudSettingsMap = oldApplicationCloudSettingsMap
}
}
for _, setting := range userAppCloudSettingUpdateReq.Settings {
newApplicationCloudSettingsMap[setting.SettingKey] = setting
}
for settingKey, setting := range newApplicationCloudSettingsMap {
settingType, exists := models.ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[settingKey]
if !exists {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" is not supported to sync", settingKey)
continue
}
if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING {
// Do Nothing
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER {
_, err := utils.StringToFloat64(setting.SettingValue)
if err != nil {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid number value \"%s\"", settingKey, setting.SettingValue)
continue
}
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN {
if setting.SettingValue != "true" && setting.SettingValue != "false" {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid boolean value \"%s\"", settingKey, setting.SettingValue)
continue
}
} else if settingType == models.USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP {
var settingValueMap map[string]bool
err := json.Unmarshal([]byte(setting.SettingValue), &settingValueMap)
if err != nil {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has invalid map value \"%s\", because %s", settingKey, setting.SettingValue, err.Error())
continue
}
} else {
log.Warnf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] user application cloud setting key \"%s\" has unknown type \"%s\"", settingKey, settingType)
continue
}
newApplicationCloudSettingSlice = append(newApplicationCloudSettingSlice, setting)
}
err = a.userAppCloudSettings.UpdateUserApplicationCloudSettings(c, uid, newApplicationCloudSettingSlice)
if err != nil {
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsUpdateHandler] failed to update user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
return false, errs.Or(err, errs.ErrOperationFailed)
}
return true, nil
}
// ApplicationSettingsDisableHandler disabled user application cloud settings by request parameters for current user
func (a *UserApplicationCloudSettingsApi) ApplicationSettingsDisableHandler(c *core.WebContext) (any, *errs.Error) {
uid := c.GetCurrentUid()
err := a.userAppCloudSettings.ClearUserApplicationCloudSettings(c, uid)
if err != nil {
log.Errorf(c, "[user_app_cloud_settings.ApplicationSettingsDisableHandler] failed to clear user application cloud settings for user \"uid:%d\", because %s", uid, err.Error())
return false, errs.Or(err, errs.ErrOperationFailed)
}
return true, nil
}
+1
View File
@@ -5,6 +5,7 @@ type AuthResponse struct {
Token string `json:"token"`
Need2FA bool `json:"need2FA"`
User *UserBasicInfo `json:"user"`
ApplicationCloudSettings *ApplicationCloudSettingSlice `json:"applicationCloudSettings,omitempty"`
NotificationContent string `json:"notificationContent,omitempty"`
}
+1
View File
@@ -27,6 +27,7 @@ type TokenRefreshResponse struct {
NewToken string `json:"newToken,omitempty"`
OldTokenId string `json:"oldTokenId,omitempty"`
User *UserBasicInfo `json:"user"`
ApplicationCloudSettings *ApplicationCloudSettingSlice `json:"applicationCloudSettings,omitempty"`
NotificationContent string `json:"notificationContent,omitempty"`
}
+89
View File
@@ -0,0 +1,89 @@
package models
import (
"encoding/json"
)
type UserApplicationCloudSettingType string
const (
USER_APPLICATION_CLOUD_SETTING_TYPE_STRING UserApplicationCloudSettingType = "string"
USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER UserApplicationCloudSettingType = "number"
USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN UserApplicationCloudSettingType = "boolean"
USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP UserApplicationCloudSettingType = "string_boolean_map"
)
var ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES = map[string]UserApplicationCloudSettingType{
// Basic Settings
"showAccountBalance": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
// Overview Page
"showAmountInHomePage": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
"timezoneUsedForStatisticsInHomePage": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
// Transaction List Page
"itemsCountInTransactionListPage": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"showTotalAmountInTransactionListPage": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
"showTagInTransactionListPage": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
// Transaction Edit Page
"autoSaveTransactionDraft": USER_APPLICATION_CLOUD_SETTING_TYPE_STRING,
"autoGetCurrentGeoLocation": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
"alwaysShowTransactionPicturesInMobileTransactionEditPage": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN,
// Exchange Rates Data Page
"currencySortByInExchangeRatesPage": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
// Statistics Settings
"statistics.defaultChartDataType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultTimezoneType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultAccountFilter": USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP,
"statistics.defaultTransactionCategoryFilter": USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP,
"statistics.defaultSortingType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultCategoricalChartType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultCategoricalChartDataRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultTrendChartType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
"statistics.defaultTrendChartDataRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
}
// UserApplicationCloudSetting represents user application cloud setting stored in database
type UserApplicationCloudSetting struct {
Uid int64 `xorm:"PK"`
Settings ApplicationCloudSettingSlice `xorm:"BLOB"`
UpdatedUnixTime int64
}
// UserApplicationCloudSettingsUpdateRequest represents all parameters of application cloud settings update request
type UserApplicationCloudSettingsUpdateRequest struct {
Settings ApplicationCloudSettingSlice `json:"settings"`
FullUpdate bool `json:"fullUpdate"`
}
// ApplicationCloudSettingSlice represents the slice data structure of ApplicationCloudSetting
type ApplicationCloudSettingSlice []ApplicationCloudSetting
// ApplicationCloudSetting represents one application cloud setting
type ApplicationCloudSetting struct {
SettingKey string `json:"settingKey"`
SettingValue string `json:"settingValue"`
}
// FromDB fills the fields from the data stored in database
func (s *ApplicationCloudSettingSlice) FromDB(data []byte) error {
return json.Unmarshal(data, s)
}
// ToDB returns the actual stored data in database
func (s *ApplicationCloudSettingSlice) ToDB() ([]byte, error) {
return json.Marshal(s)
}
// Len returns the count of items
func (s ApplicationCloudSettingSlice) Len() int {
return len(s)
}
// Swap swaps two items
func (s ApplicationCloudSettingSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less reports whether the first item is less than the second one
func (s ApplicationCloudSettingSlice) Less(i, j int) bool {
return s[i].SettingKey < s[j].SettingKey
}
+92
View File
@@ -0,0 +1,92 @@
package services
import (
"sort"
"time"
"xorm.io/xorm"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/datastore"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
// UserApplicationCloudSettingsService represents user application cloud settings service
type UserApplicationCloudSettingsService struct {
ServiceUsingDB
}
// Initialize a user application cloud settings service singleton instance
var (
UserApplicationCloudSettings = &UserApplicationCloudSettingsService{
ServiceUsingDB: ServiceUsingDB{
container: datastore.Container,
},
}
)
// GetUserApplicationCloudSettingsByUid returns user application cloud settings models of user
func (s *UserApplicationCloudSettingsService) GetUserApplicationCloudSettingsByUid(c core.Context, uid int64) (*models.UserApplicationCloudSetting, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
applicationCloudSetting := &models.UserApplicationCloudSetting{}
has, err := s.UserDB().NewSession(c).ID(uid).Get(applicationCloudSetting)
if err != nil {
return nil, err
} else if !has {
return nil, nil
}
return applicationCloudSetting, nil
}
// UpdateUserApplicationCloudSettings updates user application cloud settings
func (s *UserApplicationCloudSettingsService) UpdateUserApplicationCloudSettings(c core.Context, uid int64, settings models.ApplicationCloudSettingSlice) error {
if uid <= 0 {
return errs.ErrUserIdInvalid
}
sort.Sort(settings)
userApplicationCloudSetting := &models.UserApplicationCloudSetting{
Uid: uid,
Settings: settings,
UpdatedUnixTime: time.Now().Unix(),
}
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
exists, err := sess.Cols("uid").Where("uid=?", uid).Exist(&models.UserApplicationCloudSetting{})
if err != nil {
return err
}
if !exists {
_, err = sess.Insert(userApplicationCloudSetting)
} else {
_, err = sess.ID(uid).Cols("settings", "updated_unix_time").Update(userApplicationCloudSetting)
}
if err != nil {
return err
}
return nil
})
}
// ClearUserApplicationCloudSettings clears user application cloud settings
func (s *UserApplicationCloudSettingsService) ClearUserApplicationCloudSettings(c core.Context, uid int64) error {
if uid <= 0 {
return errs.ErrUserIdInvalid
}
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
_, err := sess.Where("uid=?", uid).Delete(&models.UserApplicationCloudSetting{})
return err
})
}
+73 -15
View File
@@ -20,24 +20,34 @@ export interface BaseApplicationSetting {
}
export interface ApplicationSettings extends BaseApplicationSetting {
// Debug Settings
debug: boolean;
// Basic Settings
theme: string;
fontSize: number;
timeZone: string;
debug: boolean;
autoUpdateExchangeRatesData: boolean;
showAccountBalance: boolean;
animate: boolean;
// Application Lock
applicationLock: boolean;
applicationLockWebAuthn: boolean;
autoUpdateExchangeRatesData: boolean;
autoSaveTransactionDraft: string;
autoGetCurrentGeoLocation: boolean;
alwaysShowTransactionPicturesInMobileTransactionEditPage: boolean;
// Navigation Bar
showAddTransactionButtonInDesktopNavbar: boolean;
// Overview Page
showAmountInHomePage: boolean;
timezoneUsedForStatisticsInHomePage: number;
// Transaction List Page
itemsCountInTransactionListPage: number;
showTotalAmountInTransactionListPage: boolean;
showTagInTransactionListPage: boolean;
showAccountBalance: boolean;
// Transaction Edit Page
autoSaveTransactionDraft: string;
autoGetCurrentGeoLocation: boolean;
alwaysShowTransactionPicturesInMobileTransactionEditPage: boolean;
// Exchange Rates Data Page
currencySortByInExchangeRatesPage: number;
// Statistics Settings
statistics: {
defaultChartDataType: number;
defaultTimezoneType: number;
@@ -49,7 +59,18 @@ export interface ApplicationSettings extends BaseApplicationSetting {
defaultTrendChartType: number;
defaultTrendChartDataRangeType: number;
};
animate: boolean;
}
export enum UserApplicationCloudSettingType {
String = 'string',
Number = 'number',
Boolean = 'boolean',
StringBooleanMap = 'string_boolean_map',
}
export interface ApplicationCloudSetting {
readonly settingKey: string;
readonly settingValue: string;
}
export interface LocaleDefaultSettings {
@@ -66,25 +87,63 @@ export interface WebAuthnConfig {
readonly credentialId: string;
}
export const ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES: Record<string, UserApplicationCloudSettingType> = {
// Basic Settings
'showAccountBalance': UserApplicationCloudSettingType.Boolean,
// Overview Page
'showAmountInHomePage': UserApplicationCloudSettingType.Boolean,
'timezoneUsedForStatisticsInHomePage': UserApplicationCloudSettingType.Number,
// Transaction List Page
'itemsCountInTransactionListPage': UserApplicationCloudSettingType.Number,
'showTotalAmountInTransactionListPage': UserApplicationCloudSettingType.Boolean,
'showTagInTransactionListPage': UserApplicationCloudSettingType.Boolean,
// Transaction Edit Page
'autoSaveTransactionDraft': UserApplicationCloudSettingType.String,
'autoGetCurrentGeoLocation': UserApplicationCloudSettingType.Boolean,
'alwaysShowTransactionPicturesInMobileTransactionEditPage': UserApplicationCloudSettingType.Boolean,
// Exchange Rates Data Page
'currencySortByInExchangeRatesPage': UserApplicationCloudSettingType.Number,
// Statistics Settings
'statistics.defaultChartDataType': UserApplicationCloudSettingType.Number,
'statistics.defaultTimezoneType': UserApplicationCloudSettingType.Number,
'statistics.defaultAccountFilter': UserApplicationCloudSettingType.StringBooleanMap,
'statistics.defaultTransactionCategoryFilter': UserApplicationCloudSettingType.StringBooleanMap,
'statistics.defaultSortingType': UserApplicationCloudSettingType.Number,
'statistics.defaultCategoricalChartType': UserApplicationCloudSettingType.Number,
'statistics.defaultCategoricalChartDataRangeType': UserApplicationCloudSettingType.Number,
'statistics.defaultTrendChartType': UserApplicationCloudSettingType.Number,
'statistics.defaultTrendChartDataRangeType': UserApplicationCloudSettingType.Number,
};
export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
// Debug Settings
debug: false,
// Basic Settings
theme: 'auto',
fontSize: 1,
timeZone: '',
debug: false,
autoUpdateExchangeRatesData: true,
showAccountBalance: true,
animate: true,
// Application Lock
applicationLock: false,
applicationLockWebAuthn: false,
autoUpdateExchangeRatesData: true,
autoSaveTransactionDraft: 'disabled',
autoGetCurrentGeoLocation: false,
alwaysShowTransactionPicturesInMobileTransactionEditPage: false,
// Navigation Bar
showAddTransactionButtonInDesktopNavbar: true,
// Overview Page
showAmountInHomePage: true,
timezoneUsedForStatisticsInHomePage: TimezoneTypeForStatistics.Default.type,
// Transaction List Page
itemsCountInTransactionListPage: 15,
showTotalAmountInTransactionListPage: true,
showTagInTransactionListPage: true,
showAccountBalance: true,
// Transaction Edit Page
autoSaveTransactionDraft: 'disabled',
autoGetCurrentGeoLocation: false,
alwaysShowTransactionPicturesInMobileTransactionEditPage: false,
// Exchange Rates Data Page
currencySortByInExchangeRatesPage: CurrencySortingType.Default.type,
// Statistics Settings
statistics: {
defaultChartDataType: ChartDataType.Default.type,
defaultTimezoneType: TimezoneTypeForStatistics.Default.type,
@@ -95,8 +154,7 @@ export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
defaultCategoricalChartDataRangeType: DEFAULT_CATEGORICAL_CHART_DATA_RANGE.type,
defaultTrendChartType: TrendChartType.Default.type,
defaultTrendChartDataRangeType: DEFAULT_TREND_CHART_DATA_RANGE.type,
},
animate: true
}
};
export const DEFAULT_LOCALE_SETTINGS: LocaleDefaultSettings = {
+15
View File
@@ -2,6 +2,9 @@ import axios, { type AxiosRequestConfig, type AxiosRequestHeaders, type AxiosRes
import type { ApiResponse } from '@/core/api.ts';
import type {
ApplicationCloudSetting
} from '@/core/setting.ts';
import {
TransactionType
} from '@/core/transaction.ts';
@@ -119,6 +122,9 @@ import type {
UserProfileUpdateRequest,
UserProfileUpdateResponse
} from '@/models/user.ts';
import type {
UserApplicationCloudSettingsUpdateRequest
} from '@/models/user_app_cloud_setting.ts';
import {
getCurrentToken,
@@ -308,6 +314,15 @@ export default {
resendVerifyEmailByLoginedUser: (): ApiResponsePromise<boolean> => {
return axios.post<ApiResponse<boolean>>('v1/users/verify_email/resend.json');
},
getUserApplicationCloudSettings: (): ApiResponsePromise<ApplicationCloudSetting[] | false> => {
return axios.get<ApiResponse<ApplicationCloudSetting[] | false>>('v1/users/settings/cloud/get.json');
},
updateUserApplicationCloudSettings: (req: UserApplicationCloudSettingsUpdateRequest): ApiResponsePromise<boolean> => {
return axios.post<ApiResponse<boolean>>('v1/users/settings/cloud/update.json', req);
},
disableUserApplicationCloudSettings: (): ApiResponsePromise<boolean> => {
return axios.post<ApiResponse<boolean>>('v1/users/settings/cloud/disable.json');
},
get2FAStatus: (): ApiResponsePromise<TwoFactorStatusResponse> => {
return axios.get<ApiResponse<TwoFactorStatusResponse>>('v1/users/2fa/status.json');
},
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "Benutzer hat abgebrochen oder dieses Gerät unterstützt WebAuthn nicht",
"Failed to authenticate with WebAuthn": "Authentifizierung mit WebAuthn fehlgeschlagen",
"WebAuthn is not enabled": "WebAuthn ist nicht aktiviert",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Sind Sie sicher, dass Sie sich erneut anmelden möchten?",
"Exchange Rates Data": "Wechselkursdaten",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "User has canceled or this device does not support WebAuthn",
"Failed to authenticate with WebAuthn": "Failed to authenticate with WebAuthn",
"WebAuthn is not enabled": "WebAuthn is not enabled",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Are you sure you want to re-login?",
"Exchange Rates Data": "Exchange Rates Data",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "El usuario ha cancelado o este dispositivo no es compatible con WebAuthn",
"Failed to authenticate with WebAuthn": "No se pudo autenticar con WebAuthn",
"WebAuthn is not enabled": "WebAuthn no está habilitado",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "¿Está seguro de que desea volver a iniciar sesión?",
"Exchange Rates Data": "Datos de tipos de cambio",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "L'utente ha annullato o questo dispositivo non supporta WebAuthn",
"Failed to authenticate with WebAuthn": "Impossibile autenticare con WebAuthn",
"WebAuthn is not enabled": "WebAuthn non è abilitato",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Sei sicuro di voler accedere di nuovo?",
"Exchange Rates Data": "Dati tassi di cambio",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "ユーザーがキャンセルしたか、このデバイスがWebAuthnをサポートしていません",
"Failed to authenticate with WebAuthn": "WebAuthnによる認証に失敗しました",
"WebAuthn is not enabled": "WebAuthnが有効になっていません",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "再ログインしますか?",
"Exchange Rates Data": "為替レートデータ",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "O usuário cancelou ou este dispositivo não suporta WebAuthn",
"Failed to authenticate with WebAuthn": "Falha na autenticação com WebAuthn",
"WebAuthn is not enabled": "WebAuthn não está ativado",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Tem certeza de que deseja fazer login novamente?",
"Exchange Rates Data": "Dados de Taxas de Câmbio",
"User Custom": "Personalização do Usuário",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "Пользователь отменил или это устройство не поддерживает WebAuthn",
"Failed to authenticate with WebAuthn": "Не удалось аутентифицироваться с помощью WebAuthn",
"WebAuthn is not enabled": "WebAuthn не включен",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Вы уверены, что хотите войти снова?",
"Exchange Rates Data": "Данные о курсах валют",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "Користувач скасував дію або пристрій не підтримує WebAuthn",
"Failed to authenticate with WebAuthn": "Не вдалося пройти автентифікацію за допомогою WebAuthn",
"WebAuthn is not enabled": "WebAuthn не увімкнено",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Ви впевнені, що хочете увійти знову?",
"Exchange Rates Data": "Дані про курси валют",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "Người dùng đã hủy hoặc thiết bị này không hỗ trợ WebAuthn",
"Failed to authenticate with WebAuthn": "Xác thực bằng WebAuthn không thành công",
"WebAuthn is not enabled": "WebAuthn chưa được bật",
"Settings Sync": "Settings Sync",
"Synchronized Settings": "Synchronized Settings",
"Enable Settings Sync": "Enable Settings Sync",
"Disable Settings Sync": "Disable Settings Sync",
"Update Synchronized Settings": "Update Synchronized Settings",
"Settings sync is not enabled": "Settings sync is not enabled",
"Settings sync has been enabled": "Settings sync has been enabled",
"Settings sync has been disabled": "Settings sync has been disabled",
"Synchronized settings have been updated": "Synchronized settings have been updated",
"Unable to retrieve user synchronized application settings": "Unable to retrieve user synchronized application settings",
"Unable to update user synchronized application settings": "Unable to update user synchronized application settings",
"Unable to disable user synchronized application settings": "Unable to disable user synchronized application settings",
"Are you sure you want to re-login?": "Bạn có chắc chắn muốn đăng nhập lại không?",
"Exchange Rates Data": "Dữ liệu tỷ giá hối đoái",
"User Custom": "User Custom",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "用户已取消或当前设备不支持 WebAuthn",
"Failed to authenticate with WebAuthn": "使用 WebAuthn 认证失败",
"WebAuthn is not enabled": "WebAuthn 没有启用",
"Settings Sync": "设置同步",
"Synchronized Settings": "同步的设置",
"Enable Settings Sync": "启用设置同步",
"Disable Settings Sync": "禁用设置同步",
"Update Synchronized Settings": "更新同步的设置",
"Settings sync is not enabled": "设置同步没有启用",
"Settings sync has been enabled": "设置同步已经启用",
"Settings sync has been disabled": "设置同步已经禁用",
"Synchronized settings have been updated": "同步的设置已经更新",
"Unable to retrieve user synchronized application settings": "无法获取用户同步的应用设置",
"Unable to update user synchronized application settings": "无法更新用户同步的应用设置",
"Unable to disable user synchronized application settings": "无法禁用用户同步的应用设置",
"Are you sure you want to re-login?": "您确定要重新登录?",
"Exchange Rates Data": "汇率数据",
"User Custom": "用户自定义",
+12
View File
@@ -2083,6 +2083,18 @@
"User has canceled or this device does not support WebAuthn": "使用者已取消或此裝置不支援 WebAuthn",
"Failed to authenticate with WebAuthn": "使用 WebAuthn 驗證失敗",
"WebAuthn is not enabled": "WebAuthn 沒有啟用",
"Settings Sync": "設定同步",
"Synchronized Settings": "同步的設定",
"Enable Settings Sync": "啟用設定同步",
"Disable Settings Sync": "停用設定同步",
"Update Synchronized Settings": "更新同步的設定",
"Settings sync is not enabled": "設定同步沒有啟用",
"Settings sync has been enabled": "設定同步已啟用",
"Settings sync has been disabled": "設定同步已停用",
"Synchronized settings have been updated": "同步的設定已更新",
"Unable to retrieve user synchronized application settings": "無法取得使用者同步的應用程式設定",
"Unable to update user synchronized application settings": "無法更新使用者同步的應用程式設定",
"Unable to disable user synchronized application settings": "無法停用使用者同步的應用程式設定",
"Are you sure you want to re-login?": "您確定要重新登入?",
"Exchange Rates Data": "匯率資料",
"User Custom": "使用者自訂",
+3
View File
@@ -1,9 +1,12 @@
import type { ApplicationCloudSetting } from '@/core/setting.ts';
import type { UserBasicInfo } from './user.ts';
export interface AuthResponse {
readonly token: string;
readonly need2FA: boolean;
readonly user?: UserBasicInfo;
readonly applicationCloudSettings?: ApplicationCloudSetting[];
readonly notificationContent?: string;
}
+3
View File
@@ -1,3 +1,5 @@
import type { ApplicationCloudSetting } from '@/core/setting.ts';
import type { UserBasicInfo } from './user.ts';
export const TOKEN_CLI_USER_AGENT: string = 'ezbookkeeping Cli';
@@ -6,6 +8,7 @@ export interface TokenRefreshResponse {
readonly newToken?: string;
readonly oldTokenId?: string;
readonly user: UserBasicInfo;
readonly applicationCloudSettings?: ApplicationCloudSetting[];
readonly notificationContent?: string;
}
+6
View File
@@ -0,0 +1,6 @@
import type { ApplicationCloudSetting } from '@/core/setting.ts';
export interface UserApplicationCloudSettingsUpdateRequest {
readonly settings: ApplicationCloudSetting[];
readonly fullUpdate: boolean;
}
+6
View File
@@ -19,6 +19,7 @@ import StatisticsSettingsPage from '@/views/mobile/statistics/SettingsPage.vue';
import TextSizeSettingsPage from '@/views/mobile/settings/TextSizeSettingsPage.vue';
import PageSettingsPage from '@/views/mobile/settings/PageSettingsPage.vue';
import ApplicationCloudSyncSettingsPage from '@/views/mobile/settings/ApplicationCloudSyncSettingsPage.vue';
import AccountFilterSettingsPage from '@/views/mobile/settings/AccountFilterSettingsPage.vue';
import CategoryFilterSettingsPage from '@/views/mobile/settings/CategoryFilterSettingsPage.vue';
import TransactionTagFilterSettingsPage from '@/views/mobile/settings/TransactionTagFilterSettingsPage.vue';
@@ -225,6 +226,11 @@ const routes: Router.RouteParameters[] = [
async: asyncResolve(PageSettingsPage),
beforeEnter: [checkLogin]
},
{
path: '/settings/sync',
async: asyncResolve(ApplicationCloudSyncSettingsPage),
beforeEnter: [checkLogin]
},
{
path: '/settings',
async: asyncResolve(SettingsPage),
+4
View File
@@ -103,6 +103,8 @@ export const useRootStore = defineStore('root', () => {
}
}
settingsStore.setApplicationSettingsFromCloudSettings(data.result.applicationCloudSettings);
updateCurrentToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
@@ -162,6 +164,8 @@ export const useRootStore = defineStore('root', () => {
}
}
settingsStore.setApplicationSettingsFromCloudSettings(data.result.applicationCloudSettings);
updateCurrentToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
+285 -36
View File
@@ -1,7 +1,24 @@
import { ref } from 'vue';
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import type { ApplicationSettings, LocaleDefaultSettings } from '@/core/setting.ts';
import {
type ApplicationSettingValue,
type ApplicationSettingSubValue,
type ApplicationSettings,
type ApplicationCloudSetting,
type LocaleDefaultSettings,
UserApplicationCloudSettingType,
ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES
} from '@/core/setting.ts';
import {
isObject,
isString,
isBoolean,
getObjectOwnFieldCount,
arrayItemToObjectField
} from '@/lib/common.ts';
import {
getApplicationSettings,
getLocaleDefaultSettings,
@@ -10,10 +27,100 @@ import {
clearSettings
} from '@/lib/settings.ts';
import logger from '@/lib/logger.ts';
import services from '@/lib/services.ts';
export const useSettingsStore = defineStore('settings', () => {
const appSettings = ref<ApplicationSettings>(getApplicationSettings());
const syncedAppSettings = ref<Record<string, boolean>>({});
const localeDefaultSettings = ref<LocaleDefaultSettings>(getLocaleDefaultSettings());
const enableApplicationCloudSync = computed<boolean>(() => getObjectOwnFieldCount(syncedAppSettings.value) > 0);
function updateApplicationSettingsValueAndAppSettingsFromCloudSetting(key: string, value: string | number | boolean | Record<string, boolean>): void {
const keyItems = key.split('.');
if (keyItems.length === 1) {
updateApplicationSettingsValue(keyItems[0], value);
appSettings.value[keyItems[0]] = value;
} else if (keyItems.length === 2) {
updateApplicationSettingsSubValue(keyItems[0], keyItems[1], value);
(appSettings.value[keyItems[0]] as Record<string, ApplicationSettingSubValue>)[keyItems[1]] = value;
} else {
logger.warn(`cannot load application cloud setting "${key}", because it has invalid key format`);
}
}
function createUserApplicationCloudSetting(key: string): ApplicationCloudSetting | null {
const settingType = ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[key];
if (!settingType) {
logger.warn(`cannot get application cloud setting "${key}", because it is not supported to sync`);
return null;
}
const keyItems = key.split('.');
let value: ApplicationSettingValue | ApplicationSettingSubValue = appSettings.value[key];
if (keyItems.length === 2) {
value = (appSettings.value[keyItems[0]] as Record<string, ApplicationSettingSubValue>)[keyItems[1]];
} else if (keyItems.length > 2) {
logger.warn(`cannot get application cloud setting "${key}", because it has invalid key format`);
return null;
}
let settingValue = '';
if (settingType === UserApplicationCloudSettingType.String) {
if (!value) {
settingValue = '';
} else {
settingValue = value.toString();
}
} else {
settingValue = JSON.stringify(value);
}
return {
settingKey: key,
settingValue: settingValue
};
}
function updateUserApplicationCloudSettingValue(key: string, value: string | number | boolean | Record<string, boolean>): void {
if (!syncedAppSettings.value || !syncedAppSettings.value[key]) {
return;
}
const settingType = ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[key];
if (!settingType) {
return;
}
const settingValue = isString(value) ? value : JSON.stringify(value);
services.updateUserApplicationCloudSettings({
settings: [{
settingKey: key,
settingValue: settingValue
}],
fullUpdate: false
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
logger.debug(`failed to update user application cloud setting "${key}" with value "${settingValue}"`);
return;
}
logger.debug(`update user application cloud setting "${key}" with value "${settingValue}" successfully`);
}).catch(error => {
logger.debug(`failed to update user application cloud setting "${key}" with value "${settingValue}"`, error);
});
}
// Basic Settings
function setTheme(value: string): void {
updateApplicationSettingsValue('theme', value);
appSettings.value.theme = value;
@@ -29,6 +136,23 @@ export const useSettingsStore = defineStore('settings', () => {
appSettings.value.timeZone = value;
}
function setAutoUpdateExchangeRatesData(value: boolean): void {
updateApplicationSettingsValue('autoUpdateExchangeRatesData', value);
appSettings.value.autoUpdateExchangeRatesData = value;
}
function setShowAccountBalance(value: boolean): void {
updateApplicationSettingsValue('showAccountBalance', value);
appSettings.value.showAccountBalance = value;
updateUserApplicationCloudSettingValue('showAccountBalance', value);
}
function setEnableAnimate(value: boolean): void {
updateApplicationSettingsValue('animate', value);
appSettings.value.animate = value;
}
// Application Lock
function setEnableApplicationLock(value: boolean): void {
updateApplicationSettingsValue('applicationLock', value);
appSettings.value.applicationLock = value;
@@ -39,114 +163,123 @@ export const useSettingsStore = defineStore('settings', () => {
appSettings.value.applicationLockWebAuthn = value;
}
function setAutoUpdateExchangeRatesData(value: boolean): void {
updateApplicationSettingsValue('autoUpdateExchangeRatesData', value);
appSettings.value.autoUpdateExchangeRatesData = value;
}
function setAutoSaveTransactionDraft(value: string): void {
updateApplicationSettingsValue('autoSaveTransactionDraft', value);
appSettings.value.autoSaveTransactionDraft = value;
}
function setAutoGetCurrentGeoLocation(value: boolean): void {
updateApplicationSettingsValue('autoGetCurrentGeoLocation', value);
appSettings.value.autoGetCurrentGeoLocation = value;
}
function setAlwaysShowTransactionPicturesInMobileTransactionEditPage(value: boolean): void {
updateApplicationSettingsValue('alwaysShowTransactionPicturesInMobileTransactionEditPage', value);
appSettings.value.alwaysShowTransactionPicturesInMobileTransactionEditPage = value;
}
// Navigation Bar
function setShowAddTransactionButtonInDesktopNavbar(value: boolean): void {
updateApplicationSettingsValue('showAddTransactionButtonInDesktopNavbar', value);
appSettings.value.showAddTransactionButtonInDesktopNavbar = value;
}
// Overview Page
function setShowAmountInHomePage(value: boolean): void {
updateApplicationSettingsValue('showAmountInHomePage', value);
appSettings.value.showAmountInHomePage = value;
updateUserApplicationCloudSettingValue('showAmountInHomePage', value);
}
function setTimezoneUsedForStatisticsInHomePage(value: number): void {
updateApplicationSettingsValue('timezoneUsedForStatisticsInHomePage', value);
appSettings.value.timezoneUsedForStatisticsInHomePage = value;
updateUserApplicationCloudSettingValue('timezoneUsedForStatisticsInHomePage', value);
}
// Transaction List Page
function setItemsCountInTransactionListPage(value: number): void {
updateApplicationSettingsValue('itemsCountInTransactionListPage', value);
appSettings.value.itemsCountInTransactionListPage = value;
updateUserApplicationCloudSettingValue('itemsCountInTransactionListPage', value);
}
function setShowTotalAmountInTransactionListPage(value: boolean): void {
updateApplicationSettingsValue('showTotalAmountInTransactionListPage', value);
appSettings.value.showTotalAmountInTransactionListPage = value;
updateUserApplicationCloudSettingValue('showTotalAmountInTransactionListPage', value);
}
function setShowTagInTransactionListPage(value: boolean): void {
updateApplicationSettingsValue('showTagInTransactionListPage', value);
appSettings.value.showTagInTransactionListPage = value;
updateUserApplicationCloudSettingValue('showTagInTransactionListPage', value);
}
function setShowAccountBalance(value: boolean): void {
updateApplicationSettingsValue('showAccountBalance', value);
appSettings.value.showAccountBalance = value;
// Transaction Edit Page
function setAutoSaveTransactionDraft(value: string): void {
updateApplicationSettingsValue('autoSaveTransactionDraft', value);
appSettings.value.autoSaveTransactionDraft = value;
updateUserApplicationCloudSettingValue('autoSaveTransactionDraft', value);
}
function setAutoGetCurrentGeoLocation(value: boolean): void {
updateApplicationSettingsValue('autoGetCurrentGeoLocation', value);
appSettings.value.autoGetCurrentGeoLocation = value;
updateUserApplicationCloudSettingValue('autoGetCurrentGeoLocation', value);
}
function setAlwaysShowTransactionPicturesInMobileTransactionEditPage(value: boolean): void {
updateApplicationSettingsValue('alwaysShowTransactionPicturesInMobileTransactionEditPage', value);
appSettings.value.alwaysShowTransactionPicturesInMobileTransactionEditPage = value;
updateUserApplicationCloudSettingValue('alwaysShowTransactionPicturesInMobileTransactionEditPage', value);
}
// Exchange Rates Data Page
function setCurrencySortByInExchangeRatesPage(value: number): void {
updateApplicationSettingsValue('currencySortByInExchangeRatesPage', value);
appSettings.value.currencySortByInExchangeRatesPage = value;
updateUserApplicationCloudSettingValue('currencySortByInExchangeRatesPage', value);
}
// Statistics Settings
function setStatisticsDefaultChartDataType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultChartDataType', value);
appSettings.value.statistics.defaultChartDataType = value;
updateUserApplicationCloudSettingValue('statistics.defaultChartDataType', value);
}
function setStatisticsDefaultTimezoneType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultTimezoneType', value);
appSettings.value.statistics.defaultTimezoneType = value;
updateUserApplicationCloudSettingValue('statistics.defaultTimezoneType', value);
}
function setStatisticsDefaultAccountFilter(value: Record<string, boolean>): void {
updateApplicationSettingsSubValue('statistics', 'defaultAccountFilter', value);
appSettings.value.statistics.defaultAccountFilter = value;
updateUserApplicationCloudSettingValue('statistics.defaultAccountFilter', value);
}
function setStatisticsDefaultTransactionCategoryFilter(value: Record<string, boolean>): void {
updateApplicationSettingsSubValue('statistics', 'defaultTransactionCategoryFilter', value);
appSettings.value.statistics.defaultTransactionCategoryFilter = value;
updateUserApplicationCloudSettingValue('statistics.defaultTransactionCategoryFilter', value);
}
function setStatisticsSortingType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultSortingType', value);
appSettings.value.statistics.defaultSortingType = value;
updateUserApplicationCloudSettingValue('statistics.defaultSortingType', value);
}
function setStatisticsDefaultCategoricalChartType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultCategoricalChartType', value);
appSettings.value.statistics.defaultCategoricalChartType = value;
updateUserApplicationCloudSettingValue('statistics.defaultCategoricalChartType', value);
}
function setStatisticsDefaultCategoricalChartDateRange(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultCategoricalChartDataRangeType', value);
appSettings.value.statistics.defaultCategoricalChartDataRangeType = value;
updateUserApplicationCloudSettingValue('statistics.defaultCategoricalChartDataRangeType', value);
}
function setStatisticsDefaultTrendChartType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultTrendChartType', value);
appSettings.value.statistics.defaultTrendChartType = value;
updateUserApplicationCloudSettingValue('statistics.defaultTrendChartType', value);
}
function setStatisticsDefaultTrendChartDateRange(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultTrendChartDataRangeType', value);
appSettings.value.statistics.defaultTrendChartDataRangeType = value;
}
function setEnableAnimate(value: boolean): void {
updateApplicationSettingsValue('animate', value);
appSettings.value.animate = value;
updateUserApplicationCloudSettingValue('statistics.defaultTrendChartDataRangeType', value);
}
function clearAppSettings(): void {
@@ -154,6 +287,108 @@ export const useSettingsStore = defineStore('settings', () => {
appSettings.value = getApplicationSettings();
}
function createApplicationCloudSettings(applicationSettingKeys: string[]): ApplicationCloudSetting[] {
if (!applicationSettingKeys || applicationSettingKeys.length < 1) {
return [];
}
const settings: ApplicationCloudSetting[] = [];
for (let i = 0; i < applicationSettingKeys.length; i++) {
const settingKey = applicationSettingKeys[i];
const cloudSetting = createUserApplicationCloudSetting(settingKey);
if (cloudSetting) {
settings.push(cloudSetting);
}
}
return settings;
}
function setApplicationSettingsFromCloudSettings(cloudSettings?: ApplicationCloudSetting[]): void {
if (!cloudSettings || cloudSettings.length < 1) {
syncedAppSettings.value = {};
return;
}
syncedAppSettings.value = arrayItemToObjectField(cloudSettings.map(item => item.settingKey), true);
for (let i = 0; i < cloudSettings.length; i++) {
const setting = cloudSettings[i];
if (!setting || !setting.settingKey) {
continue;
}
const settingType = ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES[setting.settingKey];
if (!settingType) {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because it is not supported to sync`);
continue;
}
if (settingType === UserApplicationCloudSettingType.String) {
updateApplicationSettingsValueAndAppSettingsFromCloudSetting(setting.settingKey, setting.settingValue);
} else if (settingType === UserApplicationCloudSettingType.Number) {
const value = parseFloat(setting.settingValue);
if (isNaN(value)) {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because it has invalid number value`);
continue;
}
updateApplicationSettingsValueAndAppSettingsFromCloudSetting(setting.settingKey, value);
} else if (settingType === UserApplicationCloudSettingType.Boolean) {
if (setting.settingValue !== 'true' && setting.settingValue !== 'false') {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because it has invalid boolean value`);
continue;
}
updateApplicationSettingsValueAndAppSettingsFromCloudSetting(setting.settingKey, setting.settingValue === 'true');
} else if (settingType === UserApplicationCloudSettingType.StringBooleanMap) {
try {
const map = JSON.parse(setting.settingValue);
let isValid = isObject(map);
if (isValid) {
for (const key in map) {
if (!Object.prototype.hasOwnProperty.call(map, key)) {
continue;
}
const value = map[key];
if (!isBoolean(value)) {
isValid = false;
break;
}
}
}
if (!isValid) {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because it has invalid map value`);
continue;
}
updateApplicationSettingsValueAndAppSettingsFromCloudSetting(setting.settingKey, map as Record<string, boolean>);
} catch (error) {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because cannot parse JSON (${error})`);
}
} else {
logger.warn(`cannot load application cloud setting "${setting.settingKey}", because it has unknown type "${settingType}"`);
}
}
}
function updateApplicationSyncSettingKeys(settingKeys?: string[]): void {
if (!settingKeys || settingKeys.length < 1) {
syncedAppSettings.value = {};
} else {
syncedAppSettings.value = arrayItemToObjectField(settingKeys, true);
}
}
function updateLocalizedDefaultSettings(newLocaleDefaultSettings: LocaleDefaultSettings | null) {
if (!newLocaleDefaultSettings) {
return;
@@ -166,25 +401,37 @@ export const useSettingsStore = defineStore('settings', () => {
return {
// states
appSettings,
syncedAppSettings,
localeDefaultSettings,
// computed states
enableApplicationCloudSync,
// functions
// -- Basic Settings
setTheme,
setFontSize,
setTimeZone,
setAutoUpdateExchangeRatesData,
setShowAccountBalance,
setEnableAnimate,
// -- Application Lock
setEnableApplicationLock,
setEnableApplicationLockWebAuthn,
setAutoUpdateExchangeRatesData,
setAutoSaveTransactionDraft,
setAutoGetCurrentGeoLocation,
setAlwaysShowTransactionPicturesInMobileTransactionEditPage,
// -- Navigation Bar
setShowAddTransactionButtonInDesktopNavbar,
// -- Overview Page
setShowAmountInHomePage,
setTimezoneUsedForStatisticsInHomePage,
// -- Transaction List Page
setItemsCountInTransactionListPage,
setShowTotalAmountInTransactionListPage,
setShowTagInTransactionListPage,
setShowAccountBalance,
// -- Transaction Edit Page
setAutoSaveTransactionDraft,
setAutoGetCurrentGeoLocation,
setAlwaysShowTransactionPicturesInMobileTransactionEditPage,
// -- Exchange Rates Data Page
setCurrencySortByInExchangeRatesPage,
// -- Statistics Settings
setStatisticsDefaultChartDataType,
setStatisticsDefaultTimezoneType,
setStatisticsDefaultAccountFilter,
@@ -194,8 +441,10 @@ export const useSettingsStore = defineStore('settings', () => {
setStatisticsDefaultCategoricalChartDateRange,
setStatisticsDefaultTrendChartType,
setStatisticsDefaultTrendChartDateRange,
setEnableAnimate,
clearAppSettings,
createApplicationCloudSettings,
setApplicationSettingsFromCloudSettings,
updateApplicationSyncSettingKeys,
updateLocalizedDefaultSettings
};
});
+8 -1
View File
@@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import { useSettingsStore } from './setting.ts';
import { useUserStore } from './user.ts';
import type { TokenRefreshResponse, TokenInfoResponse } from '@/models/token.ts';
@@ -11,6 +12,9 @@ import logger from '@/lib/logger.ts';
import services from '@/lib/services.ts';
export const useTokensStore = defineStore('tokens', () => {
const settingsStore = useSettingsStore();
const userStore = useUserStore();
function getAllTokens(): Promise<TokenInfoResponse[]> {
return new Promise((resolve, reject) => {
services.getTokens().then(response => {
@@ -41,8 +45,11 @@ export const useTokensStore = defineStore('tokens', () => {
services.refreshToken().then(response => {
const data = response.data;
if (data && data.success && data.result) {
settingsStore.setApplicationSettingsFromCloudSettings(data.result.applicationCloudSettings);
}
if (data && data.success && data.result && data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserBasicInfo(data.result.user);
}
+86
View File
@@ -5,6 +5,7 @@ import { useSettingsStore } from './setting.ts';
import { type WeekDayValue, WeekDay } from '@/core/datetime.ts';
import { FiscalYearStart } from '@/core/fiscalyear.ts';
import type { ApplicationCloudSetting } from '@/core/setting.ts';
import {
type UserBasicInfo,
@@ -252,6 +253,88 @@ export const useUserStore = defineStore('user', () => {
});
}
function getUserApplicationCloudSettings(): Promise<ApplicationCloudSetting[] | false> {
return new Promise((resolve, reject) => {
services.getUserApplicationCloudSettings().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
resolve(data.result);
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load user synchronized application settings', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to retrieve user synchronized application settings' });
} else {
reject(error);
}
});
});
}
function fullUpdateUserApplicationCloudSettings(enabledSettingKeys: string[]): Promise<boolean> {
const settings = settingsStore.createApplicationCloudSettings(enabledSettingKeys);
return new Promise((resolve, reject) => {
services.updateUserApplicationCloudSettings({
settings: settings,
fullUpdate: true
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to update user synchronized application settings' });
return;
}
settingsStore.updateApplicationSyncSettingKeys(enabledSettingKeys);
resolve(data.result);
}).catch(error => {
logger.error('failed to update user synchronized application settings', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to update user synchronized application settings' });
} else {
reject(error);
}
});
});
}
function disableUserApplicationCloudSettings(): Promise<boolean> {
return new Promise((resolve, reject) => {
services.disableUserApplicationCloudSettings().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to disable user synchronized application settings' });
return;
}
settingsStore.updateApplicationSyncSettingKeys(undefined);
resolve(data.result);
}).catch(error => {
logger.error('failed to disable user synchronized application settings', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to disable user synchronized application settings' });
} else {
reject(error);
}
});
});
}
function getUserDataStatistics(): Promise<DataStatisticsResponse> {
return new Promise((resolve, reject) => {
services.getUserDataStatistics().then(response => {
@@ -353,6 +436,9 @@ export const useUserStore = defineStore('user', () => {
updateUserTransactionEditScope,
updateUserAvatar,
removeUserAvatar,
getUserApplicationCloudSettings,
fullUpdateUserApplicationCloudSettings,
disableUserApplicationCloudSettings,
getUserDataStatistics,
getExportedUserData,
getUserAvatarUrl
+2
View File
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 18px;
--ebk-trends-bar-chart-legend-text-font-size: 14px;
--ebk-account-list-group-title-height: 36px;
--ebk-synchronized-settings-list-group-title-height: 36px;
--ebk-synchronized-settings-list-device-icon-font-size: 19px;
--ebk-category-separate-icon-font-size: 18px;
--ebk-transaction-date-width: 25px;
--ebk-transaction-day-font-size: 16px;
+2
View File
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 19px;
--ebk-trends-bar-chart-legend-text-font-size: 15px;
--ebk-account-list-group-title-height: 37px;
--ebk-synchronized-settings-list-group-title-height: 37px;
--ebk-synchronized-settings-list-device-icon-font-size: 20px;
--ebk-category-separate-icon-font-size: 18px;
--ebk-transaction-date-width: 28px;
--ebk-transaction-day-font-size: 17px;
+2
View File
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 16px;
--ebk-trends-bar-chart-legend-text-font-size: 12px;
--ebk-account-list-group-title-height: 36px;
--ebk-synchronized-settings-list-group-title-height: 36px;
--ebk-synchronized-settings-list-device-icon-font-size: 18px;
--ebk-category-separate-icon-font-size: 18px;
--ebk-transaction-date-width: 25px;
--ebk-transaction-day-font-size: 16px;
+2
View File
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 20px;
--ebk-trends-bar-chart-legend-text-font-size: 16px;
--ebk-account-list-group-title-height: 38px;
--ebk-synchronized-settings-list-group-title-height: 38px;
--ebk-synchronized-settings-list-device-icon-font-size: 21px;
--ebk-category-separate-icon-font-size: 18px;
--ebk-transaction-date-width: 30px;
--ebk-transaction-day-font-size: 18px;
+2
View File
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 22px;
--ebk-trends-bar-chart-legend-text-font-size: 18px;
--ebk-account-list-group-title-height: 40px;
--ebk-synchronized-settings-list-group-title-height: 40px;
--ebk-synchronized-settings-list-device-icon-font-size: 22px;
--ebk-category-separate-icon-font-size: 20px;
--ebk-transaction-date-width: 32px;
--ebk-transaction-day-font-size: 20px;
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 24px;
--ebk-trends-bar-chart-legend-text-font-size: 20px;
--ebk-account-list-group-title-height: 42px;
--ebk-synchronized-settings-list-group-title-height: 42px;
--ebk-synchronized-settings-list-device-icon-font-size: 24px;
--ebk-category-separate-icon-font-size: 20px;
--ebk-transaction-date-width: 36px;
--ebk-transaction-day-font-size: 22px;
@@ -73,6 +73,8 @@
--ebk-trends-bar-chart-legend-icon-font-size: 26px;
--ebk-trends-bar-chart-legend-text-font-size: 22px;
--ebk-account-list-group-title-height: 44px;
--ebk-synchronized-settings-list-group-title-height: 44px;
--ebk-synchronized-settings-list-device-icon-font-size: 26px;
--ebk-category-separate-icon-font-size: 22px;
--ebk-transaction-date-width: 40px;
--ebk-transaction-day-font-size: 24px;
@@ -0,0 +1,226 @@
import { ref, computed } from 'vue';
import { useSettingsStore } from '@/stores/setting.ts';
import type { ApplicationCloudSetting } from '@/core/setting.ts';
export interface CategorizedApplicationCloudSettingItems {
readonly categoryName: string;
readonly categorySubName?: string;
readonly items: ApplicationCloudSettingItem[];
}
export interface ApplicationCloudSettingItem {
readonly settingKey: string;
readonly settingName: string;
readonly mobile: boolean;
readonly desktop: boolean;
}
export const ALL_APPLICATION_CLOUD_SETTINGS: CategorizedApplicationCloudSettingItems[] = [
{
categoryName: 'Basic Settings',
items: [
{ settingKey: 'showAccountBalance', settingName: 'Show Account Balance', mobile: true, desktop: true }
]
},
{
categoryName: 'Overview Page',
items: [
{ settingKey: 'showAmountInHomePage', settingName: 'Show Amount', mobile: true, desktop: true },
{ settingKey: 'timezoneUsedForStatisticsInHomePage', settingName: 'Timezone Used for Statistics', mobile: true, desktop: true }
]
},
{
categoryName: 'Transaction List Page',
items: [
{ settingKey: 'itemsCountInTransactionListPage', settingName: 'Transactions Per Page', mobile: false, desktop: true },
{ settingKey: 'showTotalAmountInTransactionListPage', settingName: 'Show Monthly Total Amount', mobile: true, desktop: true },
{ settingKey: 'showTagInTransactionListPage', settingName: 'Show Transaction Tag', mobile: true, desktop: true }
]
},
{
categoryName: 'Transaction Edit Page',
items: [
{ settingKey: 'autoSaveTransactionDraft', settingName: 'Automatically Save Draft', mobile: true, desktop: true },
{ settingKey: 'autoGetCurrentGeoLocation', settingName: 'Automatically Add Geolocation', mobile: true, desktop: true },
{ settingKey: 'alwaysShowTransactionPicturesInMobileTransactionEditPage', settingName: 'Always Show Transaction Pictures', mobile: true, desktop: false }
]
},
{
categoryName: 'Exchange Rates Data Page',
items: [
{ settingKey: 'currencySortByInExchangeRatesPage', settingName: 'Sort by', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Common Settings',
items: [
{ settingKey: 'statistics.defaultChartDataType', settingName: 'Default Chart Data Type', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultTimezoneType', settingName: 'Timezone Used for Date Range', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultAccountFilter', settingName: 'Default Account Filter', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultTransactionCategoryFilter', settingName: 'Default Transaction Category Filter', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultSortingType', settingName: 'Default Sort Order', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Categorical Analysis Settings',
items: [
{ settingKey: 'statistics.defaultCategoricalChartType', settingName: 'Default Chart Type', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultCategoricalChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Trend Analysis Settings',
items: [
{ settingKey: 'statistics.defaultTrendChartType', settingName: 'Default Chart Type', mobile: false, desktop: true },
{ settingKey: 'statistics.defaultTrendChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
}
];
export function useAppCloudSyncBase() {
const settingsStore = useSettingsStore();
const loading = ref<boolean>(false);
const enabling = ref<boolean>(false);
const disabling = ref<boolean>(false);
const enabledApplicationCloudSettings = ref<Record<string, boolean>>(Object.assign({}, settingsStore.syncedAppSettings));
const isEnableCloudSync = computed<boolean>(() => settingsStore.enableApplicationCloudSync);
const hasEnabledApplicationCloudSettings = computed<boolean>(() => {
for (const key in enabledApplicationCloudSettings.value) {
if (!Object.prototype.hasOwnProperty.call(enabledApplicationCloudSettings.value, key)) {
continue;
}
if (enabledApplicationCloudSettings.value[key]) {
return true;
}
}
return false;
});
const enabledApplicationCloudSettingKeys = computed<string[]>(() => {
const keys: string[] = [];
for (const key in enabledApplicationCloudSettings.value) {
if (!Object.prototype.hasOwnProperty.call(enabledApplicationCloudSettings.value, key)) {
continue;
}
if (enabledApplicationCloudSettings.value[key]) {
keys.push(key);
}
}
return keys;
});
function isAllSettingsSelected(categorizedItems: CategorizedApplicationCloudSettingItems): boolean {
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
if (!enabledApplicationCloudSettings.value[item.settingKey]) {
return false;
}
}
return true;
}
function hasSettingSelectedButNotAllChecked(categorizedItems: CategorizedApplicationCloudSettingItems): boolean {
let checkedCount = 0;
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
if (!enabledApplicationCloudSettings.value[item.settingKey]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < categorizedItems.items.length;
}
function updateSettingsSelected(categorizedItems: CategorizedApplicationCloudSettingItems, value: boolean): void {
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
enabledApplicationCloudSettings.value[item.settingKey] = value;
}
}
function selectAllSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = true;
}
}
}
function selectNoneSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = false;
}
}
}
function selectInvertSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = !enabledApplicationCloudSettings.value[item.settingKey];
}
}
}
function setUserApplicationCloudSettings(settings: ApplicationCloudSetting[] | false) {
if (settings && settings.length > 0) {
settingsStore.setApplicationSettingsFromCloudSettings(settings);
for (let i = 0; i < settings.length; i++) {
const setting = settings[i];
if (setting && setting.settingKey) {
enabledApplicationCloudSettings.value[setting.settingKey] = true;
}
}
} else {
settingsStore.setApplicationSettingsFromCloudSettings(undefined);
enabledApplicationCloudSettings.value = {};
}
}
return {
// constants
ALL_APPLICATION_CLOUD_SETTINGS,
// states
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
// computed states
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
// functions
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
};
}
+13 -2
View File
@@ -13,6 +13,10 @@
<v-icon size="20" start :icon="mdiChartPieOutline"/>
{{ tt('Statistics') }}
</v-tab>
<v-tab value="cloudSyncSetting" @click="pushRouter('cloudSyncSetting')">
<v-icon size="20" start :icon="mdiCloudOutline"/>
{{ tt('Settings Sync') }}
</v-tab>
</v-tabs>
<v-window class="mt-4 disable-tab-transition" v-model="activeTab">
@@ -27,6 +31,10 @@
<v-window-item value="statisticsSetting">
<app-statistics-setting-tab/>
</v-window-item>
<v-window-item value="cloudSyncSetting">
<app-cloud-sync-setting-tab/>
</v-window-item>
</v-window>
</div>
</template>
@@ -35,6 +43,7 @@
import AppBasicSettingTab from './settings/tabs/AppBasicSettingTab.vue';
import AppLockSettingTab from './settings/tabs/AppLockSettingTab.vue';
import AppStatisticsSettingTab from './settings/tabs/AppStatisticsSettingTab.vue';
import AppCloudSyncSettingTab from './settings/tabs/AppCloudSyncSettingTab.vue';
import { ref } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
@@ -44,7 +53,8 @@ import { useI18n } from '@/locales/helpers.ts';
import {
mdiCogOutline,
mdiLockOpenOutline,
mdiChartPieOutline
mdiChartPieOutline,
mdiCloudOutline
} from '@mdi/js';
const props = defineProps<{
@@ -58,7 +68,8 @@ const { tt } = useI18n();
const ALL_TABS: string[] = [
'basicSetting',
'applicationLockSetting',
'statisticsSetting'
'statisticsSetting',
'cloudSyncSetting'
];
const activeTab = ref<string>((() => {
@@ -0,0 +1,236 @@
<template>
<v-row>
<v-col cols="12">
<v-card>
<template #title>
<span>{{ tt('Settings Sync') }}</span>
<v-progress-circular indeterminate size="20" class="ml-3" v-if="loading"></v-progress-circular>
</template>
<v-card-text class="pb-0">
<v-skeleton-loader class="skeleton-no-margin pt-2 pb-5" type="text" style="width: 150px" :loading="true" v-if="loading"></v-skeleton-loader>
<p class="text-body-1" v-if="!loading && !isEnableCloudSync">
{{ tt('Settings sync is not enabled') }}
</p>
<p class="text-body-1" v-if="!loading && isEnableCloudSync">
{{ tt('Settings sync has been enabled') }}
</p>
</v-card-text>
<v-card-text>
<v-expansion-panels class="synchronized-settings" multiple
:readonly="true" :hide-actions="true"
:disabled="loading || enabling || disabling"
v-model="openedPanel">
<v-expansion-panel class="border" value="synchronizedSettings">
<v-expansion-panel-title class="expand-panel-title-with-bg py-0">
<div class="d-flex align-center justify-center w-100">
<div class="w-100">
<span>{{ tt('Synchronized Settings') }}</span>
</div>
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
:disabled="loading || enabling || disabling" :icon="true">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent">
<v-list>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelectAll"
:title="tt('Select All')"
@click="selectAllSettings"></v-list-item>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelect"
:title="tt('Select None')"
@click="selectNoneSettings"></v-list-item>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelectInverse"
:title="tt('Invert Selection')"
@click="selectInvertSettings"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-list rounded density="comfortable" class="pa-0">
<template :key="categorizedItems.categoryName"
v-for="(categorizedItems, categoryIdx) in ALL_APPLICATION_CLOUD_SETTINGS">
<v-divider v-if="categoryIdx > 0"/>
<v-list-item>
<template #prepend>
<v-checkbox :disabled="loading || enabling || disabling"
:model-value="isAllSettingsSelected(categorizedItems)"
:indeterminate="hasSettingSelectedButNotAllChecked(categorizedItems)"
@update:model-value="updateSettingsSelected(categorizedItems, !!$event)">
<template #label>
<span>{{ tt(categorizedItems.categoryName) }}</span>
<span class="mx-2" v-if="categorizedItems.categorySubName">/</span>
<span v-if="categorizedItems.categorySubName">{{ tt(categorizedItems.categorySubName) }}</span>
</template>
</v-checkbox>
</template>
</v-list-item>
<v-divider/>
<v-list rounded density="comfortable" class="pa-0 ml-4">
<template :key="settingItem.settingKey"
v-for="(settingItem, itemIdx) in categorizedItems.items">
<v-divider v-if="itemIdx > 0"/>
<v-list-item>
<template #prepend>
<v-checkbox :disabled="loading || enabling || disabling"
:model-value="enabledApplicationCloudSettings[settingItem.settingKey]"
@update:model-value="enabledApplicationCloudSettings[settingItem.settingKey] = !!$event">
<template #label>
<span>{{ tt(settingItem.settingName) }}</span>
<v-icon class="ml-2 mr-0" start size="16" :icon="mdiCellphone" v-if="settingItem.mobile"/>
<v-icon class="ml-2 mr-0" start size="16" :icon="mdiMonitor" v-if="settingItem.desktop"/>
</template>
</v-checkbox>
</template>
</v-list-item>
</template>
</v-list>
</template>
</v-list>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="12" class="d-flex flex-wrap gap-4">
<v-btn :disabled="loading || enabling || disabling || !hasEnabledApplicationCloudSettings" v-if="!isEnableCloudSync" @click="enable(false)">
{{ tt('Enable Settings Sync') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="enabling"></v-progress-circular>
</v-btn>
<v-btn :disabled="loading || enabling || disabling || !hasEnabledApplicationCloudSettings" v-if="isEnableCloudSync" @click="enable(true)">
{{ tt('Update Synchronized Settings') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="enabling"></v-progress-circular>
</v-btn>
<v-btn :disabled="loading || enabling || disabling" v-if="isEnableCloudSync" @click="disable">
{{ tt('Disable Settings Sync') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="disabling"></v-progress-circular>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
<snack-bar ref="snackbar" />
</template>
<script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useAppCloudSyncBase } from '@/views/base/settings/AppCloudSyncPageBase.ts';
import { useUserStore } from '@/stores/user.ts';
import {
mdiDotsVertical,
mdiSelectAll,
mdiSelect,
mdiSelectInverse,
mdiCellphone,
mdiMonitor,
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
const { tt } = useI18n();
const {
ALL_APPLICATION_CLOUD_SETTINGS,
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
} = useAppCloudSyncBase();
const userStore = useUserStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const openedPanel = ref<string[]>(['synchronizedSettings']);
function init(): void {
loading.value = true;
userStore.getUserApplicationCloudSettings().then(response => {
setUserApplicationCloudSettings(response);
loading.value = false;
}).catch(error => {
loading.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
function enable(update: boolean): void {
enabling.value = true;
userStore.fullUpdateUserApplicationCloudSettings(enabledApplicationCloudSettingKeys.value).then(() => {
enabling.value = false;
if (!update) {
snackbar.value?.showMessage('Settings sync has been enabled');
} else {
snackbar.value?.showMessage('Synchronized settings have been updated');
}
}).catch(error => {
enabling.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
function disable(): void {
disabling.value = true;
userStore.disableUserApplicationCloudSettings().then(() => {
enabledApplicationCloudSettings.value = {};
disabling.value = false;
snackbar.value?.showMessage('Settings sync has been disabled');
}).catch(error => {
disabling.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
init();
</script>
<style>
.synchronized-settings .v-expansion-panel-title {
cursor: inherit;
}
.synchronized-settings .v-expansion-panel-text__wrapper {
padding: 0 0 0 0;
}
</style>
+1 -1
View File
@@ -72,8 +72,8 @@
</f7-list-item>
<f7-list-item :title="tt('Page Settings')" link="/settings/page"></f7-list-item>
<f7-list-item :title="tt('Statistics Settings')" link="/statistic/settings"></f7-list-item>
<f7-list-item :title="tt('Settings Sync')" link="/settings/sync"></f7-list-item>
<f7-list-item>
<span>{{ tt('Enable Animation') }}</span>
@@ -0,0 +1,197 @@
<template>
<f7-page @page:afterin="onPageAfterIn">
<f7-navbar>
<f7-nav-left :back-link="tt('Back')"></f7-nav-left>
<f7-nav-title :title="tt('Settings Sync')"></f7-nav-title>
<f7-nav-right>
<f7-link icon-f7="ellipsis" :class="{ 'disabled': loading || enabling || disabling }" @click="showMoreActionSheet = true"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item title="Status" after="Unknown"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-item :title="tt('Status')" :after="tt(isEnableCloudSync ? 'Enabled' : 'Disabled')"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical synchronized-settings-list"
:class="{ 'disabled': loading || enabling || disabling }">
<f7-list-item group-title :sortable="false">
<small>{{ tt('Synchronized Settings') }}</small>
</f7-list-item>
<f7-list-item class="has-child-list-item" checkbox
:disabled="loading || enabling || disabling"
:title="tt(categorizedItems.categoryName)"
:value="categorizedItems.categoryName"
:checked="isAllSettingsSelected(categorizedItems)"
:indeterminate="hasSettingSelectedButNotAllChecked(categorizedItems)"
:key="categorizedItems.categoryName"
v-for="categorizedItems in ALL_APPLICATION_CLOUD_SETTINGS"
@change="updateSettingsSelected(categorizedItems, $event.target.checked)">
<template #root>
<ul class="padding-left">
<f7-list-item checkbox
:disabled="loading || enabling || disabling"
:title="tt(settingItem.settingName)"
:value="settingItem.settingKey"
:checked="enabledApplicationCloudSettings[settingItem.settingKey]"
:key="settingItem.settingKey"
v-for="settingItem in categorizedItems.items"
@change="enabledApplicationCloudSettings[settingItem.settingKey] = $event.target.checked">
<template #after>
<f7-icon class="synchronized-settings-device-icon" f7="device_phone_portrait" v-if="settingItem.mobile"></f7-icon>
<f7-icon class="synchronized-settings-device-icon" f7="device_desktop" v-if="settingItem.desktop"></f7-icon>
</template>
</f7-list-item>
</ul>
</template>
</f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-button class="disabled">Operate</f7-list-button>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-button :class="{ 'disabled': loading || enabling || disabling || !hasEnabledApplicationCloudSettings }"
v-if="!isEnableCloudSync"
@click="enable(false)">{{ tt('Enable Settings Sync') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': loading || enabling || disabling || !hasEnabledApplicationCloudSettings }"
v-if="isEnableCloudSync"
@click="enable(true)">{{ tt('Update Synchronized Settings') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': loading || enabling || disabling }"
v-if="isEnableCloudSync"
@click="disable">{{ tt('Disable') }}</f7-list-button>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectAllSettings">{{ tt('Select All') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectNoneSettings">{{ tt('Select None') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectInvertSettings">{{ tt('Invert Selection') }}</f7-actions-button>
</f7-actions-group>
<f7-actions-group>
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
</f7-actions-group>
</f7-actions>
</f7-page>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { Router } from 'framework7/types';
import { useI18n } from '@/locales/helpers.ts';
import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
import { useAppCloudSyncBase } from '@/views/base/settings/AppCloudSyncPageBase.ts';
import { useUserStore } from '@/stores/user.ts';
const props = defineProps<{
f7router: Router.Router;
}>();
const { tt } = useI18n();
const { showToast, routeBackOnError } = useI18nUIComponents();
const {
ALL_APPLICATION_CLOUD_SETTINGS,
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
} = useAppCloudSyncBase();
const userStore = useUserStore();
const loadingError = ref<unknown | null>(null);
const showMoreActionSheet = ref<boolean>(false);
function init(): void {
loading.value = true;
userStore.getUserApplicationCloudSettings().then(response => {
setUserApplicationCloudSettings(response);
loading.value = false;
}).catch(error => {
if (error.processed) {
loading.value = false;
} else {
loadingError.value = error;
showToast(error.message || error);
}
});
}
function enable(update: boolean): void {
enabling.value = true;
showLoading(() => enabling.value);
userStore.fullUpdateUserApplicationCloudSettings(enabledApplicationCloudSettingKeys.value).then(() => {
enabling.value = false;
hideLoading();
if (!update) {
showToast('Settings sync has been enabled');
} else {
showToast('Synchronized settings have been updated');
}
}).catch(error => {
enabling.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function disable(): void {
disabling.value = true;
showLoading(() => disabling.value);
userStore.disableUserApplicationCloudSettings().then(() => {
enabledApplicationCloudSettings.value = {};
disabling.value = false;
hideLoading();
showToast('Settings sync has been disabled');
}).catch(error => {
disabling.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function onPageAfterIn(): void {
routeBackOnError(props.f7router, loadingError);
}
init();
</script>
<style>
.synchronized-settings-list {
--f7-list-group-title-height: var(--ebk-synchronized-settings-list-group-title-height);
}
.synchronized-settings-device-icon {
font-size: var(--ebk-synchronized-settings-list-device-icon-font-size);
}
</style>