sync application settings
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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),
|
||||
NotificationContent: a.GetAfterLoginNotificationContent(user.Language, c.GetClientLocale()),
|
||||
Token: token,
|
||||
Need2FA: need2FA,
|
||||
User: a.GetUserBasicInfo(user),
|
||||
ApplicationCloudSettings: applicationCloudSettings,
|
||||
NotificationContent: a.GetAfterLoginNotificationContent(user.Language, c.GetClientLocale()),
|
||||
}
|
||||
}
|
||||
|
||||
+32
-10
@@ -18,8 +18,9 @@ import (
|
||||
type TokensApi struct {
|
||||
ApiUsingConfig
|
||||
ApiWithUserInfo
|
||||
tokens *services.TokenService
|
||||
users *services.UserService
|
||||
tokens *services.TokenService
|
||||
users *services.UserService
|
||||
userAppCloudSettings *services.UserApplicationCloudSettingsService
|
||||
}
|
||||
|
||||
// Initialize a token api singleton instance
|
||||
@@ -36,8 +37,9 @@ var (
|
||||
container: avatars.Container,
|
||||
},
|
||||
},
|
||||
tokens: services.Tokens,
|
||||
users: services.Users,
|
||||
tokens: services.Tokens,
|
||||
users: services.Users,
|
||||
userAppCloudSettings: services.UserApplicationCloudSettings,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -251,9 +253,19 @@ 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),
|
||||
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||
User: a.GetUserBasicInfo(user),
|
||||
ApplicationCloudSettings: applicationCloudSettingSlice,
|
||||
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||
}
|
||||
|
||||
return refreshResp, nil
|
||||
@@ -276,13 +288,23 @@ 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),
|
||||
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||
NewToken: token,
|
||||
OldTokenId: a.tokens.GenerateTokenId(oldTokenRecord),
|
||||
User: a.GetUserBasicInfo(user),
|
||||
ApplicationCloudSettings: applicationCloudSettingSlice,
|
||||
NotificationContent: a.GetAfterOpenNotificationContent(user.Language, c.GetClientLocale()),
|
||||
}
|
||||
|
||||
return refreshResp, nil
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,10 +2,11 @@ package models
|
||||
|
||||
// AuthResponse returns a view-object of user authorization
|
||||
type AuthResponse struct {
|
||||
Token string `json:"token"`
|
||||
Need2FA bool `json:"need2FA"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
NotificationContent string `json:"notificationContent,omitempty"`
|
||||
Token string `json:"token"`
|
||||
Need2FA bool `json:"need2FA"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
ApplicationCloudSettings *ApplicationCloudSettingSlice `json:"applicationCloudSettings,omitempty"`
|
||||
NotificationContent string `json:"notificationContent,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterResponse returns a view-object of user register response
|
||||
|
||||
@@ -24,10 +24,11 @@ type TokenRevokeRequest struct {
|
||||
|
||||
// TokenRefreshResponse represents all parameters of token refreshing request
|
||||
type TokenRefreshResponse struct {
|
||||
NewToken string `json:"newToken,omitempty"`
|
||||
OldTokenId string `json:"oldTokenId,omitempty"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
NotificationContent string `json:"notificationContent,omitempty"`
|
||||
NewToken string `json:"newToken,omitempty"`
|
||||
OldTokenId string `json:"oldTokenId,omitempty"`
|
||||
User *UserBasicInfo `json:"user"`
|
||||
ApplicationCloudSettings *ApplicationCloudSettingSlice `json:"applicationCloudSettings,omitempty"`
|
||||
NotificationContent string `json:"notificationContent,omitempty"`
|
||||
}
|
||||
|
||||
// TokenInfoResponse represents a view-object of token
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "用户自定义",
|
||||
|
||||
@@ -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": "使用者自訂",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { ApplicationCloudSetting } from '@/core/setting.ts';
|
||||
|
||||
export interface UserApplicationCloudSettingsUpdateRequest {
|
||||
readonly settings: ApplicationCloudSetting[];
|
||||
readonly fullUpdate: boolean;
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user