From eaae33fb7147accdcc126aa54a16f640f39cfcab Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 14 Mar 2021 17:31:07 +0100 Subject: [PATCH] #419 - add upload inputs to modal + load available image async from server --- .../budgetmaster/images/MediaController.java | 39 ++++++++++++++ .../budgetmaster/utils/Mappings.java | 2 +- .../resources/languages/base_de.properties | 2 + .../resources/languages/base_en.properties | 2 + src/main/resources/static/css/accounts.css | 4 ++ src/main/resources/static/js/accounts.js | 53 ++++++++++++++----- .../templates/accounts/accountFunctions.ftl | 46 +++++++++++----- .../templates/accounts/availableImages.ftl | 11 ++++ .../templates/accounts/newAccount.ftl | 2 +- 9 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java create mode 100644 src/main/resources/templates/accounts/availableImages.ftl diff --git a/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java b/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java new file mode 100644 index 000000000..f3db7d452 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java @@ -0,0 +1,39 @@ +package de.deadlocker8.budgetmaster.images; + +import de.deadlocker8.budgetmaster.controller.BaseController; +import de.deadlocker8.budgetmaster.utils.Mappings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +@Controller +@RequestMapping(Mappings.MEDIA) +public class MediaController extends BaseController +{ + private final ImageService imageService; + + @Autowired + public MediaController(ImageService imageService) + { + this.imageService = imageService; + } + + @GetMapping("/getAvailableImages") + public String getAvailableImages(Model model) + { + model.addAttribute("availableImages", imageService.getRepository().findAll()); + return "accounts/availableImages"; + } + + @PostMapping("uploadImage") + public String handleImagePost(@RequestParam("file") MultipartFile file) + { + imageService.saveImageFile(file); + return "redirect:/accounts"; + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java index d1b66d84b..10985ab6a 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java @@ -14,7 +14,7 @@ public final class Mappings public static final String ERROR = "/error"; public static final String FILTER = "/filter"; public static final String HOTKEYS = "/hotkeys"; - public static final String IMAGES = "/media"; + public static final String MEDIA = "/media"; public static final String LOGIN = "/login"; public static final String REPORTS = "/reports"; public static final String SEARCH = "/search"; diff --git a/src/main/resources/languages/base_de.properties b/src/main/resources/languages/base_de.properties index 140ca2593..374f16f31 100644 --- a/src/main/resources/languages/base_de.properties +++ b/src/main/resources/languages/base_de.properties @@ -262,6 +262,8 @@ settings.search.itemsPerPage=Anzahl der Suchergebnisse pro Seite account.new.label.name=Name account.new.label.icon=Icon account.new.icon.placeholder=Hier klicken um ein Icon auszuwählen +account.new.icon.upload.choose.file=Datei auswählen +account.new.icon.upload=Hochladen account.default.name=Standardkonto account.all=Alle Konten account.budget.asof=Stand diff --git a/src/main/resources/languages/base_en.properties b/src/main/resources/languages/base_en.properties index 9bff555fd..48e7dfb92 100644 --- a/src/main/resources/languages/base_en.properties +++ b/src/main/resources/languages/base_en.properties @@ -262,6 +262,8 @@ settings.search.itemsPerPage=Number of search results per page account.new.label.name=Name account.new.label.icon=Icon account.new.icon.placeholder=Click here to select an icon +account.new.icon.upload.choose.file=Choose file +account.new.icon.upload=Upload account.default.name=Default Account account.all=All Accounts account.budget.asof=as of diff --git a/src/main/resources/static/css/accounts.css b/src/main/resources/static/css/accounts.css index 6d352cc32..c4ca1f394 100644 --- a/src/main/resources/static/css/accounts.css +++ b/src/main/resources/static/css/accounts.css @@ -39,3 +39,7 @@ .account-icon-preview-icon { height: 5rem; } + +#account-icon-preview:hover { + cursor: pointer; +} diff --git a/src/main/resources/static/js/accounts.js b/src/main/resources/static/js/accounts.js index 966e6ba28..150f2e8a7 100644 --- a/src/main/resources/static/js/accounts.js +++ b/src/main/resources/static/js/accounts.js @@ -41,18 +41,6 @@ $(document).ready(function() document.getElementById("hidden-input-account-icon").value = iconId; }); - // select an icon option - $('.account-icon-option').click(function() - { - let allIconOptions = document.querySelectorAll('.account-icon-option'); - for(let i = 0; i < allIconOptions.length; i++) - { - allIconOptions[i].classList.remove('selected'); - } - - this.classList.add('selected'); - }); - if($('#modalAccountIconSelect').length) { let modalAccountIconSelect = document.getElementById('modalAccountIconSelect'); @@ -63,4 +51,43 @@ $(document).ready(function() } }); } -}); \ No newline at end of file + + $('#account-icon-preview').click(function() + { + openSelectAccountIconModal(this); + }); +}); + +function openSelectAccountIconModal(item) +{ + $.ajax({ + type: 'GET', + url: $(item).attr('data-url'), + data: {}, + success: function(data) + { + let modalID = '#modalAccountIconSelect'; + $('#available-images').html(data); + + // select an icon option + $('.account-icon-option').click(function() + { + selectIcon(this); + }); + + $(modalID).modal(); + $(modalID).modal('open'); + } + }); +} + +function selectIcon(item) +{ + let allIconOptions = document.querySelectorAll('.account-icon-option'); + for(let i = 0; i < allIconOptions.length; i++) + { + allIconOptions[i].classList.remove('selected'); + } + + item.classList.add('selected'); +} \ No newline at end of file diff --git a/src/main/resources/templates/accounts/accountFunctions.ftl b/src/main/resources/templates/accounts/accountFunctions.ftl index a3ff14c09..ba1afc9e1 100644 --- a/src/main/resources/templates/accounts/accountFunctions.ftl +++ b/src/main/resources/templates/accounts/accountFunctions.ftl @@ -2,11 +2,41 @@ <#macro modalAccountIconSelect> <div id="modalAccountIconSelect" class="modal modal-fixed-footer background-color"> - <div class="modal-content"> + <div class="modal-content center-align"> <div class="row"> - <#list availableImages as image> - <@accountIconOption image/> - </#list> + <div class="col s12"> + <div class="headline">Upload image</div> + </div> + </div> + + <div class="row"> + <form id="form-upload-account-image" method="POST" action="/media" enctype="multipart/form-data"> + <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> + <div class="file-field input-field col s12"> + <div class="container"> + <div class="btn background-blue"> + <i class="material-icons left">folder</i> + ${locale.getString("account.new.icon.upload.choose.file")} + <input id="inputUploadFile" type="file" accept="image/png, image/jpeg" name="file"> + </div> + <div class="file-path-wrapper" id="abc"> + <input id="def" class="file-path validate" type="text"> + </div> + <@header.buttonSubmit name='action' icon='upload' localizationKey='account.new.icon.upload' classes="right"/> + </div> + </div> + </form> + </div> + + <hr> + + <div class="row"> + <div class="col s12"> + <div class="headline">Available images</div> + </div> + </div> + + <div class="row" id="available-images"> </div> </div> <div class="modal-footer background-color"> @@ -15,11 +45,3 @@ </div> </div> </#macro> - -<#macro accountIconOption image> - <div class="col s4 m2 l2 account-icon-option-column"> - <div class="account-icon-option"> - <img src="${image.getBase64EncodedImage()}" class="account-icon-preview" data-image-id="${image.getID()}"/> - </div> - </div> -</#macro> \ No newline at end of file diff --git a/src/main/resources/templates/accounts/availableImages.ftl b/src/main/resources/templates/accounts/availableImages.ftl new file mode 100644 index 000000000..504abbfbc --- /dev/null +++ b/src/main/resources/templates/accounts/availableImages.ftl @@ -0,0 +1,11 @@ +<#list availableImages as image> + <@imageOption image 'account-icon'/> +</#list> + +<#macro imageOption image classPrefix> + <div class="col s4 m2 l2 ${classPrefix}-option-column"> + <div class="${classPrefix}-option"> + <img src="${image.getBase64EncodedImage()}" class="${classPrefix}-preview" data-image-id="${image.getID()}"/> + </div> + </div> +</#macro> \ No newline at end of file diff --git a/src/main/resources/templates/accounts/newAccount.ftl b/src/main/resources/templates/accounts/newAccount.ftl index 674668fd6..fd38c5124 100644 --- a/src/main/resources/templates/accounts/newAccount.ftl +++ b/src/main/resources/templates/accounts/newAccount.ftl @@ -53,7 +53,7 @@ <label class="input-label" for="account-icon">${locale.getString("account.new.label.icon")}</label> <div id="account-icon" class="valign-wrapper"> - <a href="#modalAccountIconSelect" id="account-icon-preview" class="modal-trigger"> + <a id="account-icon-preview" data-url="<@s.url '/media/getAvailableImages'/>"> <img id="account-icon-preview-icon" src="<#if account.getIcon()??>${account.getIcon().getBase64EncodedImage()}</#if>" class="account-icon-preview <#if account.getIcon()?? == false>hidden</#if>"/> <div id="account-icon-placeholder" class="<#if account.getIcon()??>hidden</#if>">${locale.getString("account.new.icon.placeholder")}</div> </a> -- GitLab