UI improvements

This commit is contained in:
F43nd1r 2018-03-24 05:18:49 +01:00
parent 4311a672dc
commit 063a51d956
17 changed files with 281 additions and 99 deletions

View file

@ -10,7 +10,7 @@ buildscript {
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "com.devsoap.plugin:gradle-vaadin-plugin:1.3.0"
classpath "com.devsoap.plugin:gradle-vaadin-plugin:1.3.1"
classpath 'io.spring.gradle:propdeps-plugin:0.0.9.RELEASE'
classpath "gradle.plugin.com.github.jk1:gradle-license-report:0.4.1"
}
@ -60,13 +60,6 @@ vaadinSuperDevMode {
}
dependencyManagement {
dependencies {
dependencySet(group: 'com.vaadin', version: '2.1.0.beta2') {
entry 'vaadin-spring-boot-starter'
entry 'vaadin-spring-boot'
entry 'vaadin-spring'
}
}
imports {
mavenBom "com.vaadin:vaadin-bom:${vaadin.version}"
}
@ -87,6 +80,7 @@ dependencies {
exclude group: 'javax.servlet', module: 'servlet-api'
exclude group: 'jfree'
}
compile 'com.vaadin:vaadin-icons:3.0.1'
compile 'org.jfree:jfreechart:1.5.0'
compile 'javax.servlet:javax.servlet-api:3.1.0'
compile 'org.vaadin.addons:stepper:2.4.0'
@ -104,16 +98,6 @@ dependencies {
compileJava.dependsOn(processResources)
configurations {
'vaadin-client' {
resolutionStrategy {
dependencySubstitution {
substitute module('javax.validation:validation-api') with module('javax.validation:validation-api:1.0.0.GA')
}
}
}
}
war {
archiveName = 'acra.war'
version = version

View file

@ -2,11 +2,13 @@ package com.faendir.acra.ui;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.sql.user.UserManager;
import com.faendir.acra.ui.view.base.Path;
import com.faendir.acra.ui.view.user.ChangePasswordView;
import com.faendir.acra.ui.view.user.UserManagerView;
import com.faendir.acra.util.Style;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.shared.communication.PushMode;
@ -16,7 +18,9 @@ import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.LoginForm;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
@ -40,15 +44,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
public class BackendUI extends UI {
@NonNull private final AuthenticationManager authenticationManager;
@NonNull private final ApplicationContext applicationContext;
@NonNull private final VerticalLayout content;
@NonNull private final Panel content;
@NonNull private final Path path;
@Autowired
public BackendUI(@NonNull AuthenticationManager authenticationManager, @NonNull ApplicationContext applicationContext) {
this.authenticationManager = authenticationManager;
this.applicationContext = applicationContext;
content = new VerticalLayout();
content = new Panel();
content.setSizeFull();
Style.NO_PADDING.apply(content);
Style.apply(content, Style.NO_PADDING, Style.NO_BACKGROUND, Style.NO_BORDER);
path = new Path();
}
@Override
@ -93,28 +99,28 @@ public class BackendUI extends UI {
private void showMain() {
NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class);
Button up = new Button("Up", e -> navigationManager.navigateBack());
Button up = new Button(VaadinIcons.ARROW_UP, e -> navigationManager.navigateBack());
Style.apply(up, Style.BUTTON_ROUND, Style.NO_PADDING);
HorizontalLayout header = new HorizontalLayout(up);
header.setExpandRatio(up, 1);
header.setWidth(100, Unit.PERCENTAGE);
header.addComponent(path);
header.setExpandRatio(path, 1);
MenuBar menuBar = new MenuBar();
header.addComponent(menuBar);
MenuBar.MenuItem user = menuBar.addItem("", VaadinIcons.USER, null);
if (SecurityUtils.hasRole(UserManager.ROLE_ADMIN)) {
Button userManager = new Button("User Manager", e -> navigationManager.navigateTo(UserManagerView.class, ""));
header.addComponent(userManager);
header.setComponentAlignment(userManager, Alignment.MIDDLE_RIGHT);
user.addItem("User Manager", e -> navigationManager.cleanNavigateTo(UserManagerView.class));
user.addSeparator();
}
user.addItem("Change Password", e -> navigationManager.cleanNavigateTo(ChangePasswordView.class));
user.addItem("Logout", e -> logout());
Button changePassword = new Button("Change Password", e -> navigationManager.navigateTo(ChangePasswordView.class, ""));
header.addComponent(changePassword);
header.setComponentAlignment(changePassword, Alignment.MIDDLE_RIGHT);
Button logout = new Button("Logout", e -> logout());
header.addComponent(logout);
header.setComponentAlignment(logout, Alignment.MIDDLE_RIGHT);
Style.apply(header, Style.PADDING_TOP, Style.PADDING_LEFT, Style.PADDING_RIGHT);
Style.apply(header, Style.PADDING_TOP, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM, Style.BACKGROUND_HEADER);
VerticalLayout root = new VerticalLayout(header, content);
root.setExpandRatio(content, 1);
root.setSpacing(false);
root.setSizeFull();
Style.NO_PADDING.apply(root);
setContent(root);
@ -123,7 +129,14 @@ public class BackendUI extends UI {
@NonNull
@UIScope
@Bean
public VerticalLayout mainView() {
public Panel mainView() {
return content;
}
@NonNull
@UIScope
@Bean
public Path mainPath() {
return path;
}
}

View file

@ -6,6 +6,7 @@ import com.faendir.acra.ui.annotation.RequiresRole;
import com.faendir.acra.ui.view.ErrorView;
import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.ui.view.base.ParametrizedNamedView;
import com.faendir.acra.ui.view.base.Path;
import com.faendir.acra.util.MyNavigator;
import com.faendir.acra.util.Utils;
import com.vaadin.navigator.View;
@ -13,8 +14,8 @@ import com.vaadin.spring.access.ViewAccessControl;
import com.vaadin.spring.access.ViewInstanceAccessControl;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
@ -23,8 +24,6 @@ import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
@ -35,56 +34,59 @@ import java.util.Optional;
@Component
@Configurable
public class NavigationManager implements ViewAccessControl, ViewInstanceAccessControl, Serializable {
@NonNull
private final MyNavigator navigator;
@NonNull
private final ApplicationContext applicationContext;
@NonNull
private final List<String> backStack;
@NonNull private final Path path;
@NonNull private final MyNavigator navigator;
@NonNull private final ApplicationContext applicationContext;
@Autowired
public NavigationManager(@NonNull UI ui, @NonNull VerticalLayout mainView, @NonNull MyNavigator navigator, @NonNull ApplicationContext applicationContext) {
public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator, @NonNull ApplicationContext applicationContext) {
this.path = mainPath;
this.navigator = navigator;
this.applicationContext = applicationContext;
this.navigator.init(ui, mainView);
this.backStack = new ArrayList<>();
navigator.setErrorView(ErrorView.class);
String target = Optional.ofNullable(ui.getPage().getLocation().getFragment()).orElse("").replace("!", "");
backStack.add(target);
ui.access(() -> navigateTo(target));
}
public void navigateTo(@NonNull Class<? extends NamedView> namedView, @Nullable String parameters) {
navigateTo(namedView, parameters, false);
}
public void navigateTo(@NonNull Class<? extends NamedView> namedView, @Nullable String parameters, boolean newTab) {
String target = namedView.getAnnotation(SpringView.class).name() + (parameters == null ? "" : "/" + parameters);
if (newTab) {
navigator.getUI().getPage().open(Utils.getUrlWithFragment(target), "_blank", false);
} else if (!backStack.get(0).equals(target)) {
backStack.add(0, target);
} else if (path.isEmpty() || !path.getLast().getId().equals(target)) {
navigateTo(target);
}
}
public void cleanNavigateTo(@NonNull Class<? extends NamedView> namedView) {
cleanHistory();
navigateTo(namedView, "", false);
}
private void navigateTo(@NonNull String fragment) {
navigator.navigateTo(fragment);
}
private void cleanHistory() {
path.clear();
}
public void navigateBack() {
if (backStack.size() < 2) {
backStack.set(0, "");
navigateTo("");
if (path.getSize() < 2) {
if (!"".equals(path.getLast().getId())) {
path.goUp();
navigateTo("");
}
} else {
backStack.remove(0);
navigateTo(backStack.get(0));
path.goUp();
navigateTo(path.getLast().getId());
}
}
public void updatePageParameters(@Nullable String parameters) {
String target = navigator.getCurrentView().getClass().getAnnotation(SpringView.class).name() + (parameters == null ? "" : "/" + parameters);
backStack.set(0, target);
Path.Element element = path.goUp();
path.goTo(element.getLabel(), target, this::navigateTo);
navigator.getUI().getPage().setUriFragment(target, false);
}
@ -96,14 +98,21 @@ public class NavigationManager implements ViewAccessControl, ViewInstanceAccessC
@Override
public boolean isAccessGranted(UI ui, @NonNull String beanName, @NonNull View view) {
if (!(view instanceof ParametrizedNamedView)) {
return true;
boolean result = false;
if ((view instanceof NamedView)) {
if ((view instanceof ParametrizedNamedView)) {
ParametrizedNamedView<?> v = (ParametrizedNamedView<?>) view;
if (v.validate(navigator.getParameters())) {
RequiresAppPermission annotation = applicationContext.findAnnotationOnBean(beanName, RequiresAppPermission.class);
result = annotation == null || SecurityUtils.hasPermission(v.getApp(), annotation.value());
}
} else {
result = true;
}
if (result) {
path.goTo(((NamedView) view).getTitle(), navigator.getNavState(), this::navigateTo);
}
}
ParametrizedNamedView<?> v = (ParametrizedNamedView<?>) view;
if (!v.validate(navigator.getParameters())) {
return false;
}
RequiresAppPermission annotation = applicationContext.findAnnotationOnBean(beanName, RequiresAppPermission.class);
return annotation == null || SecurityUtils.hasPermission(v.getApp(), annotation.value());
return result;
}
}

View file

@ -4,7 +4,7 @@ import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Composite;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import org.springframework.stereotype.Component;
@ -15,14 +15,13 @@ import org.springframework.stereotype.Component;
*/
@Component
@UIScope
public class ErrorView extends CustomComponent implements View {
public class ErrorView extends Composite implements View {
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
Label label = new Label("This page does not exist or you do not have the permission to view it.");
VerticalLayout layout = new VerticalLayout(label);
layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER);
layout.setSizeFull();
setSizeFull();
setCompositionRoot(layout);
}
}

View file

@ -79,4 +79,9 @@ public class Overview extends NamedView {
popup.clear().addComponent(new ConfigurationLabel(userPasswordPair.getFirst().getUsername(), userPasswordPair.getSecond())).addCloseButton().show();
}).show();
}
@Override
public String getTitle() {
return "Overview";
}
}

View file

@ -48,12 +48,16 @@ public class AppView extends ParametrizedNamedView<Pair<App, String>> {
content.setSizeFull();
Style.apply(content, Style.NO_PADDING, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
setCompositionRoot(content);
setSizeFull();
tabSheet.setSizeFull();
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(parameter.getFirst().getId() + "/" + e.getTabSheet().getSelectedTab().getCaption()));
tabSheet.setInitialTab(parameter.getSecond());
}
@Override
protected String getTitle(@NonNull Pair<App, String> appStringPair) {
return appStringPair.getFirst().getName();
}
@Override
public Pair<App, String> validateAndParseFragment(@NonNull String fragment) {
String[] parameters = fragment.split("/");

View file

@ -2,17 +2,19 @@ package com.faendir.acra.ui.view.base;
import com.faendir.acra.ui.NavigationManager;
import com.vaadin.navigator.View;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Composite;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* Non-abstract subclasses must be annotated with {@link com.vaadin.spring.annotation.SpringView}
*
* @author Lukas
* @since 14.05.2017
*/
@Component
public abstract class NamedView extends CustomComponent implements View {
public abstract class NamedView extends Composite implements View {
private NavigationManager navigationManager;
protected NavigationManager getNavigationManager() {
@ -24,4 +26,6 @@ public abstract class NamedView extends CustomComponent implements View {
public void setNavigationManager(NavigationManager navigationManager) {
this.navigationManager = navigationManager;
}
public abstract String getTitle();
}

View file

@ -39,4 +39,11 @@ public abstract class ParametrizedNamedView<T> extends NamedView {
}
protected abstract void enter(@NonNull T t);
@Override
public String getTitle() {
return getTitle(t);
}
protected abstract String getTitle(@NonNull T t);
}

View file

@ -0,0 +1,107 @@
package com.faendir.acra.ui.view.base;
import com.faendir.acra.util.Style;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Composite;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;
/**
* @author Lukas
* @since 24.03.2018
*/
public class Path extends Composite {
private final Deque<Element> elements;
private final HorizontalLayout layout;
public Path() {
elements = new ArrayDeque<>();
layout = new HorizontalLayout();
layout.setSpacing(false);
setCompositionRoot(layout);
}
public void goTo(String label, String id, Consumer<String> action) {
goTo(new Element(label, id, action));
}
public void goTo(Element element) {
if (elements.stream().map(Element::getId).anyMatch(element.getId()::equals)) {
while (!getLast().getId().equals(element.getId())) {
goUp();
}
} else {
if (!elements.isEmpty()) {
Label icon = new Label(VaadinIcons.CARET_RIGHT.getHtml(), ContentMode.HTML);
layout.addComponent(icon);
layout.setComponentAlignment(icon, Alignment.MIDDLE_CENTER);
}
Button button = new Button(element.getLabel());
button.addClickListener(e -> element.getAction().accept(element.getId()));
Style.BUTTON_BORDERLESS.apply(button);
layout.addComponent(button);
elements.addLast(element);
}
}
public int getSize() {
return elements.size();
}
public boolean isEmpty() {
return elements.isEmpty();
}
public Element getLast() {
return elements.getLast();
}
public Element goUp() {
if (elements.isEmpty()) {
return null;
}
removeLastComponent();
if (elements.size() != 1) removeLastComponent();
return elements.removeLast();
}
public void clear() {
layout.removeAllComponents();
elements.clear();
}
private void removeLastComponent() {
layout.removeComponent(layout.getComponent(layout.getComponentCount() - 1));
}
public static class Element {
private final String label;
private final String id;
private final Consumer<String> action;
public Element(String label, String id, Consumer<String> action) {
this.label = label;
this.id = id;
this.action = action;
}
public String getLabel() {
return label;
}
public String getId() {
return id;
}
public Consumer<String> getAction() {
return action;
}
}
}

View file

@ -86,6 +86,12 @@ public class BugView extends ParametrizedNamedView<Bug> {
Style.apply(root, Style.NO_BACKGROUND, Style.NO_BORDER);
setCompositionRoot(root);
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
setSizeFull();
}
@Override
protected String getTitle(@NonNull Bug bug) {
String title = bug.getTitle();
if (title.length() > 100) title = title.substring(0, 95) + "";
return title;
}
}

View file

@ -85,13 +85,16 @@ public class ReportView extends ParametrizedNamedView<Report> {
VerticalLayout layout = new VerticalLayout(summary, details);
layout.setSizeUndefined();
layout.setExpandRatio(details, 1);
Style.NO_PADDING.apply(layout);
Style.apply(layout, Style.NO_PADDING, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
Panel root = new Panel(layout);
root.setSizeFull();
Style.apply(root, Style.NO_BACKGROUND, Style.NO_BORDER);
setCompositionRoot(root);
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
setSizeFull();
}
@Override
protected String getTitle(@NonNull Report report) {
return report.getId();
}
@NonNull

View file

@ -1,7 +1,6 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.sql.model.App;
import com.faendir.acra.sql.model.User;
import com.faendir.acra.sql.user.UserManager;
import com.faendir.acra.ui.BackendUI;
@ -59,16 +58,15 @@ public class ChangePasswordView extends NamedView {
VerticalLayout root = new VerticalLayout(layout);
root.setSizeFull();
root.setComponentAlignment(layout, Alignment.MIDDLE_CENTER);
setSizeFull();
setCompositionRoot(root);
}
@Nullable
public App parseFragment(@NonNull String fragment) {
return null;
}
public boolean validate(@Nullable String fragment) {
return true;
}
@Override
public String getTitle() {
return "Change Password";
}
}

View file

@ -73,7 +73,6 @@ public class UserManagerView extends NamedView {
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
Style.NO_PADDING.apply(layout);
setCompositionRoot(layout);
setSizeFull();
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
}
@ -91,4 +90,9 @@ public class UserManagerView extends NamedView {
})
.show();
}
@Override
public String getTitle() {
return "User Manager";
}
}

View file

@ -2,6 +2,7 @@ package com.faendir.acra.util;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.spring.navigator.SpringNavigator;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
@ -12,17 +13,28 @@ import org.springframework.stereotype.Component;
@Component
@UIScope
public class MyNavigator extends SpringNavigator {
@NonNull private String navState;
@Nullable private String parameters;
public MyNavigator() {
navState = "";
}
@Override
public void navigateTo(String navigationState) {
String[] split = navigationState.split("/", 2);
parameters = split.length == 2 ? split[1] : null;
super.navigateTo(navigationState);
public void navigateTo(@Nullable String navigationState) {
navState = navigationState != null ? navigationState : "";
int index;
parameters = (index = navState.indexOf('/')) != -1 ? navState.substring(index + 1) : null;
super.navigateTo(navState);
}
@Nullable
public String getParameters() {
return parameters;
}
@NonNull
public String getNavState() {
return navState;
}
}

View file

@ -18,8 +18,12 @@ public enum Style {
MARGIN_RIGHT("margin-right"),
MARGIN_BOTTOM("margin-bottom"),
BACKGROUND_LIGHT_GRAY("background-light-gray"),
BACKGROUND_HEADER("background-header"),
NO_BACKGROUND("no-background"),
NO_BORDER("no-border");
NO_BORDER("no-border"),
BORDER_TOP("border-top"),
BUTTON_ROUND("button-round"),
BUTTON_BORDERLESS("v-button-borderless");
@NonNull private final String name;
Style(@NonNull String name) {

View file

@ -30,21 +30,22 @@
}
.no-padding {
padding: 0 !important;
padding: 0;
}
$padding: 10px;
$height: 37px;
.padding-left {
padding-left: $padding !important;
padding-left: $padding;
}
.padding-top {
padding-top: $padding !important;
padding-top: $padding;
}
.padding-right {
padding-right: $padding !important;
padding-right: $padding;
}
.padding-bottom {
@ -73,13 +74,30 @@
background: lightgray;
}
.background-header {
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
}
.no-background {
background: transparent !important;
background: transparent;
}
.no-border {
border: none !important;
box-shadow: none !important;
-webkit-box-shadow: none !important;
border: none;
box-shadow: none;
-webkit-box-shadow: none;
}
.border-top {
border-top: 1px solid lightgray;
border-radius: 0;
}
.button-round {
border-radius: 100%;
border: none;
padding: 0;
width: $height;
height: $height;
}
}

View file

@ -1,6 +1,10 @@
/* This file is automatically managed and will be overwritten from time to time. */
/* Do not manually edit this file. */
/* Provided by vaadin-icons-3.0.1.jar */
@import "../../../VAADIN/addons/vaadin-icons/vaadin-icons.scss";
/* Provided by stepper-2.4.0.jar */
@import "../../..//VAADIN/addons/stepper/stepper.scss";
@ -11,6 +15,7 @@
/* Import and include this mixin into your project theme to include the addon themes */
@mixin addons {
@include vaadin-icons;
@include stepper;
@include stepper;
}