From 2ab4497bd15339cbc814d03f2478b90fa05f23fd Mon Sep 17 00:00:00 2001 From: MK13 Date: Thu, 25 Aug 2022 17:14:37 +0200 Subject: [PATCH] Basic implementation for notes app --- .../resources/static/css/dashboard_main.css | 6 +- .../de/nbscloud/files/api/FilesService.java | 73 +++++++++- .../de/nbscloud/files/FileSystemService.java | 84 +++++++++++- .../de/nbscloud/files/FilesServiceImpl.java | 30 +++- .../files/controller/FilesController.java | 46 +++---- .../main/resources/static/css/files_main.css | 99 -------------- .../resources/templates/files/filesIndex.html | 17 +-- .../templates/files/includes/menu.html | 14 +- notes/pom.xml | 4 + .../notes/controller/NotesController.java | 129 ++++++++++++++++++ .../resources/i18n/notes_messages.properties | 7 + .../i18n/notes_messages_de_DE.properties | 7 + .../main/resources/static/css/notes_main.css | 59 ++++++++ .../templates/notes/fragments/treeLevel.html | 12 ++ .../templates/notes/includes/menu.html | 47 +++++++ .../resources/templates/notes/notesIndex.html | 40 ++++++ .../nbscloud/webcontainer/MessageHelper.java | 43 ++++++ .../resources/config/application.properties | 2 +- .../resources/static/css/darkModeColors.css | 1 + .../resources/static/css/lightModeColors.css | 1 + .../src/main/resources/static/css/main.css | 103 ++++++++++++++ .../templates/includes/messages.html | 18 +++ 22 files changed, 671 insertions(+), 171 deletions(-) create mode 100644 notes/src/main/java/de/nbscloud/notes/controller/NotesController.java create mode 100644 notes/src/main/resources/i18n/notes_messages.properties create mode 100644 notes/src/main/resources/i18n/notes_messages_de_DE.properties create mode 100644 notes/src/main/resources/static/css/notes_main.css create mode 100644 notes/src/main/resources/templates/notes/fragments/treeLevel.html create mode 100644 notes/src/main/resources/templates/notes/includes/menu.html create mode 100644 notes/src/main/resources/templates/notes/notesIndex.html create mode 100644 web-container-registry/src/main/java/de/nbscloud/webcontainer/MessageHelper.java create mode 100644 web-container/src/main/resources/templates/includes/messages.html diff --git a/dashboard/src/main/resources/static/css/dashboard_main.css b/dashboard/src/main/resources/static/css/dashboard_main.css index 31707ae..97cc16c 100644 --- a/dashboard/src/main/resources/static/css/dashboard_main.css +++ b/dashboard/src/main/resources/static/css/dashboard_main.css @@ -1,7 +1,3 @@ -#content-container { - padding-top: 2em; -} - #widgets-container { padding: 2em; display: flex; @@ -16,7 +12,7 @@ .widget { flex-grow: 1; - background-color: #1d2121; + background-color:var(--background-color-highlight); padding: 1em; align-self: stretch; } diff --git a/files-api/src/main/java/de/nbscloud/files/api/FilesService.java b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java index 403498f..4e1d8fa 100644 --- a/files-api/src/main/java/de/nbscloud/files/api/FilesService.java +++ b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java @@ -4,6 +4,7 @@ import de.nbscloud.webcontainer.registry.App; import java.nio.file.Path; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -12,22 +13,80 @@ public interface FilesService { LocalDateTime lastModified) { } - public void createAppDirectory(App app); + class ContentTree { + private String name; + private boolean directory; + private String path; + private List subTree = new ArrayList<>(); + private ContentTree parent; - public void createDirectory(App app, Path path); + public String getName() { + return name; + } - public void createFile(App app, Path path, byte[] content); + public void setName(String name) { + this.name = name; + } - public void delete(App app, Path path); + public boolean isDirectory() { + return directory; + } - public void get(App app, Path path); + public void setDirectory(boolean directory) { + this.directory = directory; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getSubTree() { + return subTree; + } + + public void setSubTree(List subTree) { + this.subTree = subTree; + } + + public boolean getHasSubTree() { + return !this.subTree.isEmpty(); + } + + public ContentTree getParent() { + return parent; + } + + public void setParent(ContentTree parent) { + this.parent = parent; + } + } + + void createAppDirectory(App app); + + void createDirectory(App app, Path path); + + void createFile(App app, Path path, byte[] content); + + void overwriteFile(App app, Path path, byte[] content); + + void delete(App app, Path path); + + byte[] get(App app, Path path); /** - * Paths in return list are always relative to the appDir. + * Paths in return list are always relative to the appDir. Non-recursive list. * * @param app to list the files for * @param path in case of {@link Optional#EMPTY} the appDir is used as start dir. If not empty, path has to be relative * to the appDir */ - public List list(App app, Optional path); + List list(App app, Optional path); + + ContentTree getTree(App app, Optional path); + + List collectDirs(App app, Optional path); } diff --git a/files/src/main/java/de/nbscloud/files/FileSystemService.java b/files/src/main/java/de/nbscloud/files/FileSystemService.java index 988f2fb..4e08fd3 100644 --- a/files/src/main/java/de/nbscloud/files/FileSystemService.java +++ b/files/src/main/java/de/nbscloud/files/FileSystemService.java @@ -1,6 +1,7 @@ package de.nbscloud.files; import de.nbscloud.files.api.FilesService.ContentContainer; +import de.nbscloud.files.api.FilesService.ContentTree; import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.exception.FileSystemServiceException; import org.apache.commons.io.FileUtils; @@ -21,6 +22,7 @@ import java.time.ZoneId; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -117,6 +119,14 @@ public class FileSystemService { } } + public void overwriteFile(Path path, byte[] content) { + try { + Files.write(path, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new FileSystemServiceException("Could not overwrite file", e); + } + } + public Path move(Path originalPath, Path newPath) { try { return Files.move(originalPath, newPath, StandardCopyOption.ATOMIC_MOVE); @@ -286,7 +296,7 @@ public class FileSystemService { List list(Path startPath, SortOrder sortOrder, Function relativizer) { try { - List contentList = Files.list(this.locationTracker.getCurrentLocation()) + List contentList = Files.list(startPath) .filter(path -> { boolean ok = Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) || Files.isDirectory(path); @@ -319,22 +329,86 @@ public class FileSystemService { } } + ContentTree getTree(Path startPath, SortOrder sortOrder, Function relativizer) { + try { + if (!Files.isDirectory(startPath)) { + return null; + } + + final ContentTree root = new ContentTree(); + + root.setName(startPath.getFileName().toString()); + root.setPath(relativizer.apply(startPath).toString()); + root.setDirectory(true); + + final AtomicReference current = new AtomicReference<>(root); + + Files.walkFileTree(startPath, new SimpleFileVisitor() { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (startPath.equals(dir)) { + return FileVisitResult.CONTINUE; + } + + final ContentTree dirTree = new ContentTree(); + + dirTree.setName(dir.getFileName().toString()); + dirTree.setPath(relativizer.apply(dir).toString()); + dirTree.setDirectory(true); + dirTree.setParent(current.get()); + + current.get().getSubTree().add(dirTree); + current.set(dirTree); + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + final ContentTree fileTree = new ContentTree(); + + fileTree.setName(file.getFileName().toString()); + fileTree.setPath(relativizer.apply(file).toString()); + fileTree.setDirectory(false); + fileTree.setParent(current.get()); + + current.get().getSubTree().add(fileTree); + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + current.set(current.get().getParent()); + + return FileVisitResult.CONTINUE; + } + }); + + return root; + } catch (IOException e) { + throw new FileSystemServiceException("Could not list files", e); + } + } + public List list(SortOrder sortOrder) { return list(this.locationTracker.getCurrentLocation(), sortOrder, path -> this.locationTracker.getRelativeToBaseDir(path)); } public List collectDirs(String sourceFile) { + return collectDirs(this.locationTracker.resolve(sourceFile), this.locationTracker.getBaseDirPath(), path -> this.locationTracker.getRelativeToBaseDir(path), false); + } + + List collectDirs(Path sourcePath, Path baseDir, Function relativizer, boolean includeSource) { try { final List resultList = new ArrayList<>(); - final Path sourcePath = this.locationTracker.resolve(sourceFile); final boolean sourceIsDir = Files.isDirectory(sourcePath); - Files.walkFileTree(this.locationTracker.getBaseDirPath(), new SimpleFileVisitor() { + Files.walkFileTree(baseDir, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { super.visitFile(dir, attrs); - if (sourceIsDir && sourcePath.equals(dir)) { + if (!includeSource && sourceIsDir && sourcePath.equals(dir)) { return FileVisitResult.SKIP_SUBTREE; } @@ -350,7 +424,7 @@ public class FileSystemService { return FileVisitResult.SKIP_SUBTREE; } - final String relPath = locationTracker.getRelativeToBaseDir(dir).toString(); + final String relPath = relativizer.apply(dir).toString(); resultList.add("/" + relPath); diff --git a/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java index 1375683..06a66a2 100644 --- a/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java +++ b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java @@ -18,6 +18,10 @@ public class FilesServiceImpl implements FilesService { private AppLocationTracker locationTracker; private Path resolve(App app, Path path) { + if(path.startsWith("/")) { + path = path.subpath(0, path.getNameCount()); + } + return this.locationTracker.resolve(app.getId(), path); } @@ -41,14 +45,19 @@ public class FilesServiceImpl implements FilesService { this.fileSystemService.createFile(resolve(app, path), content); } + @Override + public void overwriteFile(App app, Path path, byte[] content) { + this.fileSystemService.overwriteFile(resolve(app, path), content); + } + @Override public void delete(App app, Path path) { this.fileSystemService.delete(resolve(app, path)); } @Override - public void get(App app, Path path) { - this.fileSystemService.get(resolve(app, path)); + public byte[] get(App app, Path path) { + return this.fileSystemService.get(resolve(app, path)); } @Override @@ -58,4 +67,21 @@ public class FilesServiceImpl implements FilesService { return this.fileSystemService.list(p, FileSystemService.SortOrder.NATURAL, callbackPath -> appPath.relativize(callbackPath)); } + + @Override + public ContentTree getTree(App app, Optional path) { + final Path appPath = resolve(app, Path.of("")); + final Path p = path.map(tmpPath -> resolve(app, tmpPath)).orElse(appPath); + + return this.fileSystemService.getTree(p, FileSystemService.SortOrder.NATURAL, callbackPath -> appPath.relativize(callbackPath)); + } + + @Override + public List collectDirs(App app, Optional path) { + final Path appPath = resolve(app, Path.of("")); + final Path p = path.map(tmpPath -> resolve(app, tmpPath)).orElse(appPath); + + + return this.fileSystemService.collectDirs(p, appPath, callbackPath -> appPath.relativize(callbackPath), true); + } } diff --git a/files/src/main/java/de/nbscloud/files/controller/FilesController.java b/files/src/main/java/de/nbscloud/files/controller/FilesController.java index 6585c62..5f511e8 100644 --- a/files/src/main/java/de/nbscloud/files/controller/FilesController.java +++ b/files/src/main/java/de/nbscloud/files/controller/FilesController.java @@ -10,6 +10,7 @@ import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.exception.FileSystemServiceException; import de.nbscloud.files.form.RenameForm; import de.nbscloud.files.form.ShareForm; +import de.nbscloud.webcontainer.MessageHelper; import de.nbscloud.webcontainer.registry.AppRegistry; import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig; import org.apache.commons.io.input.ObservableInputStream; @@ -64,12 +65,8 @@ public class FilesController implements InitializingBean { private AppRegistry appRegistry; @Autowired private ObjectMapper mapper; - - // We have to temporarily store messages as we redirect: in the manipulation methods - // so everything we add to the model will be gone - private final List errors = new ArrayList<>(); - private final List shareInfo = new ArrayList<>(); - private final List infoMessages = new ArrayList<>(); + @Autowired + private MessageHelper messageHelper; @GetMapping("/files/browse/**") public String start(Model model, HttpServletRequest httpServletRequest, String sortOrder) { @@ -77,9 +74,7 @@ public class FilesController implements InitializingBean { updateLocation(httpServletRequest); - model.addAttribute("errors", getAndClear(this.errors)); - model.addAttribute("infoMessages", getAndClear(this.infoMessages)); - model.addAttribute("shareInfo", getAndClear(this.shareInfo)); + this.messageHelper.addAndClearAll(model); model.addAttribute("currentLocation", getCurrentLocationPrefixed()); model.addAttribute("content", getContent(order)); model.addAttribute("sortOrder", order); @@ -98,17 +93,17 @@ public class FilesController implements InitializingBean { this.locationTracker.resolve(filename), this.locationTracker.resolveTrash(filename)); - this.infoMessages.add("nbscloud.files.delete.success"); + this.messageHelper.addInfo("nbscloud.files.delete.success"); } catch (RuntimeException e) { logger.error("Could not soft delete file", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); } } else { // Hard delete this.fileSystemService.delete(filename); - this.infoMessages.add("nbscloud.files.delete.success"); + this.messageHelper.addInfo("nbscloud.files.delete.success"); } return "redirect:/files/browse/" + this.locationTracker.getRelativeLocation(); @@ -138,11 +133,11 @@ public class FilesController implements InitializingBean { try { this.fileSystemService.move(sourcePath, targetPath); - this.infoMessages.add("nbscloud.files.rename.success"); + this.messageHelper.addInfo("nbscloud.files.rename.success"); } catch (RuntimeException e) { logger.error("Could not rename file", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); } return "redirect:/files/browse/" + this.locationTracker.getRelativeLocation(); @@ -194,11 +189,11 @@ public class FilesController implements InitializingBean { try { this.fileSystemService.createFile(file.getOriginalFilename(), file.getBytes()); - this.infoMessages.add("nbscloud.files.created.file.success"); + this.messageHelper.addInfo("nbscloud.files.created.file.success"); } catch (IOException | RuntimeException e) { logger.error("Could not upload file", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); } } @@ -210,11 +205,11 @@ public class FilesController implements InitializingBean { try { this.fileSystemService.createDirectory(dirName); - this.infoMessages.add("nbscloud.files.created.dir.success"); + this.messageHelper.addInfo("nbscloud.files.created.dir.success"); } catch (RuntimeException e) { logger.error("Could not create dir", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); } return "redirect:/files/browse/" + this.locationTracker.getRelativeLocation(); @@ -241,6 +236,7 @@ public class FilesController implements InitializingBean { final Share share = new Share(); // The format is always "yyyy-MM-dd", see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date final LocalDate expiryDate = Optional.ofNullable(expiryDateString) + .map(ed -> ed.isBlank() ? null : ed) .map(ed -> LocalDate.parse(ed, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) .orElse(null); @@ -254,11 +250,11 @@ public class FilesController implements InitializingBean { this.fileSystemService.createFile(this.locationTracker.resolveShare(share.getUuid()), shareJson.getBytes(StandardCharsets.UTF_8)); - this.shareInfo.add("/files/shares?shareUuid=" + share.getUuid()); + this.messageHelper.addShare("/files/shares?shareUuid=" + share.getUuid()); } catch (RuntimeException | JsonProcessingException e) { logger.error("Could not share file", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); } return "redirect:/files/browse/" + this.locationTracker.getRelativeLocation(); @@ -325,20 +321,12 @@ public class FilesController implements InitializingBean { } catch (FileSystemServiceException e) { logger.error("Could not generate gallery", e); - this.errors.add(e.getMessage()); + this.messageHelper.addError(e.getMessage()); return "redirect:/files/browse/" + this.locationTracker.getRelativeLocation(); } } - private List getAndClear(List source) { - final List retList = new ArrayList<>(source); - - source.clear(); - - return retList; - } - private List getContent(FileSystemService.SortOrder order) { final List contentList = this.fileSystemService.list(order); diff --git a/files/src/main/resources/static/css/files_main.css b/files/src/main/resources/static/css/files_main.css index 0203256..51eae9d 100644 --- a/files/src/main/resources/static/css/files_main.css +++ b/files/src/main/resources/static/css/files_main.css @@ -73,105 +73,6 @@ width: 70em; } -#menu-container { - padding-top: 2em; - margin-left: 3em; -} - -.files-menu-icon { - background: none !important; - border: none; - padding: 0 !important; - color: var(--text-color); - cursor: pointer; - font-size: 2em; -} - -.files-menu-icon:hover { - color: var(--link-color); -} - -#content-container { - padding-top: unset !important; -} - -#create-dir-container > details > summary { - list-style-type: none; -} - -#create-dir-container > details[open] > summary { - list-style-type: none; -} - -#menu-container > div { - display: inline-block !important; -} - -.create-dir-container-details-modal-content { - padding: 0.5em; - pointer-events: all; - display: inline; -} - -.create-dir-container-details-modal { - background: var(--background-color); - filter: brightness(var(--background-brightness)); - border-radius: 0.3em; - left: auto; - top: auto; - right: auto; - bottom: auto; - padding: 0; - pointer-events: none; - position: absolute; - display: flex; - flex-direction: column; - z-index: 100; -} - -.menu-spacer { - width: 2em; -} - -.messageContainer { - width: 100%; - display: flex; - flex-direction: row; - justify-content: center; -} - -.message { - margin-top: 2em; - border: 1px; - border-style: solid; - border-radius: 0.3em; - padding: 1em; - display: inline-block; -} - -.shareMessage { - border-color: var(--good-color) !important; -} - -.infoMessage { - border-color: var(--good-color) !important; -} - -.errorMessage { - border-color: var(--error-color) !important; -} - -@media only screen and (max-width: 450px) { - #menu-container { - padding-top: 1em; - margin-left: 1em; - } - - .menu-spacer { - width: 1em; - } -} - #gallery-link { text-decoration: none; } diff --git a/files/src/main/resources/templates/files/filesIndex.html b/files/src/main/resources/templates/files/filesIndex.html index 420f259..b332a12 100644 --- a/files/src/main/resources/templates/files/filesIndex.html +++ b/files/src/main/resources/templates/files/filesIndex.html @@ -13,22 +13,7 @@
-
-
- - -
-
-
-
- -
-
-
-
- -
-
+
diff --git a/files/src/main/resources/templates/files/includes/menu.html b/files/src/main/resources/templates/files/includes/menu.html index 0b6f157..fcdf2c1 100644 --- a/files/src/main/resources/templates/files/includes/menu.html +++ b/files/src/main/resources/templates/files/includes/menu.html @@ -1,23 +1,23 @@