diff --git a/src/main/java/de/deadlocker8/budgetmaster/images/ImageService.java b/src/main/java/de/deadlocker8/budgetmaster/images/ImageService.java index cd364e3e69e49b338ef76e51da5da0eb15d382c6..6dba1b71474ef7b1dd976684c19c9356991c04b1 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/images/ImageService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/images/ImageService.java @@ -5,6 +5,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; @Service public class ImageService implements Resetable @@ -46,6 +51,32 @@ public class ImageService implements Resetable // setAsDefaultAccount(account.getID()); // LOGGER.debug("Created default account"); } + } + + @Transactional + public void saveImageFile(MultipartFile file) throws IOException + { + Byte[] byteObjects = new Byte[file.getBytes().length]; + + int i = 0; + for(byte b : file.getBytes()) + { + byteObjects[i++] = b; + } + + final Optional<String> fileExtensionOptional = getFileExtension(file.getOriginalFilename()); + if(fileExtensionOptional.isEmpty()) + { + throw new IllegalArgumentException("Could not determine file extension from file name: " + file.getOriginalFilename()); + } + + final Image image = new Image(byteObjects, fileExtensionOptional.get()); + imageRepository.save(image); + } + private Optional<String> getFileExtension(String filename) { + return Optional.ofNullable(filename) + .filter(f -> f.contains(".")) + .map(f -> f.substring(filename.lastIndexOf(".") + 1)); } } diff --git a/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java b/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java index f3db7d452eb91466975cae9613545c3d71081682..41fb1c6014ae4a4fe34cbe33bede87ee24b0dd6b 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/images/MediaController.java @@ -1,16 +1,17 @@ package de.deadlocker8.budgetmaster.images; +import com.google.gson.JsonObject; import de.deadlocker8.budgetmaster.controller.BaseController; import de.deadlocker8.budgetmaster.utils.Mappings; +import de.thecodelabs.utils.util.Localization; 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.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @Controller @RequestMapping(Mappings.MEDIA) public class MediaController extends BaseController @@ -31,9 +32,27 @@ public class MediaController extends BaseController } @PostMapping("uploadImage") - public String handleImagePost(@RequestParam("file") MultipartFile file) + @ResponseBody + public String uploadImage(@RequestParam("file") MultipartFile file) { - imageService.saveImageFile(file); - return "redirect:/accounts"; + boolean success = true; + String localizedMessage = Localization.getString("upload.image.success"); + + try + { + imageService.saveImageFile(file); + } + catch(IOException e) + { + e.printStackTrace(); + success = false; + localizedMessage = Localization.getString("upload.image.error", e.getMessage()); + } + + final JsonObject data = new JsonObject(); + data.addProperty("isUploadSuccessful", success); + data.addProperty("localizedMessage", localizedMessage); + + return data.toString(); } } diff --git a/src/main/resources/languages/base_de.properties b/src/main/resources/languages/base_de.properties index 374f16f31164f7076bbf93101a212fdcf1312f10..e44b9ba9101ef9af515cf508be56e2eeeca0e855 100644 --- a/src/main/resources/languages/base_de.properties +++ b/src/main/resources/languages/base_de.properties @@ -139,6 +139,8 @@ notification.settings.saved=Einstellungen gespeichert notification.settings.update.available=BudgetMaster Update "{0}" verfügbar notification.settings.database.delete.success=Datenbank erfolgreich gelöscht notification.settings.database.import.success=Import erfolgreich: {0} Konten, {1} Buchungen, {2} Kategorien, {3} Vorlagen und {4} Diagramme +upload.image.success=Erfolgreich hochgeladen +upload.image.error=Fehler: {0} # WARNING warning.text.account.delete=Das Konto "{0}" kann nicht gelöscht werden, da mindestens ein Konto existieren muss. Um dieses Konto zu löschen musst du zuerst ein neues anlegen. diff --git a/src/main/resources/languages/base_en.properties b/src/main/resources/languages/base_en.properties index 48e7dfb924ad2c9e7b7c9980ba3d1dd90521d852..cca8b4d17af55634268520ba673e4fc59c22d4d8 100644 --- a/src/main/resources/languages/base_en.properties +++ b/src/main/resources/languages/base_en.properties @@ -139,6 +139,8 @@ notification.settings.saved=Settings saved notification.settings.update.available=BudgetMaster update "{0}" available notification.settings.database.delete.success=Successfully deleted database notification.settings.database.import.success=Import successful: {0} accounts, {1} transactions, {2} categories, {3} templates and {4} charts +upload.image.success=Upload successful +upload.image.error=Error: {0} # WARNING warning.text.account.delete=The account "{0}" could not be deleted, because at least one account must exist at all time. You have to create a new account in order to delete this one. diff --git a/src/main/resources/static/js/accounts.js b/src/main/resources/static/js/accounts.js index 150f2e8a78476d58111cfaff560fcaafa27dcb0c..7987917809c2ae8f2616fd93ef4668e08cf552e0 100644 --- a/src/main/resources/static/js/accounts.js +++ b/src/main/resources/static/js/accounts.js @@ -56,6 +56,11 @@ $(document).ready(function() { openSelectAccountIconModal(this); }); + + $('#button-upload-new-image').click(function() + { + uploadImage(); + }); }); function openSelectAccountIconModal(item) @@ -72,7 +77,7 @@ function openSelectAccountIconModal(item) // select an icon option $('.account-icon-option').click(function() { - selectIcon(this); + selectIcon(this); }); $(modalID).modal(); @@ -90,4 +95,37 @@ function selectIcon(item) } item.classList.add('selected'); +} + +function uploadImage() +{ + let formID = 'form-upload-account-image'; + let form = document.getElementById(formID); + + $.ajax({ + url: form.action, + enctype: 'multipart/form-data', + type: 'post', + processData: false, + contentType: false, + cache: false, + data: new FormData(form), + success: function(response) + { + let parsedData = JSON.parse(response); + let isUploadSuccessful = parsedData['isUploadSuccessful'] + M.toast({ + html: parsedData['localizedMessage'], + classes: isUploadSuccessful ? 'green' : 'red' + }); + }, + error: function(response) + { + let parsedData = JSON.parse(response); + M.toast({ + html: parsedData['localizedMessage'], + classes: 'red' + }); + } + }); } \ 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 78eb9c1d4f97b71045752a7fe0425b6c604454e2..e03a5bfffa50c5526ec483cd8afcffaa353774d6 100644 --- a/src/main/resources/templates/accounts/accountFunctions.ftl +++ b/src/main/resources/templates/accounts/accountFunctions.ftl @@ -1,3 +1,4 @@ +<#import "/spring.ftl" as s> <#import "../helpers/header.ftl" as header> <#macro modalAccountIconSelect> @@ -32,7 +33,8 @@ </#macro> <#macro uploadImageForm> - <form id="form-upload-account-image" method="POST" action="/media" enctype="multipart/form-data"> + <form></form> + <form id="form-upload-account-image" method="post" action="<@s.url '/media/uploadImage'/>" enctype="multipart/form-data"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <div class="file-field input-field col s12"> <div class="container"> @@ -41,10 +43,10 @@ ${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 class="file-path-wrapper"> + <input class="file-path validate" type="text"> </div> - <@header.buttonSubmit name='action' icon='upload' localizationKey='account.new.icon.upload' classes="right"/> + <@header.buttonLink url='' icon='upload' localizationKey='account.new.icon.upload' id='button-upload-new-image' classes='right' noUrl=true/> </div> </div> </form>