From 123c3c979a391e445fae2a384a61196072e6a5bc Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 12 Jun 2022 12:23:17 +0200 Subject: [PATCH] #696 - save personalization settings --- .../settings/SettingsController.java | 55 ++++++++++++---- .../PersonalizationSettingsContainer.java | 65 +++++++++++++++++++ .../containers/SecuritySettingsContainer.java | 21 +----- .../resources/languages/base_de.properties | 2 + .../resources/languages/base_en.properties | 2 + .../resources/static/js/settingsContainers.js | 3 + .../containers/settingsPersonalization.ftl | 65 +++++++++++++++++++ .../resources/templates/settings/settings.ftl | 56 ++-------------- 8 files changed, 184 insertions(+), 85 deletions(-) create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/PersonalizationSettingsContainer.java create mode 100644 BudgetMasterServer/src/main/resources/templates/settings/containers/settingsPersonalization.ftl diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java index ffab9b429..2fa7b1975 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java @@ -2,7 +2,6 @@ package de.deadlocker8.budgetmaster.settings; import com.google.gson.JsonObject; import de.deadlocker8.budgetmaster.Build; -import de.deadlocker8.budgetmaster.accounts.AccountService; import de.deadlocker8.budgetmaster.backup.*; import de.deadlocker8.budgetmaster.categories.CategoryService; import de.deadlocker8.budgetmaster.controller.BaseController; @@ -12,9 +11,9 @@ import de.deadlocker8.budgetmaster.database.InternalDatabase; import de.deadlocker8.budgetmaster.database.model.BackupDatabase; import de.deadlocker8.budgetmaster.services.ImportResultItem; import de.deadlocker8.budgetmaster.services.ImportService; +import de.deadlocker8.budgetmaster.settings.containers.PersonalizationSettingsContainer; import de.deadlocker8.budgetmaster.settings.containers.SecuritySettingsContainer; import de.deadlocker8.budgetmaster.update.BudgetMasterUpdateService; -import de.deadlocker8.budgetmaster.utils.LanguageType; import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; @@ -79,6 +78,7 @@ public class SettingsController extends BaseController public static final String IMPORT_DATABASE_STEP_1 = "settings/importStepOne"; public static final String IMPORT_DATABASE_RESULT = "settings/importResult"; public static final String CONTAINER_SECURITY = "settings/containers/settingsSecurity"; + public static final String CONTAINER_PERSONALIZATION = "settings/containers/settingsPersonalization"; } private static class RequestAttributeNames @@ -139,7 +139,7 @@ public class SettingsController extends BaseController return ReturnValues.CONTAINER_SECURITY; } - final String password = securitySettingsContainer.getPassword(); + final String password = securitySettingsContainer.password(); if(password.equals(PASSWORD_PLACEHOLDER)) { final JsonObject toastContent = getToastContent("notification.settings.security.warning", NotificationType.WARNING); @@ -154,6 +154,45 @@ public class SettingsController extends BaseController return ReturnValues.CONTAINER_SECURITY; } + @PostMapping(value = "/save/personalization") + public String saveContainerPersonalization(Model model, + @ModelAttribute("PersonalizationSettingsContainer") PersonalizationSettingsContainer personalizationSettingsContainer, + BindingResult bindingResult) + { + personalizationSettingsContainer.fixBooleans(); + + final Settings settings = settingsService.getSettings(); + + if(bindingResult.hasErrors()) + { + model.addAttribute(ModelAttributes.ERROR, bindingResult); + + final JsonObject toastContent = getToastContent("notification.settings.personalization.error", NotificationType.ERROR); + model.addAttribute(ModelAttributes.TOAST_CONTENT, toastContent); + model.addAttribute(ModelAttributes.SETTINGS, settings); + model.addAttribute(ModelAttributes.SEARCH_RESULTS_PER_PAGE, SEARCH_RESULTS_PER_PAGE_OPTIONS); + return ReturnValues.CONTAINER_PERSONALIZATION; + } + + // update settings + settings.setLanguage(personalizationSettingsContainer.getLanguageType()); + settings.setCurrency(personalizationSettingsContainer.currency()); + settings.setUseDarkTheme(personalizationSettingsContainer.useDarkTheme()); + settings.setShowCategoriesAsCircles(personalizationSettingsContainer.showCategoriesAsCircles()); + settings.setSearchItemsPerPage(personalizationSettingsContainer.searchItemsPerPage()); + settingsService.updateSettings(settings); + + // reload localization + Localization.load(); + categoryService.localizeDefaultCategories(); + + final JsonObject toastContent = getToastContent("notification.settings.personalization.saved", NotificationType.SUCCESS); + model.addAttribute(ModelAttributes.TOAST_CONTENT, toastContent); + model.addAttribute(ModelAttributes.SETTINGS, settings); + model.addAttribute(ModelAttributes.SEARCH_RESULTS_PER_PAGE, SEARCH_RESULTS_PER_PAGE_OPTIONS); + return ReturnValues.CONTAINER_PERSONALIZATION; + } + private JsonObject getToastContent(String localizationKey, NotificationType notificationType) { final JsonObject toastContent = new JsonObject(); @@ -170,11 +209,9 @@ public class SettingsController extends BaseController @PostMapping(value = "/save") public String post(WebRequest request, Model model, @ModelAttribute("Settings") Settings settings, BindingResult bindingResult, - @RequestParam(value = "languageType") String languageType, @RequestParam(value = "autoBackupStrategyType", required = false) String autoBackupStrategyType, @RequestParam(value = "runBackup", required = false) Boolean runBackup) { - settings.setLanguage(LanguageType.fromName(languageType)); if(autoBackupStrategyType == null) { settings.setAutoBackupStrategy(AutoBackupStrategy.NONE); @@ -253,11 +290,6 @@ public class SettingsController extends BaseController settings.setAutoBackupGitUserName(defaultSettings.getAutoBackupGitUserName()); settings.setAutoBackupGitToken(defaultSettings.getAutoBackupGitToken()); } - - if(settings.getShowCategoriesAsCircles() == null) - { - settings.setShowCategoriesAsCircles(false); - } } public void updateSettings(Settings settings) @@ -275,9 +307,6 @@ public class SettingsController extends BaseController final String cron = backupService.computeCron(settings.getAutoBackupTime(), settings.getAutoBackupDays()); backupTaskOptional.ifPresent(runnable -> backupService.startBackupCron(cron, runnable)); } - - Localization.load(); - categoryService.localizeDefaultCategories(); } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/PersonalizationSettingsContainer.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/PersonalizationSettingsContainer.java new file mode 100644 index 000000000..722be8225 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/PersonalizationSettingsContainer.java @@ -0,0 +1,65 @@ +package de.deadlocker8.budgetmaster.settings.containers; + +import de.deadlocker8.budgetmaster.utils.LanguageType; + +public final class PersonalizationSettingsContainer +{ + private final String language; + private final String currency; + private Boolean useDarkTheme; + private Boolean showCategoriesAsCircles; + private final Integer searchItemsPerPage; + + public PersonalizationSettingsContainer(String language, String currency, Boolean useDarkTheme, + Boolean showCategoriesAsCircles, Integer searchItemsPerPage) + { + this.language = language; + this.currency = currency; + this.useDarkTheme = useDarkTheme; + this.showCategoriesAsCircles = showCategoriesAsCircles; + this.searchItemsPerPage = searchItemsPerPage; + } + + public LanguageType getLanguageType() + { + return LanguageType.fromName(language); + } + + public String language() + { + return language; + } + + public String currency() + { + return currency; + } + + public Boolean useDarkTheme() + { + return useDarkTheme; + } + + public Boolean showCategoriesAsCircles() + { + return showCategoriesAsCircles; + } + + public Integer searchItemsPerPage() + { + return searchItemsPerPage; + } + + public void fixBooleans() + { + if(useDarkTheme == null) + { + useDarkTheme = false; + } + + if(showCategoriesAsCircles == null) + { + showCategoriesAsCircles = false; + } + } +} diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java index 308fdbf81..85f14e0a8 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java @@ -5,27 +5,8 @@ import org.springframework.validation.FieldError; import java.util.Optional; -public class SecuritySettingsContainer +public record SecuritySettingsContainer(String password, String passwordConfirmation) { - private final String password; - private final String passwordConfirmation; - - public SecuritySettingsContainer(String password, String passwordConfirmation) - { - this.password = password; - this.passwordConfirmation = passwordConfirmation; - } - - public String getPassword() - { - return password; - } - - public String getPasswordConfirmation() - { - return passwordConfirmation; - } - public Optional<FieldError> validate() { if(password == null || password.equals("")) diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index 974fd17c8..fda2f2b71 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -171,6 +171,8 @@ notification.settings.saved=Einstellungen gespeichert notification.settings.security.saved=Passwort gespeichert notification.settings.security.warning=Passwort entspricht bereits gespeicherten Passwort notification.settings.security.error=Fehler beim Speichern des Passworts +notification.settings.personalization.saved=Personalisierung gespeichert +notification.settings.personalization.error=Fehler beim Speichern der Personalisierung notification.settings.update.available=BudgetMaster Update "{0}" verfügbar notification.settings.database.delete.success=Datenbank erfolgreich gelöscht notification.settings.backup.run.success=Backup erfolgreich diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index aba7f6bf9..ddb208bb4 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -172,6 +172,8 @@ notification.settings.saved=Settings saved notification.settings.security.saved=Password saved notification.settings.security.warning=Password equals already saved password notification.settings.security.error=Error saving password +notification.settings.personalization.saved=Personalization settings saved +notification.settings.personalization.error=Error saving personalization settings notification.settings.update.available=BudgetMaster update "{0}" available notification.settings.database.delete.success=Successfully deleted database notification.settings.backup.run.success=Backup successful diff --git a/BudgetMasterServer/src/main/resources/static/js/settingsContainers.js b/BudgetMasterServer/src/main/resources/static/js/settingsContainers.js index 164c146d7..1dd8cbf45 100644 --- a/BudgetMasterServer/src/main/resources/static/js/settingsContainers.js +++ b/BudgetMasterServer/src/main/resources/static/js/settingsContainers.js @@ -13,7 +13,10 @@ function initSettingsContainer(formName, containerId) success: function(response) { $('#' + containerId).html(response); + + // re-init materialize components $('.tooltipped').tooltip(); + $('select').formSelect(); let toastContent = document.querySelector('#' + containerId + ' .securityContainerToastContent').innerHTML.trim(); if(toastContent) diff --git a/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsPersonalization.ftl b/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsPersonalization.ftl new file mode 100644 index 000000000..5d930720a --- /dev/null +++ b/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsPersonalization.ftl @@ -0,0 +1,65 @@ +<#import "/spring.ftl" as s> +<#import "../../helpers/validation.ftl" as validation> +<#import "../../helpers/header.ftl" as header> +<@header.globals/> + +<#import "settingsContainer.ftl" as settingsContainerMacros> +<#import "../settingsMacros.ftl" as settingsMacros> + +<#macro personalizationSettingsContainer settings> + <@settingsContainerMacros.settingsContainer 'PersonalizationSettingsContainer' 'personalizationSettingsContainer'> + <#-- language --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">translate</i> + <select id="settings-language" name="language" <@validation.validation "language"/>> + <#list helpers.getAvailableLanguages() as language> + <#if settings.getLanguage() == language> + <option selected value="${language.getName()}">${language.getName()}</option> + <#else> + <option value="${language.getName()}">${language.getName()}</option> + </#if> + </#list> + </select> + <label for="settings-language">${locale.getString("settings.language")}</label> + </div> + </div> + + <#-- currency --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">euro</i> + <input id="settings-currency" type="text" name="currency" <@validation.validation "currency"/> value="<#if settings.getCurrency()??>${settings.getCurrency()}</#if>"> + <label for="settings-currency">${locale.getString("settings.currency")}</label> + </div> + </div> + + <#-- dark theme and category style --> + <@settingsMacros.switches settings/> + + <#-- search items per page --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">search</i> + <select id="settings-search-items-per-page" name="searchItemsPerPage" <@validation.validation "searchItemsPerPage"/>> + <#list searchResultsPerPageOptions as number> + <#if settings.getSearchItemsPerPage() == number> + <option selected value="${number}">${number}</option> + <#else> + <option value="${number}">${number}</option> + </#if> + </#list> + </select> + <label for="settings-search-items-per-page">${locale.getString("settings.search.itemsPerPage")}</label> + </div> + </div> + + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green' formaction='/settings/save/personalization'/> + </div> + </div> + </@settingsContainerMacros.settingsContainer> +</#macro> + +<@personalizationSettingsContainer settings/> \ No newline at end of file diff --git a/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl b/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl index d2d9605cd..667380853 100644 --- a/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl +++ b/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl @@ -15,6 +15,7 @@ <#import "settingsMacros.ftl" as settingsMacros> <#import "containers/settingsSecurity.ftl" as settingsSecurityMacros> + <#import "containers/settingsPersonalization.ftl" as settingsPersonalizationMacros> <main> @@ -35,58 +36,8 @@ <@settingsSecurityMacros.securitySettingsContainer/> </@settingsMacros.settingsCollapsibleItem> - <@settingsMacros.settingsCollapsibleItem "" "format_paint" locale.getString("settings.appearance")> - <#-- language --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">translate</i> - <select id="settings-language" name="languageType" <@validation.validation "language"/>> - <#list helpers.getAvailableLanguages() as language> - <#if settings.getLanguage() == language> - <option selected value="${language.getName()}">${language.getName()}</option> - <#else> - <option value="${language.getName()}">${language.getName()}</option> - </#if> - </#list> - </select> - <label for="settings-language">${locale.getString("settings.language")}</label> - </div> - </div> - - <#-- currency --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">euro</i> - <input id="settings-currency" type="text" name="currency" <@validation.validation "currency"/> value="<#if settings.getCurrency()??>${settings.getCurrency()}</#if>"> - <label for="settings-currency">${locale.getString("settings.currency")}</label> - </div> - </div> - - <#-- rest, dark theme and category style --> - <@settingsMacros.switches settings/> - - <#-- search items per page --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">search</i> - <select id="settings-search-items-per-page" name="searchItemsPerPage" <@validation.validation "searchItemsPerPage"/>> - <#list searchResultsPerPageOptions as number> - <#if settings.getSearchItemsPerPage() == number> - <option selected value="${number}">${number}</option> - <#else> - <option value="${number}">${number}</option> - </#if> - </#list> - </select> - <label for="settings-search-items-per-page">${locale.getString("settings.search.itemsPerPage")}</label> - </div> - </div> - - <div class="row"> - <div class="col s12 center-align"> - <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> - </div> - </div> + <@settingsMacros.settingsCollapsibleItem "personalizationSettingsContainer" "format_paint" locale.getString("settings.appearance")> + <@settingsPersonalizationMacros.personalizationSettingsContainer settings/> </@settingsMacros.settingsCollapsibleItem> <@settingsMacros.settingsCollapsibleItem "" "list" "Transactions"> @@ -290,6 +241,7 @@ <script> initSettingsContainer('SecuritySettingsContainer', 'securitySettingsContainer'); + initSettingsContainer('PersonalizationSettingsContainer', 'personalizationSettingsContainer'); </script> </@header.body> </html> -- GitLab