Merge pull request #13 from F43nd1r/vaadin10
[WIP] Migrate to Vaadin 10
This commit is contained in:
commit
f956bc8573
136 changed files with 2622 additions and 6015 deletions
|
@ -1,6 +1,8 @@
|
|||
[![Build Status](https://travis-ci.org/F43nd1r/Acrarium.svg?branch=master)](https://travis-ci.org/F43nd1r/Acrarium)
|
||||
|
||||
# Acrarium (formerly Acra-backend)
|
||||
<h1 align=center>
|
||||
<img src="src/main/webapp/frontend/logo.png" width=50%>
|
||||
</h1>
|
||||
A Backend for ACRA written in Java using Spring Boot, Vaadin and MySQL
|
||||
|
||||
# Setup
|
||||
|
@ -49,3 +51,7 @@ Binary distributions may contain dependencies licensed under any of the followin
|
|||
- [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt)
|
||||
- [EPL 1.0](http://www.eclipse.org/legal/epl-v10.html)
|
||||
- [MPL 1.1](https://www.mozilla.org/en-US/MPL/1.1/)
|
||||
|
||||
# Credits
|
||||
|
||||
Thanks to [Mirza Zulfan](https://github.com/mirzazulfan) for creating the logo.
|
||||
|
|
129
build.gradle
129
build.gradle
|
@ -13,21 +13,25 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'war'
|
||||
id 'org.springframework.boot' version '2.0.4.RELEASE'
|
||||
id 'com.devsoap.plugin.vaadin' version '1.3.1'
|
||||
id 'org.springframework.boot' version '2.1.0.RELEASE'
|
||||
id 'com.devsoap.vaadin-flow' version '1.0.0.M6'
|
||||
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
|
||||
id 'cn.bestwu.propdeps' version '0.0.10'
|
||||
id 'net.researchgate.release' version '2.7.0'
|
||||
}
|
||||
|
||||
vaadin {
|
||||
version '12.0.0.beta1'
|
||||
submitStatistics false
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
maven { url 'https://maven.vaadin.com/vaadin-addons' }
|
||||
vaadin.repositories()
|
||||
}
|
||||
|
||||
group 'com.faendir'
|
||||
|
@ -37,6 +41,7 @@ targetCompatibility = 1.8
|
|||
ext {
|
||||
generated = file("$buildDir/generated")
|
||||
queryDslOutput = file("$generated/querydsl/java")
|
||||
messagesOutput = file("$generated/faendir/java")
|
||||
}
|
||||
|
||||
compileJava {
|
||||
|
@ -50,77 +55,57 @@ idea {
|
|||
module {
|
||||
sourceDirs += queryDslOutput
|
||||
generatedSourceDirs += queryDslOutput
|
||||
sourceDirs += messagesOutput
|
||||
generatedSourceDirs += messagesOutput
|
||||
}
|
||||
}
|
||||
|
||||
vaadin {
|
||||
version '8.5.1'
|
||||
push true
|
||||
}
|
||||
|
||||
vaadinCompile {
|
||||
style 'PRETTY'
|
||||
outputDirectory 'src/main/resources'
|
||||
strict true
|
||||
widgetset 'com.faendir.acra.AppWidgetset'
|
||||
}
|
||||
|
||||
vaadinThemeCompile {
|
||||
themesDirectory 'src/main/resources/VAADIN/themes'
|
||||
}
|
||||
|
||||
vaadinSuperDevMode {
|
||||
noserver false
|
||||
extraArgs ""
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "com.vaadin:vaadin-bom:${vaadin.version}"
|
||||
}
|
||||
configurations {
|
||||
all*.exclude module : 'slf4j-simple'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//spring
|
||||
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
compile 'mysql:mysql-connector-java'
|
||||
compile 'org.springframework.boot:spring-boot-starter-security'
|
||||
compile 'org.liquibase:liquibase-core'
|
||||
compile 'org.yaml:snakeyaml'
|
||||
optional 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'mysql:mysql-connector-java'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.liquibase:liquibase-core'
|
||||
implementation 'org.yaml:snakeyaml'
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
|
||||
def queryDslVersion = '4.2.1'
|
||||
compile "com.querydsl:querydsl-jpa:$queryDslVersion"
|
||||
compile "com.querydsl:querydsl-sql:$queryDslVersion"
|
||||
compile "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
|
||||
compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
|
||||
implementation "com.querydsl:querydsl-jpa:$queryDslVersion"
|
||||
implementation "com.querydsl:querydsl-sql:$queryDslVersion"
|
||||
annotationProcessor "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
|
||||
annotationProcessor "javax.persistence:javax.persistence-api:2.2"
|
||||
annotationProcessor "javax.annotation:javax.annotation-api:1.3.2"
|
||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
|
||||
//vaadin
|
||||
compile 'com.vaadin:vaadin-spring-boot-starter'
|
||||
compile 'com.vaadin:vaadin-push'
|
||||
compile('org.vaadin.addon:jfreechartwrapper:4.0.0') {
|
||||
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'
|
||||
implementation vaadin.bom()
|
||||
implementation vaadin.platform()
|
||||
implementation vaadin.dependency('icons-flow')
|
||||
implementation vaadin.dependency('spring-boot-starter')
|
||||
implementation 'com.vaadin:vaadin-grid-flow:2.1.2'
|
||||
implementation 'org.jfree:jfreechart:1.5.0'
|
||||
implementation 'org.apache.xmlgraphics:batik-svggen:1.7'
|
||||
implementation 'javax.servlet:javax.servlet-api:4.0.1'
|
||||
implementation 'org.webjars.bowergithub.simpleelements:simple-dropdown:1.0.0'
|
||||
//utility
|
||||
compile 'org.codeartisans:org.json:20161124'
|
||||
compile 'org.apache.commons:commons-text:1.4'
|
||||
compile 'org.xbib:time:1.0.0'
|
||||
compile 'ch.acra:acra-javacore:5.1.3'
|
||||
compile 'com.faendir.proguard:retrace:1.3'
|
||||
compile 'javax.xml.bind:jaxb-api:2.3.0'
|
||||
compile 'com.github.ziplet:ziplet:2.3.0'
|
||||
compile 'me.xdrop:fuzzywuzzy:1.1.10'
|
||||
compile 'com.talanlabs:avatar-generator:1.1.0'
|
||||
compile 'org.ektorp:org.ektorp.spring:1.5.0'
|
||||
implementation 'org.codeartisans:org.json:20161124'
|
||||
implementation 'org.apache.commons:commons-text:1.4'
|
||||
implementation 'org.xbib:time:1.0.0'
|
||||
implementation 'ch.acra:acra-javacore:5.1.3'
|
||||
implementation 'com.faendir.proguard:retrace:1.3'
|
||||
implementation 'javax.xml.bind:jaxb-api:2.3.0'
|
||||
implementation 'com.github.ziplet:ziplet:2.3.0'
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.1.10'
|
||||
implementation 'com.talanlabs:avatar-generator:1.1.0'
|
||||
implementation 'org.ektorp:org.ektorp.spring:1.5.0'
|
||||
//testing
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
testCompile 'org.springframework.security:spring-security-test'
|
||||
testCompile 'com.h2database:h2'
|
||||
testCompile files('libs/ojdbc6.jar')
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'com.h2database:h2'
|
||||
testImplementation files('libs/ojdbc6.jar')
|
||||
}
|
||||
|
||||
compileJava.dependsOn(processResources)
|
||||
|
@ -129,19 +114,11 @@ war {
|
|||
archiveName = 'acra.war'
|
||||
version = version
|
||||
enabled = true
|
||||
dependsOn vaadinThemeCompile, vaadinCompile
|
||||
}
|
||||
|
||||
task generateThemeClasses(type: com.faendir.acra.gradle.ThemeClassGenerator) {
|
||||
themesDirectory file('src/main/resources/VAADIN/themes')
|
||||
outputDirectory file("$generated/faendir/java")
|
||||
}
|
||||
|
||||
compileJava.dependsOn(generateThemeClasses)
|
||||
|
||||
task generateMessageClasses(type: com.faendir.acra.gradle.I18nClassGenerator) {
|
||||
inputDirectory file('src/main/resources/i18n/com/faendir/acra')
|
||||
outputDirectory file("$generated/faendir/java")
|
||||
outputDirectory messagesOutput
|
||||
packageName 'com.faendir.acra.i18n'
|
||||
className 'Messages'
|
||||
}
|
||||
|
@ -154,9 +131,3 @@ test {
|
|||
exceptionFormat "full"
|
||||
}
|
||||
}
|
||||
|
||||
release {
|
||||
failOnUnversionedFiles = false
|
||||
tagTemplate = 'v$version'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
plugins {
|
||||
id 'groovy'
|
||||
}
|
||||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
|
@ -19,7 +22,8 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
gradleApi()
|
||||
compile localGroovy()
|
||||
compile gradleApi()
|
||||
compile 'com.squareup:javapoet:1.11.1'
|
||||
compile 'com.google.guava:guava:26.0-jre'
|
||||
}
|
|
@ -21,7 +21,11 @@ import com.squareup.javapoet.FieldSpec
|
|||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.CacheableTask
|
||||
import org.gradle.api.tasks.SkipWhenEmpty
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.plugins.ide.idea.IdeaPlugin
|
||||
|
||||
import static javax.lang.model.element.Modifier.*
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.gradle
|
||||
|
||||
import com.squareup.javapoet.FieldSpec
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.CacheableTask
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.SkipWhenEmpty
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.plugins.ide.idea.IdeaPlugin
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static javax.lang.model.element.Modifier.*
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 05.06.18
|
||||
*/
|
||||
@CacheableTask
|
||||
@SuppressWarnings("GroovyUnusedDeclaration")
|
||||
class ThemeClassGenerator extends DefaultTask {
|
||||
@SkipWhenEmpty
|
||||
@InputDirectory
|
||||
File themesDirectory
|
||||
@OutputDirectory
|
||||
File outputDirectory
|
||||
|
||||
ThemeClassGenerator() {
|
||||
project.afterEvaluate {
|
||||
project.sourceSets.main.java.srcDirs outputs.files
|
||||
project.plugins.withType(IdeaPlugin.class, { IdeaPlugin idea -> idea.model.module.generatedSourceDirs += outputs.files })
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void exec() {
|
||||
Pattern pattern = Pattern.compile("\\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)")
|
||||
themesDirectory.eachDir {
|
||||
Set<String> cssClasses = new LinkedHashSet<>();
|
||||
project.fileTree(dir: it, include: "*.scss", exclude: "styles.scss").forEach {
|
||||
Matcher matcher = pattern.matcher(it.text.replaceAll("//.*\\n", "").replaceAll("/\\*.*\\*/", "").replaceAll("\".*\"", ""))
|
||||
while (matcher.find()) {
|
||||
cssClasses.add(matcher.group(1))
|
||||
}
|
||||
}
|
||||
cssClasses.removeIf { it.startsWith("v-") }
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(it.name).addModifiers(PUBLIC, FINAL)
|
||||
classBuilder.addField(FieldSpec.builder(String, "THEME_NAME", PUBLIC, STATIC, FINAL).initializer('$S', it.name).build())
|
||||
for (String cssClass : cssClasses) {
|
||||
classBuilder.addField(FieldSpec.builder(String, cssClass.toUpperCase().replace("-", "_"), PUBLIC, STATIC, FINAL).initializer('$S', cssClass).build())
|
||||
}
|
||||
JavaFile.builder("com.vaadin.ui.themes", classBuilder.build())
|
||||
.skipJavaLangImports(true)
|
||||
.indent(" ")
|
||||
.build()
|
||||
.writeTo(outputDirectory)
|
||||
}
|
||||
}
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,5 @@
|
|||
#Sat May 20 23:53:35 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
|
||||
|
|
8
gradlew
vendored
8
gradlew
vendored
|
@ -28,16 +28,16 @@ APP_NAME="Gradle"
|
|||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
|
@ -155,7 +155,7 @@ if $cygwin ; then
|
|||
fi
|
||||
|
||||
# Escape application args
|
||||
save ( ) {
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
|
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
|
@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
|||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
|
2
settings.gradle
Normal file
2
settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
include 'buildSrc'
|
||||
enableFeaturePreview('IMPROVED_POM_SUPPORT')
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.client;
|
||||
|
||||
import com.faendir.acra.ui.view.base.MiddleClickExtension;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.event.dom.client.MouseDownEvent;
|
||||
import com.vaadin.client.ComponentConnector;
|
||||
import com.vaadin.client.MouseEventDetailsBuilder;
|
||||
import com.vaadin.client.ServerConnector;
|
||||
import com.vaadin.client.extensions.AbstractExtensionConnector;
|
||||
import com.vaadin.shared.MouseEventDetails;
|
||||
import com.vaadin.shared.communication.ServerRpc;
|
||||
import com.vaadin.shared.ui.Connect;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.06.18
|
||||
*/
|
||||
@Connect(MiddleClickExtension.class)
|
||||
public class MiddleClickExtensionConnector extends AbstractExtensionConnector {
|
||||
@Override
|
||||
protected void extend(ServerConnector target) {
|
||||
getParent().getWidget().addDomHandler(event -> {
|
||||
if (event.getNativeButton() == NativeEvent.BUTTON_MIDDLE) {
|
||||
event.preventDefault();
|
||||
middleClick(event);
|
||||
}
|
||||
}, MouseDownEvent.getType());
|
||||
}
|
||||
|
||||
protected void middleClick(MouseDownEvent event) {
|
||||
getRpcProxy(Rpc.class).middleClick(MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentConnector getParent() {
|
||||
return (ComponentConnector) super.getParent();
|
||||
}
|
||||
|
||||
public interface Rpc extends ServerRpc {
|
||||
void middleClick(MouseEventDetails details);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.client.mygrid;
|
||||
|
||||
import com.faendir.acra.client.MiddleClickExtensionConnector;
|
||||
import com.faendir.acra.ui.view.base.layout.MyGrid;
|
||||
import com.google.gwt.event.dom.client.MouseDownEvent;
|
||||
import com.vaadin.client.MouseEventDetailsBuilder;
|
||||
import com.vaadin.client.connectors.grid.GridConnector;
|
||||
import com.vaadin.client.widget.grid.CellReference;
|
||||
import com.vaadin.shared.MouseEventDetails;
|
||||
import com.vaadin.shared.communication.ServerRpc;
|
||||
import com.vaadin.shared.data.DataCommunicatorConstants;
|
||||
import com.vaadin.shared.ui.Connect;
|
||||
import elemental.json.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 15.01.2018
|
||||
*/
|
||||
@Connect(MyGrid.MiddleClickExtension.class)
|
||||
public class GridMiddleClickExtensionConnector extends MiddleClickExtensionConnector {
|
||||
|
||||
@Override
|
||||
protected void middleClick(MouseDownEvent event) {
|
||||
CellReference<JsonObject> cell = getParent().getWidget().getEventCell();
|
||||
if(cell != null) {
|
||||
getRpcProxy(Rpc.class).middleClick(cell.getRowIndex(), cell.getRow().getString(DataCommunicatorConstants.KEY), getParent().getColumnId(cell.getColumn()),
|
||||
MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridConnector getParent() {
|
||||
return (GridConnector) super.getParent();
|
||||
}
|
||||
|
||||
public interface Rpc extends ServerRpc {
|
||||
void middleClick(int rowIndex, String rowKey, String columnInternalId, MouseEventDetails details);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.client.mytabsheet;
|
||||
|
||||
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.event.dom.client.MouseDownEvent;
|
||||
import com.google.gwt.regexp.shared.MatchResult;
|
||||
import com.google.gwt.regexp.shared.RegExp;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.vaadin.client.MouseEventDetailsBuilder;
|
||||
import com.vaadin.client.ServerConnector;
|
||||
import com.vaadin.client.extensions.AbstractExtensionConnector;
|
||||
import com.vaadin.client.ui.tabsheet.TabsheetConnector;
|
||||
import com.vaadin.shared.MouseEventDetails;
|
||||
import com.vaadin.shared.communication.ServerRpc;
|
||||
import com.vaadin.shared.ui.Connect;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.06.18
|
||||
*/
|
||||
@Connect(MyTabSheet.MiddleClickExtension.class)
|
||||
public class TabSheetMiddleClickExtensionConnector extends AbstractExtensionConnector {
|
||||
@Override
|
||||
protected void extend(ServerConnector target) {
|
||||
getParent().getWidget().addDomHandler(event -> {
|
||||
if (event.getNativeButton() == NativeEvent.BUTTON_MIDDLE) {
|
||||
event.preventDefault();
|
||||
Element element = Element.as(event.getNativeEvent().getEventTarget());
|
||||
String id = getParent().getWidget().getSubPartName(DOM.asOld(element));
|
||||
if (id != null) {
|
||||
MatchResult result = RegExp.compile("tab\\[(\\d)\\]").exec(id);
|
||||
if (result != null) {
|
||||
getRpcProxy(Rpc.class).middleClick(Integer.parseInt(result.getGroup(1)),
|
||||
MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, MouseDownEvent.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabsheetConnector getParent() {
|
||||
return (TabsheetConnector) super.getParent();
|
||||
}
|
||||
|
||||
public interface Rpc extends ServerRpc {
|
||||
void middleClick(int tabIndex, MouseEventDetails details);
|
||||
}
|
||||
}
|
|
@ -20,10 +20,10 @@ import com.querydsl.core.types.Expression;
|
|||
import com.querydsl.core.types.Order;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import com.vaadin.data.provider.AbstractBackEndDataProvider;
|
||||
import com.vaadin.data.provider.Query;
|
||||
import com.vaadin.data.provider.QuerySortOrder;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.flow.data.provider.AbstractBackEndDataProvider;
|
||||
import com.vaadin.flow.data.provider.Query;
|
||||
import com.vaadin.flow.data.provider.QuerySortOrder;
|
||||
import com.vaadin.flow.data.provider.SortDirection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public interface HasI18n {
|
||||
I18N getI18n();
|
||||
}
|
18
src/main/java/com/faendir/acra/i18n/I18NConfiguration.java
Normal file
18
src/main/java/com/faendir/acra/i18n/I18NConfiguration.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.flow.i18n.I18NProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 10.11.18
|
||||
*/
|
||||
@Configuration
|
||||
public class I18NConfiguration {
|
||||
|
||||
@Bean
|
||||
public I18NProvider i18NProvider() {
|
||||
return new ResourceBundleI18NProvider("i18n.com.faendir.acra.messages");
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.Accordion;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.TabSheet;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nAccordion extends Accordion implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final Map<TabSheet.Tab, Pair<String, Object[]>> tabCaptionIds;
|
||||
|
||||
public I18nAccordion(I18N i18n) {
|
||||
this.i18n = i18n;
|
||||
tabCaptionIds = new HashMap<>();
|
||||
}
|
||||
|
||||
public Tab addTab(Component c, String captionId, Object... parameters) {
|
||||
Tab tab = super.addTab(c, i18n.get(captionId, parameters));
|
||||
tabCaptionIds.put(tab, Pair.of(captionId, parameters));
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
tabCaptionIds.forEach((tab, pair) -> tab.setCaption(i18n.get(pair.getFirst(), locale, pair.getSecond())));
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.Button;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nButton extends Button implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nButton(ClickListener clickListener, I18N i18n, String captionId, Object... params) {
|
||||
this(i18n, captionId, params);
|
||||
addClickListener(clickListener);
|
||||
}
|
||||
|
||||
public I18nButton(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nCheckBox extends CheckBox implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nCheckBox(boolean value, I18N i18n, String captionId, Object... params) {
|
||||
this(i18n, captionId, params);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
public I18nCheckBox(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nComboBox<T> extends ComboBox<T> implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nComboBox(Collection<T> values, I18N i18n, String captionId, Object... params) {
|
||||
this(i18n, captionId, params);
|
||||
setItems(values);
|
||||
}
|
||||
|
||||
public I18nComboBox(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.vaadin.spring.i18n.MessageProvider;
|
||||
import org.vaadin.spring.i18n.ResourceBundleMessageProvider;
|
||||
import org.vaadin.spring.i18n.annotation.EnableI18N;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
@EnableI18N
|
||||
@Configuration
|
||||
public class I18nConfiguration {
|
||||
|
||||
@Bean
|
||||
MessageProvider messageProvider() {
|
||||
return new ResourceBundleMessageProvider("i18n.com.faendir.acra.messages");
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import org.vaadin.risto.stepper.IntStepper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nIntStepper extends IntStepper implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nIntStepper(int value) {
|
||||
this(value, null, null);
|
||||
}
|
||||
|
||||
public I18nIntStepper(int value, I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
setValue(value);
|
||||
if (i18n != null) {
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
if (i18n != null) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.ui.Label;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nLabel extends Label implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nLabel(ContentMode contentMode, I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
setContentMode(contentMode);
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
public I18nLabel(I18N i18n, String captionId, Object... params) {
|
||||
this(ContentMode.TEXT, i18n, captionId, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setValue(i18n.get(captionId, locale, params));
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.LoginForm;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nLoginForm extends LoginForm implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String usernameId;
|
||||
private final String passwordId;
|
||||
private final String loginId;
|
||||
|
||||
public I18nLoginForm(I18N i18n, String usernameId, String passwordId, String loginId) {
|
||||
this.i18n = i18n;
|
||||
this.usernameId = usernameId;
|
||||
this.passwordId = passwordId;
|
||||
this.loginId = loginId;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setUsernameCaption(i18n.get(usernameId));
|
||||
setPasswordCaption(i18n.get(passwordId));
|
||||
setLoginButtonCaption(i18n.get(loginId));
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.server.Resource;
|
||||
import com.vaadin.ui.MenuBar;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nMenuBar extends MenuBar implements Translatable {
|
||||
private final I18N i18n;
|
||||
|
||||
public I18nMenuBar(I18N i18n) {
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
public I18nMenuItem addItem(Resource icon, String captionId, Object... params) {
|
||||
I18nMenuItem item = new I18nMenuItem(icon, null, i18n, captionId, params);
|
||||
getItems().add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
getItems().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale));
|
||||
}
|
||||
|
||||
public class I18nMenuItem extends MenuItem implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
private I18nMenuItem(@Nullable Resource icon, Command command, I18N i18n, String captionId, Object... params) {
|
||||
//noinspection ConstantConditions
|
||||
super(i18n.get(captionId, params), icon, command);
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public I18nMenuItem addItem(Command command, String captionId, Object... params) {
|
||||
I18nMenuItem item = new I18nMenuItem(null, command, i18n, captionId, params);
|
||||
if(getChildren() == null) {
|
||||
removeItem(addItem(""));
|
||||
}
|
||||
getChildren().add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setText(i18n.get(captionId, params));
|
||||
if(getChildren() != null) {
|
||||
getChildren().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Panel;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nPanel extends Panel implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nPanel(Component content, I18N i18n, String captionId, Object... params) {
|
||||
this(i18n, captionId, params);
|
||||
setContent(content);
|
||||
}
|
||||
|
||||
public I18nPanel(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.PasswordField;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nPasswordField extends PasswordField implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nPasswordField(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.Slider;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nSlider extends Slider implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nSlider(int min, int max, I18N i18n, String captionId, Object... params) {
|
||||
super(min, max);
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.TextArea;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 16.08.18
|
||||
*/
|
||||
public class I18nTextArea extends TextArea implements Translatable {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nTextArea(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.ui.TextField;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.08.18
|
||||
*/
|
||||
public class I18nTextField extends TextField implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public I18nTextField(String value, I18N i18n, String captionId, Object... params) {
|
||||
this(i18n, captionId, params);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
public I18nTextField(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.vaadin.spring.i18n;
|
||||
package com.faendir.acra.i18n;
|
||||
|
||||
import com.vaadin.flow.i18n.I18NProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -8,57 +9,46 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MessageProvider} that reads messages
|
||||
* from {@link ResourceBundle}s with a specific base name.
|
||||
* Implementation of {@link I18NProvider} that reads messages from {@link ResourceBundle}s with a specific base name.
|
||||
*
|
||||
* @author Petter Holmström (petter@vaadin.com)
|
||||
* @author lukas
|
||||
* @since 10.11.18
|
||||
*/
|
||||
public class ResourceBundleMessageProvider implements MessageProvider {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleMessageProvider.class);
|
||||
public class ResourceBundleI18NProvider implements I18NProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleI18NProvider.class);
|
||||
|
||||
private final String baseName;
|
||||
private final String encoding;
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and UTF-8 encoding.
|
||||
* Creates a new {@code ResourceBundleI18NProvider} with the given base name and UTF-8 encoding.
|
||||
*
|
||||
* @param baseName the base name to use, must not be {@code null}.
|
||||
*/
|
||||
public ResourceBundleMessageProvider(String baseName) {
|
||||
public ResourceBundleI18NProvider(String baseName) {
|
||||
this(baseName, "UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and encoding.
|
||||
* Creates a new {@code ResourceBundleI18NProvider} with the given base name and encoding.
|
||||
*
|
||||
* @param baseName the base name to use, must not be {@code null}.
|
||||
* @param encoding the encoding to use when reading the resource bundle, must not be {@code null}.
|
||||
*/
|
||||
public ResourceBundleMessageProvider(String baseName, String encoding) {
|
||||
public ResourceBundleI18NProvider(String baseName, String encoding) {
|
||||
this.baseName = baseName;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageFormat resolveCode(String s, Locale locale) {
|
||||
final ResourceBundle resourceBundle = getResourceBundle(locale);
|
||||
final String message = getString(resourceBundle, s);
|
||||
return getMessageFormat(message, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
ResourceBundle.clearCache(this.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
private ResourceBundle getResourceBundle(Locale locale) {
|
||||
try {
|
||||
return ResourceBundle.getBundle(baseName, locale, this.getClass().getClassLoader(), new MessageControl());
|
||||
|
@ -79,34 +69,33 @@ public class ResourceBundleMessageProvider implements MessageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private static MessageFormat getMessageFormat(String message, Locale locale) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return new MessageFormat(message, locale);
|
||||
@Override
|
||||
public List<Locale> getProvidedLocales() {
|
||||
return Stream.of(Locale.getAvailableLocales()).filter(locale -> getResourceBundle(locale) != null).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslation(String key, Locale locale, Object... params) {
|
||||
final ResourceBundle resourceBundle = getResourceBundle(locale);
|
||||
final String message = getString(resourceBundle, key);
|
||||
return message == null ? null : String.format(message, params);
|
||||
}
|
||||
|
||||
private class MessageControl extends ResourceBundle.Control {
|
||||
@Override
|
||||
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader,
|
||||
boolean reload) throws IllegalAccessException, InstantiationException, IOException {
|
||||
boolean reload) throws IllegalAccessException, InstantiationException, IOException {
|
||||
if ("java.properties".equals(format)) {
|
||||
final String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
|
||||
final InputStream stream = loader.getResourceAsStream(resourceName);
|
||||
if (stream == null) {
|
||||
return null; // Not found
|
||||
}
|
||||
Reader reader = null;
|
||||
try {
|
||||
reader = new InputStreamReader(stream, encoding);
|
||||
try (Reader reader = new InputStreamReader(stream, encoding)) {
|
||||
return new PropertyResourceBundle(reader);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
stream.close();
|
||||
throw ex;
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super.newBundle(baseName, locale, format, loader, reload);
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
package com.faendir.acra.security;
|
||||
|
||||
import com.vaadin.server.VaadinService;
|
||||
import com.vaadin.server.VaadinSession;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
|
|
|
@ -13,18 +13,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.service;
|
||||
|
||||
import com.faendir.acra.model.Report;
|
||||
import com.talanlabs.avatargenerator.Avatar;
|
||||
import com.talanlabs.avatargenerator.IdenticonAvatar;
|
||||
import com.vaadin.server.ExternalResource;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
|
@ -39,9 +40,9 @@ public class AvatarService {
|
|||
avatar = IdenticonAvatar.newAvatarBuilder().size(32, 32).build();
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "avatars", key = "#report.installationId")
|
||||
public ExternalResource getAvatar(@NonNull Report report) {
|
||||
//@Cacheable(cacheNames = "avatars", key = "#report.installationId")
|
||||
public Component getAvatar(@NonNull Report report) {
|
||||
byte[] bytes = avatar.createAsPngBytes(report.getInstallationId().hashCode());
|
||||
return new ExternalResource("data:image/png;base64," + Base64Utils.encodeToString(bytes));
|
||||
return new Image(new StreamResource("", () -> new ByteArrayInputStream(bytes)), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ import javax.persistence.EntityManager;
|
|||
import javax.validation.constraints.Size;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collection;
|
||||
|
@ -392,7 +393,7 @@ public class DataService implements Serializable {
|
|||
|
||||
@NonNull
|
||||
@PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)")
|
||||
public <T> Map<T, Long> countReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression<T> select) {
|
||||
public <T> Map<T, Long> countReports(@NonNull App app, @Nullable Predicate where, @NonNull Expression<T> select) {
|
||||
List<Tuple> result = ((JPAQuery<?>) new JPAQuery<>(entityManager)).from(report)
|
||||
.where(report.stacktrace.bug.app.eq(app).and(where))
|
||||
.groupBy(select)
|
||||
|
@ -442,7 +443,7 @@ public class DataService implements Serializable {
|
|||
if (!id.startsWith("_design")) {
|
||||
total++;
|
||||
try {
|
||||
JSONObject report = new JSONObject(IOUtils.toString(db.getAsStream(id)));
|
||||
JSONObject report = new JSONObject(IOUtils.toString(db.getAsStream(id), StandardCharsets.UTF_8));
|
||||
fixStringIsArray(report, ReportField.STACK_TRACE);
|
||||
fixStringIsArray(report, ReportField.LOGCAT);
|
||||
createNewReport(user.getUsername(), report.toString(), Collections.emptyList());
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui;
|
||||
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.I18nLoginForm;
|
||||
import com.faendir.acra.i18n.I18nMenuBar;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.User;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.navigation.Path;
|
||||
import com.faendir.acra.ui.view.user.ChangePasswordView;
|
||||
import com.faendir.acra.ui.view.user.UserManagerView;
|
||||
import com.vaadin.annotations.Theme;
|
||||
import com.vaadin.annotations.Viewport;
|
||||
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;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.spring.annotation.SpringUI;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.LoginForm;
|
||||
import com.vaadin.ui.Notification;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.vaadin.ui.themes.DarkAcraTheme;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.TranslatableUI;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 22.03.2017
|
||||
*/
|
||||
@SpringUI
|
||||
@Theme(AcraTheme.THEME_NAME)
|
||||
@Widgetset("com.faendir.acra.AppWidgetset")
|
||||
@Viewport("width=device-width, initial-scale=1")
|
||||
public class BackendUI extends TranslatableUI {
|
||||
private static final String DARK_THEME = "dark";
|
||||
@NonNull private final AuthenticationManager authenticationManager;
|
||||
@NonNull private final ApplicationContext applicationContext;
|
||||
@NonNull private final I18N i18n;
|
||||
@NonNull private final Panel content;
|
||||
@NonNull private final Path path;
|
||||
|
||||
@Autowired
|
||||
public BackendUI(@NonNull AuthenticationManager authenticationManager, @NonNull ApplicationContext applicationContext, @NonNull I18N i18n) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.applicationContext = applicationContext;
|
||||
this.i18n = i18n;
|
||||
content = new Panel();
|
||||
content.setSizeFull();
|
||||
content.addStyleNames(AcraTheme.NO_PADDING, AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
|
||||
path = new Path();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initUI(VaadinRequest request) {
|
||||
if (isDarkTheme()) {
|
||||
setTheme(DarkAcraTheme.THEME_NAME);
|
||||
}
|
||||
if (SecurityUtils.isLoggedIn()) {
|
||||
showMain();
|
||||
} else {
|
||||
showLogin();
|
||||
}
|
||||
setLocale(request.getLocale());
|
||||
}
|
||||
|
||||
private void login(@NonNull String username, @NonNull String password) {
|
||||
try {
|
||||
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
|
||||
if (!token.getAuthorities().contains(User.Role.USER)) {
|
||||
throw new InsufficientAuthenticationException(i18n.get(Messages.MISSING_ROLE));
|
||||
}
|
||||
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
showMain();
|
||||
getPushConfiguration().setPushMode(PushMode.AUTOMATIC);
|
||||
} catch (AuthenticationException ex) {
|
||||
Notification.show(i18n.get(Messages.LOGIN_FAILED), Notification.Type.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
getPushConfiguration().setPushMode(PushMode.DISABLED);
|
||||
SecurityContextHolder.clearContext();
|
||||
getPage().reload();
|
||||
getSession().close();
|
||||
}
|
||||
|
||||
private void showLogin() {
|
||||
LoginForm loginForm = new I18nLoginForm(i18n, Messages.USERNAME, Messages.PASSWORD, Messages.LOGIN);
|
||||
loginForm.addLoginListener(event -> login(event.getLoginParameter("username"), event.getLoginParameter("password")));
|
||||
VerticalLayout layout = new VerticalLayout(loginForm);
|
||||
layout.setSizeFull();
|
||||
layout.setComponentAlignment(loginForm, Alignment.MIDDLE_CENTER);
|
||||
setContent(layout);
|
||||
}
|
||||
|
||||
private void showMain() {
|
||||
NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class);
|
||||
|
||||
I18nMenuBar menuBar = new I18nMenuBar(i18n);
|
||||
I18nMenuBar.I18nMenuItem user = menuBar.addItem(VaadinIcons.USER, Messages.ONE_ARG, SecurityUtils.getUsername());
|
||||
I18nMenuBar.I18nMenuItem theme = user.addItem(e -> getPage().setLocation(UriComponentsBuilder.fromUri(getPage().getLocation())
|
||||
.replaceQueryParam(DARK_THEME, e.isChecked())
|
||||
.build()
|
||||
.toUri()), Messages.DARK_THEME);
|
||||
theme.setCheckable(true);
|
||||
theme.setChecked(isDarkTheme());
|
||||
user.addSeparator();
|
||||
if (SecurityUtils.hasRole(User.Role.ADMIN)) {
|
||||
user.addItem(e -> navigationManager.cleanNavigateTo(UserManagerView.class), Messages.USER_MANAGER);
|
||||
user.addSeparator();
|
||||
}
|
||||
user.addItem(e -> navigationManager.cleanNavigateTo(ChangePasswordView.class), Messages.CHANGE_PASSWORD);
|
||||
user.addItem(e -> logout(), Messages.LOGOUT);
|
||||
|
||||
HorizontalLayout header = new HorizontalLayout(path, menuBar);
|
||||
header.setExpandRatio(path, 1);
|
||||
header.setWidth(100, Unit.PERCENTAGE);
|
||||
header.addStyleNames(AcraTheme.PADDING_TOP, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM, AcraTheme.BACKGROUND_HEADER);
|
||||
|
||||
HorizontalLayout footer = new HorizontalLayout();
|
||||
footer.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
|
||||
Label footerLabel = new I18nLabel(ContentMode.HTML, i18n, Messages.FOOTER);
|
||||
footerLabel.setWidth(100, Unit.PERCENTAGE);
|
||||
footerLabel.addStyleName(AcraTheme.CENTER_TEXT);
|
||||
footer.addComponent(footerLabel);
|
||||
footer.setSpacing(false);
|
||||
footer.setWidth(100, Unit.PERCENTAGE);
|
||||
footer.addStyleNames(AcraTheme.BACKGROUND_FOOTER, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_TOP, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM);
|
||||
VerticalLayout root = new VerticalLayout(header, content, footer);
|
||||
root.setExpandRatio(content, 1);
|
||||
root.setSpacing(false);
|
||||
root.setSizeFull();
|
||||
root.addStyleName(AcraTheme.NO_PADDING);
|
||||
setContent(root);
|
||||
}
|
||||
|
||||
private boolean isDarkTheme() {
|
||||
return Optional.ofNullable(UriComponentsBuilder.fromUri(getPage().getLocation()).build().getQueryParams().get(DARK_THEME)).map(list -> list.contains("true")).orElse(false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@UIScope
|
||||
@Bean
|
||||
public Panel mainView() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@UIScope
|
||||
@Bean
|
||||
public Path mainPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.annotation;
|
||||
|
||||
import com.faendir.acra.model.Permission;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 21.06.2017
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface RequiresAppPermission {
|
||||
@NonNull Permission.Level value();
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.annotation;
|
||||
|
||||
import com.faendir.acra.model.User;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 31.05.2017
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface RequiresRole {
|
||||
@NonNull User.Role value();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 11.10.18
|
||||
*/
|
||||
public interface ActiveChildAware<C, P> {
|
||||
void setActiveChild(C child, P parameter);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.rest.RestReportInterface;
|
||||
import com.faendir.acra.ui.view.Overview;
|
||||
import com.faendir.acra.util.PlainTextUser;
|
||||
import com.vaadin.flow.component.Text;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
||||
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 09.11.18
|
||||
*/
|
||||
public class ConfigurationLabel extends Text implements LocaleChangeObserver {
|
||||
private final PlainTextUser user;
|
||||
|
||||
public ConfigurationLabel(@NonNull PlainTextUser user) {
|
||||
super("");
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localeChange(LocaleChangeEvent event) {
|
||||
setText(getTranslation(Messages.CONFIGURATION_LABEL, UI.getCurrent().getRouter().getUrl(Overview.class), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword()));
|
||||
}
|
||||
}
|
74
src/main/java/com/faendir/acra/ui/base/HasRoute.java
Normal file
74
src/main/java/com/faendir/acra/ui/base/HasRoute.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.router.AfterNavigationEvent;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.Location;
|
||||
import com.vaadin.flow.router.NavigationTrigger;
|
||||
import com.vaadin.flow.router.Router;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 18.10.18
|
||||
*/
|
||||
public interface HasRoute {
|
||||
@NonNull
|
||||
Path.Element<?> getPathElement();
|
||||
|
||||
@Nullable
|
||||
default Parent<?> getLogicalParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default List<Path.Element<?>> getPathElements(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
|
||||
List<Path.Element<?>> list = new ArrayList<>();
|
||||
list.add(getPathElement());
|
||||
Parent<?> parent = getLogicalParent();
|
||||
if (parent != null) {
|
||||
list.addAll(parent.get(applicationContext, afterNavigationEvent).getPathElements(applicationContext, afterNavigationEvent));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
class Parent<T extends HasRoute> {
|
||||
private final Class<T> parentClass;
|
||||
|
||||
public Parent(Class<T> parentClass) {
|
||||
this.parentClass = parentClass;
|
||||
}
|
||||
|
||||
public T get(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
|
||||
return applicationContext.getBean(parentClass);
|
||||
}
|
||||
}
|
||||
|
||||
class ParametrizedParent<T extends HasRoute & HasUrlParameter<P>, P> extends Parent<T> {
|
||||
private final P parameter;
|
||||
|
||||
public ParametrizedParent(Class<T> parentClass, P parameter) {
|
||||
super(parentClass);
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
|
||||
T t = super.get(applicationContext, afterNavigationEvent);
|
||||
t.setParameter(new BeforeRouteEvent(afterNavigationEvent.getSource(), afterNavigationEvent.getLocation(), afterNavigationEvent.getActiveChain().get(0).getClass()), parameter);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
class BeforeRouteEvent extends BeforeEvent {
|
||||
|
||||
public BeforeRouteEvent(Router router, Location location, Class<?> navigationTarget) {
|
||||
super(router, NavigationTrigger.PAGE_LOAD,location, navigationTarget, UI.getCurrent());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
public
|
||||
interface HasSecureIntParameter extends HasUrlParameter<Integer> {
|
||||
@Override
|
||||
default void setParameter(BeforeEvent event, Integer parameter) {
|
||||
if (SecurityUtils.isLoggedIn()) {
|
||||
setParameterSecure(event, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
void setParameterSecure(BeforeEvent event, Integer parameter);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
public
|
||||
interface HasSecureStringParameter extends HasUrlParameter<String> {
|
||||
@Override
|
||||
default void setParameter(BeforeEvent event, String parameter) {
|
||||
if (SecurityUtils.isLoggedIn()) {
|
||||
setParameterSecure(event, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
void setParameterSecure(BeforeEvent event, String parameter);
|
||||
}
|
149
src/main/java/com/faendir/acra/ui/base/JFreeChartWrapper.java
Normal file
149
src/main/java/com/faendir/acra/ui/base/JFreeChartWrapper.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.HasStyle;
|
||||
import com.vaadin.flow.component.Tag;
|
||||
import com.vaadin.flow.server.InputStreamFactory;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import org.apache.batik.svggen.SVGGraphics2D;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.awt.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 11.10.18
|
||||
*/
|
||||
@Tag("object")
|
||||
public class JFreeChartWrapper extends Component implements HasSize, HasStyle {
|
||||
|
||||
// 809x 500 ~g olden ratio
|
||||
private static final int DEFAULT_WIDTH = 809;
|
||||
private static final int DEFAULT_HEIGHT = 500;
|
||||
|
||||
private final JFreeChart chart;
|
||||
private int graphWidthInPixels = -1;
|
||||
private int graphHeightInPixels = -1;
|
||||
private String aspectRatio = "none";
|
||||
|
||||
public JFreeChartWrapper(JFreeChart chartToBeWrapped) {
|
||||
this.chart = chartToBeWrapped;
|
||||
getElement().setAttribute("type", "image/svg+xml");
|
||||
//getElement().getStyle().set("display", "block");
|
||||
getElement().setAttribute("data", new StreamResource("chart" + System.currentTimeMillis() + ".svg", (InputStreamFactory) () -> {
|
||||
int width = getGraphWidth();
|
||||
int height = getGraphHeight();
|
||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder;
|
||||
try {
|
||||
docBuilder = docBuilderFactory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e1) {
|
||||
throw new RuntimeException(e1);
|
||||
}
|
||||
Document document = docBuilder.newDocument();
|
||||
Element svgelem = document.createElement("svg");
|
||||
document.appendChild(svgelem);
|
||||
// Create an instance of the SVG Generator
|
||||
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
|
||||
|
||||
// draw the chart in the SVG generator
|
||||
chart.draw(svgGenerator, new Rectangle(width, height));
|
||||
Element el = svgGenerator.getRoot();
|
||||
el.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height + "");
|
||||
el.setAttributeNS(null, "style", "width:100%;height:100%;");
|
||||
el.setAttributeNS(null, "preserveAspectRatio", getSvgAspectRatio());
|
||||
// Write svg to buffer
|
||||
try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Writer out = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
|
||||
/*
|
||||
* don't use css, FF3 can'd deal with the result perfectly: wrong font sizes
|
||||
*/
|
||||
boolean useCSS = false;
|
||||
svgGenerator.stream(el, out, useCSS, false);
|
||||
stream.flush();
|
||||
return new ByteArrayInputStream(stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method may be used to tune rendering of the chart when using
|
||||
* relative sizes. Most commonly you should use just use common methods
|
||||
* inherited from {@link HasSize} interface.
|
||||
* <p>
|
||||
* Sets the pixel size of the area where the graph is rendered. Most commonly developer may need to fine tune the value when the {@link JFreeChartWrapper} has a relative size.
|
||||
*
|
||||
* @see JFreeChartWrapper#getGraphWidth()
|
||||
* @see #setSvgAspectRatio(String)
|
||||
*/
|
||||
public void setGraphWidth(int width) {
|
||||
graphWidthInPixels = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method may be used to tune rendering of the chart when using
|
||||
* relative sizes. Most commonly you should use just use common methods
|
||||
* inherited from {@link HasSize} interface.
|
||||
* <p>
|
||||
* Sets the pixel size of the area where the graph is rendered. Most commonly developer may need to fine tune the value when the {@link JFreeChartWrapper} has a relative size.
|
||||
*
|
||||
* @see JFreeChartWrapper#getGraphHeight()
|
||||
* @see #setSvgAspectRatio(String)
|
||||
*/
|
||||
public void setGraphHeight(int height) {
|
||||
graphHeightInPixels = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pixel width into which the graph is rendered. Unless explicitly
|
||||
* set, the value is derived from the components size, except when the
|
||||
* component has relative size.
|
||||
*/
|
||||
public int getGraphWidth() {
|
||||
if (graphWidthInPixels > 0) {
|
||||
return graphWidthInPixels;
|
||||
}
|
||||
return DEFAULT_WIDTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pixel height into which the graph is rendered. Unless explicitly
|
||||
* set, the value is derived from the components size, except when the
|
||||
* component has relative size.
|
||||
*/
|
||||
public int getGraphHeight() {
|
||||
if (graphHeightInPixels > 0) {
|
||||
return graphHeightInPixels;
|
||||
}
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
public String getSvgAspectRatio() {
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* See SVG spec from W3 for more information.
|
||||
* Default is "none" (stretch), another common value is "xMidYMid" (stretch
|
||||
* proportionally, align middle of the area).
|
||||
*/
|
||||
public void setSvgAspectRatio(String svgAspectRatioSetting) {
|
||||
aspectRatio = svgAspectRatioSetting;
|
||||
}
|
||||
}
|
117
src/main/java/com/faendir/acra/ui/base/MyGrid.java
Normal file
117
src/main/java/com/faendir/acra/ui/base/MyGrid.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.dataprovider.QueryDslDataProvider;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.grid.ItemClickEvent;
|
||||
import com.vaadin.flow.data.renderer.Renderer;
|
||||
import com.vaadin.flow.data.selection.SelectionListener;
|
||||
import com.vaadin.flow.function.ValueProvider;
|
||||
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
||||
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 13.07.18
|
||||
*/
|
||||
public class MyGrid<T> extends Composite<Grid<T>> implements LocaleChangeObserver, HasSize {
|
||||
private final QueryDslDataProvider<T> dataProvider;
|
||||
private final Map<Grid.Column<T>, Pair<String, Object[]>> columnCaptions;
|
||||
|
||||
public MyGrid(QueryDslDataProvider<T> dataProvider) {
|
||||
this.dataProvider = dataProvider;
|
||||
getContent().setDataProvider(dataProvider);
|
||||
getContent().setSizeFull();
|
||||
getContent().setMultiSort(true);
|
||||
getContent().setColumnReorderingAllowed(true);
|
||||
columnCaptions = new HashMap<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Grid.Column<T> addColumn(@NonNull ValueProvider<T, ?> valueProvider, @NonNull String captionId, Object... params) {
|
||||
return setupColumn(getContent().addColumn(valueProvider), captionId, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Grid.Column<T> addColumn(@NonNull ValueProvider<T, ?> valueProvider, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
|
||||
return setupSortableColumn(addColumn(valueProvider, captionId, params), sort);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer) {
|
||||
return getContent().addColumn(renderer);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer, @NonNull String captionId, Object... params) {
|
||||
return setupColumn(getContent().addColumn(renderer), captionId, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
|
||||
return setupSortableColumn(addColumn(renderer, captionId, params), sort);
|
||||
}
|
||||
|
||||
private Grid.Column<T> setupColumn(@NonNull Grid.Column<T> column, @NonNull String captionId, Object... params) {
|
||||
String caption = getTranslation(captionId, params);
|
||||
column = column.setHeader(caption).setResizable(true).setFlexGrow(0).setWidth(Math.max(50, caption.length() * 10 + 20) + "px");
|
||||
columnCaptions.put(column, Pair.of(captionId, params));
|
||||
return column;
|
||||
}
|
||||
|
||||
private Grid.Column<T> setupSortableColumn(@NonNull Grid.Column<T> column, @NonNull Expression<? extends Comparable> sort) {
|
||||
column.setId(dataProvider.addSortable(sort));
|
||||
column.setSortable(true);
|
||||
return column;
|
||||
}
|
||||
|
||||
public Registration addItemClickListener(ComponentEventListener<ItemClickEvent<T>> listener) {
|
||||
return getContent().addItemClickListener(listener);
|
||||
}
|
||||
|
||||
public <C extends Component & HasUrlParameter<R>, R> void addOnClickNavigation(Class<C> target, Function<T, R> parameterTransformer) {
|
||||
getContent().addItemClickListener(e -> getUI().ifPresent(e.getButton() == 1 ? (ui -> ui.getPage().executeJavaScript("window.open(\"" + ui.getRouter().getUrl(target, parameterTransformer.apply(e.getItem())) + "\", \"blank\", \"\");")) : (ui -> ui.navigate(target, parameterTransformer.apply(e.getItem())))));
|
||||
}
|
||||
|
||||
public Registration addSelectionListener(SelectionListener<Grid<T>, T> listener) {
|
||||
return getContent().addSelectionListener(listener);
|
||||
}
|
||||
|
||||
public void setSelectionMode(Grid.SelectionMode selectionMode) {
|
||||
getContent().setSelectionMode(selectionMode);
|
||||
}
|
||||
|
||||
public void deselectAll() {
|
||||
getContent().deselectAll();
|
||||
}
|
||||
|
||||
public QueryDslDataProvider<T> getDataProvider() {
|
||||
return dataProvider;
|
||||
}
|
||||
|
||||
public Set<T> getSelectedItems() {
|
||||
return getContent().getSelectedItems();
|
||||
}
|
||||
|
||||
public void setHeightToRows() {
|
||||
getContent().setHeightByRows(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localeChange(LocaleChangeEvent event) {
|
||||
columnCaptions.forEach((column, caption) -> column.setHeader(getTranslation(caption.getFirst(), caption.getSecond())));
|
||||
}
|
||||
}
|
49
src/main/java/com/faendir/acra/ui/base/ParentLayout.java
Normal file
49
src/main/java/com/faendir/acra/ui/base/ParentLayout.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.HasElement;
|
||||
import com.vaadin.flow.router.RouterLayout;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 13.07.18
|
||||
*/
|
||||
public class ParentLayout extends FlexLayout implements RouterLayout {
|
||||
private Component content;
|
||||
private HasElement routerRoot;
|
||||
|
||||
public ParentLayout(HasElement routerRoot) {
|
||||
this();
|
||||
this.routerRoot = routerRoot;
|
||||
}
|
||||
|
||||
public ParentLayout() {
|
||||
this.routerRoot = this;
|
||||
}
|
||||
|
||||
private void setContent(HasElement content, HasElement root) {
|
||||
if (root == this) {
|
||||
this.content = content instanceof Component ? (Component) content : null;
|
||||
}
|
||||
root.getElement().removeAllChildren();
|
||||
root.getElement().appendChild(content.getElement());
|
||||
}
|
||||
|
||||
public HasElement getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(HasElement content) {
|
||||
setContent(content, this);
|
||||
}
|
||||
|
||||
public void setRouterRoot(HasElement routerRoot) {
|
||||
this.routerRoot = routerRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showRouterLayoutContent(HasElement content) {
|
||||
setContent(content, routerRoot);
|
||||
}
|
||||
}
|
131
src/main/java/com/faendir/acra/ui/base/Path.java
Normal file
131
src/main/java/com/faendir/acra/ui/base/Path.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.faendir.acra.ui.component.Label;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.vaadin.flow.component.AttachEvent;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.DetachEvent;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.router.AfterNavigationEvent;
|
||||
import com.vaadin.flow.router.AfterNavigationListener;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.RouterLink;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 18.10.18
|
||||
*/
|
||||
public class Path extends Composite<FlexLayout> implements AfterNavigationListener {
|
||||
private final ApplicationContext applicationContext;
|
||||
private Registration registration;
|
||||
|
||||
public Path(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
getContent().setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterNavigation(AfterNavigationEvent event) {
|
||||
getContent().removeAll();
|
||||
List<Element<?>> elements = event.getActiveChain().stream().filter(HasRoute.class::isInstance).map(HasRoute.class::cast).flatMap(e -> e.getPathElements(applicationContext, event).stream()).collect(Collectors.toList());
|
||||
if (!elements.isEmpty()) {
|
||||
Collections.reverse(elements);
|
||||
getContent().add(elements.remove(0).toComponent());
|
||||
for (Element<?> element : elements) {
|
||||
getContent().add(VaadinIcon.CARET_RIGHT.create(), element.toComponent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onAttach(AttachEvent attachEvent) {
|
||||
super.onAttach(attachEvent);
|
||||
registration = attachEvent.getUI().addAfterNavigationListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(DetachEvent detachEvent) {
|
||||
registration.remove();
|
||||
super.onDetach(detachEvent);
|
||||
}
|
||||
|
||||
public static class Element<T extends Component> {
|
||||
final Class<T> target;
|
||||
final String titleId;
|
||||
final Object[] params;
|
||||
|
||||
public Element(Class<T> target, String titleId, Object... params) {
|
||||
this.target = target;
|
||||
this.titleId = titleId;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
protected RouterLink createRouterLink() {
|
||||
return new RouterLink("", target);
|
||||
}
|
||||
|
||||
protected Component createContent() {
|
||||
Translatable<Label> p = Translatable.createLabel(titleId, params);
|
||||
p.setMargin(0, HasSize.Unit.PIXEL);
|
||||
p.getStyle().set("padding-top", "2px");
|
||||
return p;
|
||||
}
|
||||
|
||||
public Component toComponent() {
|
||||
RouterLink routerLink = createRouterLink();
|
||||
Component component = createContent();
|
||||
routerLink.add(component);
|
||||
routerLink.getStyle().set("line-height", "32px");
|
||||
routerLink.getStyle().set("padding", "1rem");
|
||||
routerLink.getStyle().set("font-size", "130%");
|
||||
routerLink.getStyle().set("text-decoration","none");
|
||||
routerLink.getStyle().set("color","inherit");
|
||||
return routerLink;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ParametrizedTextElement<T extends Component & HasUrlParameter<P>, P> extends Element<T> {
|
||||
final P parameter;
|
||||
|
||||
public ParametrizedTextElement(Class<T> target, P parameter, String titleId, Object... params) {
|
||||
super(target, titleId, params);
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RouterLink createRouterLink() {
|
||||
return new RouterLink("", target, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImageElement<T extends Component> extends Element<T> {
|
||||
private final String src;
|
||||
|
||||
public ImageElement(Class<T> target, String src, String titleId, Object... params) {
|
||||
super(target, titleId, params);
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component createContent() {
|
||||
Translatable<Image> image = Translatable.createImage(src, titleId, params);
|
||||
image.setHeight(32, HasSize.Unit.PIXEL);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/com/faendir/acra/ui/base/ReportList.java
Normal file
48
src/main/java/com/faendir/acra/ui/base/ReportList.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package com.faendir.acra.ui.base;
|
||||
|
||||
import com.faendir.acra.dataprovider.QueryDslDataProvider;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.model.Report;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.AvatarService;
|
||||
import com.faendir.acra.ui.base.popup.Popup;
|
||||
import com.faendir.acra.ui.view.report.ReportView;
|
||||
import com.faendir.acra.util.TimeSpanRenderer;
|
||||
import com.vaadin.flow.component.ClickEvent;
|
||||
import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.09.18
|
||||
*/
|
||||
public class ReportList extends MyGrid<Report>{
|
||||
public ReportList(@NonNull App app, @NonNull QueryDslDataProvider<Report> dataProvider, @NonNull AvatarService avatarService, @NonNull Consumer<Report> reportDeleter) {
|
||||
super(dataProvider);
|
||||
setSelectionMode(Grid.SelectionMode.NONE);
|
||||
addColumn(new ComponentRenderer<>(avatarService::getAvatar) , QReport.report.installationId, Messages.USER);
|
||||
addColumn(new TimeSpanRenderer<>(Report::getDate), QReport.report.date, Messages.DATE);
|
||||
addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, Messages.APP_VERSION);
|
||||
addColumn(Report::getAndroidVersion, QReport.report.androidVersion, Messages.ANDROID_VERSION);
|
||||
addColumn(Report::getPhoneModel, QReport.report.phoneModel, Messages.DEVICE);
|
||||
addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, Messages.STACKTRACE).setFlexGrow(1);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
addColumn(new ComponentRenderer<>(report -> new Button(new Icon(VaadinIcon.TRASH),
|
||||
(ComponentEventListener<ClickEvent<Button>>) event -> new Popup().setTitle(Messages.DELETE_REPORT_CONFIRM).addYesNoButtons(p -> {
|
||||
reportDeleter.accept(report);
|
||||
getDataProvider().refreshAll();
|
||||
}, true).show())));
|
||||
}
|
||||
addOnClickNavigation(ReportView.class, Report::getId);
|
||||
}
|
||||
}
|
|
@ -14,26 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.popup;
|
||||
package com.faendir.acra.ui.base.popup;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.FormLayout;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.Window;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.HasSize;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -41,22 +37,21 @@ import java.util.function.Consumer;
|
|||
* @author Lukas
|
||||
* @since 19.12.2017
|
||||
*/
|
||||
public class Popup extends Window implements Translatable {
|
||||
public class Popup extends Dialog {
|
||||
private final List<Component> components;
|
||||
private final Map<ValidatedField<?, ?>, Pair<Boolean, ValidatedField.Listener>> fields;
|
||||
private final List<Button> buttons;
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
private final List<Translatable<Button>> buttons;
|
||||
|
||||
public Popup(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
public Popup() {
|
||||
components = new ArrayList<>();
|
||||
fields = new HashMap<>();
|
||||
buttons = new ArrayList<>();
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Popup setTitle(@NonNull String titleId, Object... params) {
|
||||
components.add(0, Translatable.createText(titleId, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -66,18 +61,18 @@ public class Popup extends Window implements Translatable {
|
|||
|
||||
@NonNull
|
||||
public Popup addCreateButton(@NonNull Consumer<Popup> onCreateAction, boolean closeAfter) {
|
||||
buttons.add(new I18nButton(event -> {
|
||||
buttons.add(Translatable.createButton(event -> {
|
||||
onCreateAction.accept(this);
|
||||
if (closeAfter) {
|
||||
close();
|
||||
}
|
||||
}, i18n, Messages.CREATE));
|
||||
}, Messages.CREATE));
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Popup addCloseButton() {
|
||||
buttons.add(new I18nButton(event -> close(), i18n, Messages.CLOSE));
|
||||
buttons.add(Translatable.createButton(event -> close(), Messages.CLOSE));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -88,13 +83,13 @@ public class Popup extends Window implements Translatable {
|
|||
|
||||
@NonNull
|
||||
public Popup addYesNoButtons(@NonNull Consumer<Popup> onYesAction, boolean closeAfter) {
|
||||
buttons.add(new I18nButton(event -> {
|
||||
buttons.add(Translatable.createButton(event -> {
|
||||
onYesAction.accept(this);
|
||||
if (closeAfter) {
|
||||
close();
|
||||
}
|
||||
}, i18n, Messages.YES));
|
||||
buttons.add(new I18nButton(event -> close(), i18n, Messages.NO));
|
||||
}, Messages.YES));
|
||||
buttons.add(Translatable.createButton(event -> close(), Messages.NO));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -134,28 +129,30 @@ public class Popup extends Window implements Translatable {
|
|||
} else if (buttons.size() > 1) {
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||
components.add(buttonLayout);
|
||||
buttons.forEach(buttonLayout::addComponent);
|
||||
buttons.forEach(button -> button.setWidth(100, Unit.PERCENTAGE));
|
||||
buttons.forEach(buttonLayout::add);
|
||||
buttons.forEach(com.faendir.acra.ui.component.HasSize::setWidthFull);
|
||||
}
|
||||
components.forEach(component -> component.setWidth(100, Unit.PERCENTAGE));
|
||||
components.forEach(component -> {
|
||||
if(component instanceof HasSize) {
|
||||
try {
|
||||
((HasSize)component).setWidth("100%");
|
||||
}catch (UnsupportedOperationException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
FormLayout layout = new FormLayout();
|
||||
components.forEach(layout::addComponent);
|
||||
layout.setResponsiveSteps(new FormLayout.ResponsiveStep("0px", 1));
|
||||
components.forEach(c -> layout.addFormItem(c, ""));
|
||||
checkValid();
|
||||
layout.addStyleNames(AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
|
||||
setContent(layout);
|
||||
center();
|
||||
if (!isAttached()) {
|
||||
UI.getCurrent().addWindow(this);
|
||||
removeAll();
|
||||
add(layout);
|
||||
if (!isOpened()) {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkValid() {
|
||||
boolean valid = fields.values().stream().map(Pair::getFirst).reduce(Boolean::logicalAnd).orElse(true);
|
||||
buttons.forEach(button -> button.setEnabled(valid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
buttons.forEach(button -> button.getContent().setEnabled(valid));
|
||||
}
|
||||
}
|
|
@ -14,14 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.popup;
|
||||
package com.faendir.acra.ui.base.popup;
|
||||
|
||||
import com.faendir.acra.i18n.HasI18n;
|
||||
import com.vaadin.server.UserError;
|
||||
import com.vaadin.ui.AbstractComponent;
|
||||
import com.vaadin.ui.AbstractField;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.HasValidation;
|
||||
import com.vaadin.flow.component.HasValue;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -35,34 +34,38 @@ import java.util.function.Supplier;
|
|||
* @author Lukas
|
||||
* @since 22.06.2017
|
||||
*/
|
||||
public class ValidatedField<V, T extends AbstractComponent> {
|
||||
public class ValidatedField<V, T extends Component> {
|
||||
private final T field;
|
||||
private final Supplier<V> valueSupplier;
|
||||
private final I18N i18n;
|
||||
private final Consumer<String> messageSetter;
|
||||
private final Map<Function<V, Boolean>, String> validators;
|
||||
private final List<Listener> listeners;
|
||||
private boolean valid;
|
||||
|
||||
private ValidatedField(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration, I18N i18n) {
|
||||
private ValidatedField(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration, Consumer<String> messageSetter) {
|
||||
this.field = field;
|
||||
this.valueSupplier = valueSupplier;
|
||||
this.i18n = i18n;
|
||||
this.messageSetter = messageSetter;
|
||||
this.validators = new HashMap<>();
|
||||
this.listeners = new ArrayList<>();
|
||||
this.valid = false;
|
||||
listenerRegistration.accept(this::validate);
|
||||
}
|
||||
|
||||
public static <V, T extends AbstractField<V> & HasI18n> ValidatedField<V, T> of(T field) {
|
||||
return new ValidatedField<>(field, field::getValue, vConsumer -> field.addValueChangeListener(event -> vConsumer.accept(event.getValue())), field.getI18n());
|
||||
public static <V, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, T> of(T field) {
|
||||
return new ValidatedField<>(field, field::getValue, vConsumer -> field.addValueChangeListener(event -> vConsumer.accept(event.getValue())), field::setErrorMessage);
|
||||
}
|
||||
|
||||
public static <V, T extends AbstractComponent & HasI18n> ValidatedField<V, T> of(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration) {
|
||||
return new ValidatedField<>(field, valueSupplier, listenerRegistration, field.getI18n());
|
||||
public static <V, C extends Composite<T>, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, C> of(C field) {
|
||||
return new ValidatedField<>(field, () -> field.getContent().getValue(), vConsumer -> field.getContent().addValueChangeListener(event -> vConsumer.accept(event.getValue())), m -> field.getContent().setErrorMessage(m));
|
||||
}
|
||||
|
||||
public ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessageId) {
|
||||
validators.put(validator, errorMessageId);
|
||||
public static <V, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, T> of(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration) {
|
||||
return new ValidatedField<>(field, valueSupplier, listenerRegistration, field::setErrorMessage);
|
||||
}
|
||||
|
||||
public ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessage) {
|
||||
validators.put(validator, errorMessage);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -77,10 +80,10 @@ public class ValidatedField<V, T extends AbstractComponent> {
|
|||
private boolean validate(V value) {
|
||||
boolean valid = validators.entrySet().stream().allMatch(entry -> {
|
||||
if (entry.getKey().apply(value)) {
|
||||
field.setComponentError(null);
|
||||
messageSetter.accept(null);
|
||||
return true;
|
||||
} else {
|
||||
field.setComponentError(new UserError(i18n.get(entry.getValue())));
|
||||
messageSetter.accept(entry.getValue());
|
||||
return false;
|
||||
}
|
||||
});
|
62
src/main/java/com/faendir/acra/ui/base/statistics/Chart.java
Normal file
62
src/main/java/com/faendir/acra/ui/base/statistics/Chart.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.base.statistics;
|
||||
|
||||
import com.faendir.acra.ui.component.Card;
|
||||
import com.faendir.acra.ui.base.JFreeChartWrapper;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 01.06.18
|
||||
*/
|
||||
abstract class Chart<T> extends Composite<Card> {
|
||||
private JFreeChart chart;
|
||||
|
||||
Chart(@NonNull String captionId, @NonNull Object... params) {
|
||||
getContent().setWidth(500, HasSize.Unit.PIXEL);
|
||||
getContent().setMaxWidthFull();
|
||||
getContent().setHeader(Translatable.createText(captionId, params));
|
||||
}
|
||||
|
||||
public void setContent(@NonNull Map<T, Long> map) {
|
||||
chart = createChart(map);
|
||||
JFreeChartWrapper content = new JFreeChartWrapper(chart);
|
||||
content.setSvgAspectRatio("xMidYMid");
|
||||
content.setWidthFull();
|
||||
getContent().removeAll();
|
||||
getContent().add(content);
|
||||
}
|
||||
|
||||
Paint getForegroundColor() {
|
||||
return /*UI.getCurrent().getTheme().toLowerCase().contains("dark")*/false ? Statistics.FOREGROUND_DARK : Statistics.FOREGROUND_LIGHT;
|
||||
}
|
||||
|
||||
protected abstract JFreeChart createChart(@NonNull Map<T, Long> map);
|
||||
|
||||
@Nullable
|
||||
JFreeChart getChart() {
|
||||
return chart;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.statistics;
|
||||
package com.faendir.acra.ui.base.statistics;
|
||||
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
|
@ -24,9 +24,11 @@ import org.jfree.chart.plot.PiePlot;
|
|||
import org.jfree.chart.util.SortOrder;
|
||||
import org.jfree.data.general.DefaultPieDataset;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.awt.*;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -35,18 +37,25 @@ import java.util.Map;
|
|||
* @since 01.06.18
|
||||
*/
|
||||
class PieChart extends Chart<String> {
|
||||
PieChart(I18N i18n, String captionId, Object... params) {
|
||||
super(i18n, captionId, params);
|
||||
private static final int MAX_PARTS = 4;
|
||||
PieChart(@NonNull String captionId, @NonNull Object... params) {
|
||||
super(captionId, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JFreeChart createChart(@NonNull Map<String, Long> map) {
|
||||
List<Map.Entry<String, Long>> values = new ArrayList<>(map.entrySet());
|
||||
values.sort((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()));
|
||||
DefaultPieDataset dataset = new DefaultPieDataset();
|
||||
map.forEach((label, count) -> dataset.insertValue(0, label, count));
|
||||
dataset.sortByKeys(SortOrder.ASCENDING);
|
||||
values.subList(0, Math.min(MAX_PARTS, values.size())).forEach(e -> dataset.insertValue(0, e.getKey(), e.getValue()));
|
||||
dataset.sortByValues(SortOrder.DESCENDING);
|
||||
if (values.size() > MAX_PARTS) {
|
||||
dataset.insertValue(dataset.getItemCount(), "Other", values.subList(MAX_PARTS, values.size()).stream().mapToLong(Map.Entry::getValue).sum());
|
||||
}
|
||||
JFreeChart chart = ChartFactory.createPieChart("", dataset, false, false, false);
|
||||
chart.setBackgroundPaint(null);
|
||||
PiePlot plot = (PiePlot) chart.getPlot();
|
||||
plot.setStartAngle(0);
|
||||
plot.setShadowPaint(null);
|
||||
plot.setBackgroundAlpha(0);
|
||||
plot.setOutlineVisible(false);
|
||||
|
@ -55,9 +64,10 @@ class PieChart extends Chart<String> {
|
|||
plot.setLabelShadowPaint(null);
|
||||
Paint foregroundColor = getForegroundColor();
|
||||
plot.setLabelPaint(foregroundColor);
|
||||
plot.setLabelFont(Statistics.LABEL_FONT);
|
||||
plot.setLabelLinkPaint(foregroundColor);
|
||||
plot.setLabelLinkStyle(PieLabelLinkStyle.QUAD_CURVE);
|
||||
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2})"));
|
||||
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2})", NumberFormat.getNumberInstance(), new DecimalFormat("0.0%")));
|
||||
//noinspection unchecked
|
||||
((List<String>) dataset.getKeys()).forEach(key -> plot.setExplodePercent(key, 0.01));
|
||||
return chart;
|
|
@ -13,66 +13,69 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.statistics;
|
||||
package com.faendir.acra.ui.base.statistics;
|
||||
|
||||
import com.faendir.acra.i18n.I18nCheckBox;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.ComparableExpressionBase;
|
||||
import com.querydsl.core.types.dsl.DateTimePath;
|
||||
import com.querydsl.sql.SQLExpressions;
|
||||
import com.vaadin.data.HasValue;
|
||||
import com.vaadin.server.Sizeable;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.ComponentContainer;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.HasEnabled;
|
||||
import com.vaadin.flow.component.HasSize;
|
||||
import com.vaadin.flow.component.HasStyle;
|
||||
import com.vaadin.flow.component.HasValue;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.vaadin.risto.stepper.IntStepper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 01.06.18
|
||||
*/
|
||||
class Property<F, C extends Component & HasValue<F>, T> {
|
||||
class Property<F, C extends Component & HasValue<?, F> & HasEnabled & HasSize & HasStyle, T> {
|
||||
private final App app;
|
||||
private final DataService dataService;
|
||||
private final CheckBox checkBox;
|
||||
private final Translatable<Checkbox> checkBox;
|
||||
private final C filterComponent;
|
||||
private final Function<F, BooleanExpression> filter;
|
||||
private final Chart<T> chart;
|
||||
private final Expression<T> select;
|
||||
|
||||
private Property(App app, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, I18N i18n, String filterTextId) {
|
||||
private Property(App app, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, String filterTextId, Object... params) {
|
||||
this.app = app;
|
||||
this.dataService = dataService;
|
||||
this.checkBox = new I18nCheckBox(i18n, filterTextId);
|
||||
this.checkBox = Translatable.createCheckbox(false, filterTextId, params);
|
||||
checkBox.preventWhiteSpaceBreaking();
|
||||
this.filterComponent = filterComponent;
|
||||
this.filter = filter;
|
||||
this.chart = chart;
|
||||
this.select = select;
|
||||
filterComponent.setEnabled(false);
|
||||
filterComponent.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
checkBox.setValue(false);
|
||||
checkBox.addValueChangeListener(e -> filterComponent.setEnabled(e.getValue()));
|
||||
filterComponent.setWidth("100%");
|
||||
checkBox.getContent().addValueChangeListener(e -> filterComponent.setEnabled(e.getValue()));
|
||||
}
|
||||
|
||||
void addTo(ComponentContainer filterLayout, ComponentContainer chartLayout) {
|
||||
filterLayout.addComponent(checkBox);
|
||||
filterLayout.addComponent(filterComponent);
|
||||
chartLayout.addComponent(chart);
|
||||
void addTo(FormLayout filterLayout, FlexComponent chartLayout) {
|
||||
filterLayout.addFormItem(filterComponent, checkBox);
|
||||
chartLayout.add(chart);
|
||||
chartLayout.expand(chart);
|
||||
}
|
||||
|
||||
BooleanExpression applyFilter(@Nullable BooleanExpression expression) {
|
||||
if (checkBox.getValue() && filterComponent.getValue() != null) {
|
||||
if (checkBox.getContent().getValue() && filterComponent.getValue() != null) {
|
||||
return filter.apply(filterComponent.getValue()).and(expression);
|
||||
}
|
||||
return expression;
|
||||
|
@ -91,22 +94,23 @@ class Property<F, C extends Component & HasValue<F>, T> {
|
|||
this.expression = expression;
|
||||
}
|
||||
|
||||
Property<?, ?, ?> createStringProperty(App app, ComparableExpressionBase<String> stringExpression, I18N i18n, String filterTextId, String chartTitleId) {
|
||||
ComboBox<String> comboBox = new ComboBox<>(null, dataService.getFromReports(app, expression, stringExpression));
|
||||
comboBox.setEmptySelectionAllowed(false);
|
||||
return new Property<>(app, comboBox, stringExpression::eq, new PieChart(i18n, chartTitleId), dataService, stringExpression, i18n, filterTextId);
|
||||
Property<?, ?, ?> createStringProperty(App app, ComparableExpressionBase<String> stringExpression, String filterTextId, String chartTitleId) {
|
||||
List<String> list = dataService.getFromReports(app, expression, stringExpression);
|
||||
ComboBox<String> comboBox = new ComboBox<>(null, list);
|
||||
comboBox.setAllowCustomValue(false);
|
||||
comboBox.setRequired(true);
|
||||
comboBox.setValue(list.get(0));
|
||||
return new Property<>(app, comboBox, stringExpression::eq, new PieChart(chartTitleId), dataService, stringExpression, filterTextId);
|
||||
}
|
||||
|
||||
Property<?, ?, ?> createAgeProperty(App app, DateTimePath<ZonedDateTime> dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) {
|
||||
IntStepper stepper = new IntStepper();
|
||||
stepper.setValue(30);
|
||||
stepper.setMinValue(1);
|
||||
Property<?, ?, ?> createAgeProperty(App app, DateTimePath<ZonedDateTime> dateTimeExpression, String filterTextId, String chartTitleId) {
|
||||
TextField stepper = new TextField();
|
||||
stepper.setValue("30");
|
||||
return new Property<>(app, stepper,
|
||||
days -> dateTimeExpression.after(ZonedDateTime.now().minus(days, ChronoUnit.DAYS)),
|
||||
new TimeChart(i18n, chartTitleId),
|
||||
days -> dateTimeExpression.after(ZonedDateTime.now().minus(Integer.parseInt(days), ChronoUnit.DAYS)),
|
||||
new TimeChart(chartTitleId),
|
||||
dataService,
|
||||
SQLExpressions.date(Date.class, dateTimeExpression),
|
||||
i18n,
|
||||
filterTextId);
|
||||
}
|
||||
}
|
|
@ -14,25 +14,21 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.statistics;
|
||||
package com.faendir.acra.ui.base.statistics;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nPanel;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.view.base.layout.FlexLayout;
|
||||
import com.faendir.acra.ui.component.Card;
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.Composite;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.vaadin.risto.stepper.IntStepper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
@ -42,50 +38,43 @@ import java.util.List;
|
|||
* @author lukas
|
||||
* @since 21.05.18
|
||||
*/
|
||||
public class Statistics extends Composite {
|
||||
public class Statistics extends Composite<FlexLayout> {
|
||||
static final Color BLUE = new Color(0x197de1); //vaadin blue
|
||||
static final Color FOREGROUND_DARK = new Color(0xcacecf);
|
||||
static final Color FOREGROUND_LIGHT = new Color(0x464646);
|
||||
@Nullable private final BooleanExpression baseExpression;
|
||||
static final Font LABEL_FONT = new Font("Roboto", Font.PLAIN, 18);
|
||||
@Nullable
|
||||
private final BooleanExpression baseExpression;
|
||||
private final List<Property<?, ?, ?>> properties;
|
||||
|
||||
public Statistics(App app, @Nullable BooleanExpression baseExpression, DataService dataService, I18N i18n) {
|
||||
public Statistics(App app, @Nullable BooleanExpression baseExpression, DataService dataService) {
|
||||
this.baseExpression = baseExpression;
|
||||
properties = new ArrayList<>();
|
||||
GridLayout filterLayout = new GridLayout(2, 1);
|
||||
filterLayout.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
|
||||
filterLayout.setSpacing(true);
|
||||
filterLayout.setSizeFull();
|
||||
filterLayout.addStyleNames(AcraTheme.PADDING_LEFT, AcraTheme.PADDING_TOP, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM);
|
||||
filterLayout.setColumnExpandRatio(1, 1);
|
||||
FormLayout filterLayout = new FormLayout();
|
||||
filterLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0px", 1));
|
||||
filterLayout.setWidth("100%");
|
||||
Card card = new Card(filterLayout);
|
||||
card.setHeader(Translatable.createText(Messages.FILTER));
|
||||
card.setWidth(500, HasSize.Unit.PIXEL);
|
||||
|
||||
IntStepper dayStepper = new IntStepper();
|
||||
dayStepper.setValue(30);
|
||||
dayStepper.setMinValue(1);
|
||||
TextField dayStepper = new TextField();
|
||||
dayStepper.setValue("30");
|
||||
Property.Factory factory = new Property.Factory(dataService, baseExpression);
|
||||
properties.add(factory.createAgeProperty(app, QReport.report.date, i18n, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.androidVersion, i18n, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.stacktrace.version.name, i18n, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.phoneModel, i18n, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.brand, i18n, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND));
|
||||
properties.add(factory.createAgeProperty(app, QReport.report.date, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.androidVersion, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.stacktrace.version.name, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.phoneModel, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL));
|
||||
properties.add(factory.createStringProperty(app, QReport.report.brand, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND));
|
||||
|
||||
Panel filterPanel = new I18nPanel(filterLayout, i18n, Messages.FILTER);
|
||||
filterPanel.addStyleName(AcraTheme.NO_BACKGROUND);
|
||||
FlexLayout layout = new FlexLayout(filterPanel);
|
||||
layout.setWidth(100, Unit.PERCENTAGE);
|
||||
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
|
||||
getContent().setWidthFull();
|
||||
getContent().removeAll();
|
||||
|
||||
properties.forEach(property -> property.addTo(filterLayout, layout));
|
||||
getContent().add(card);
|
||||
getContent().expand(card);
|
||||
properties.forEach(property -> property.addTo(filterLayout, getContent()));
|
||||
|
||||
Button applyButton = new I18nButton(e -> update(), i18n, Messages.APPLY);
|
||||
applyButton.setWidth(100, Unit.PERCENTAGE);
|
||||
filterLayout.space();
|
||||
filterLayout.addComponent(applyButton);
|
||||
filterLayout.newLine();
|
||||
|
||||
Panel root = new Panel(layout);
|
||||
root.setSizeFull();
|
||||
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
|
||||
setCompositionRoot(root);
|
||||
filterLayout.add(Translatable.createButton(e -> update(), Messages.APPLY));
|
||||
update();
|
||||
}
|
||||
|
|
@ -13,9 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.statistics;
|
||||
package com.faendir.acra.ui.base.statistics;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.axis.NumberTickUnitSource;
|
||||
|
@ -28,11 +27,9 @@ import org.jfree.data.time.Day;
|
|||
import org.jfree.data.time.TimeSeries;
|
||||
import org.jfree.data.time.TimeSeriesCollection;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -40,19 +37,19 @@ import java.util.Map;
|
|||
* @since 01.06.18
|
||||
*/
|
||||
class TimeChart extends Chart<Date> {
|
||||
TimeChart(I18N i18n, String captionId, Object... params) {
|
||||
super(i18n, captionId, params);
|
||||
TimeChart(@NonNull String captionId, @NonNull Object... params) {
|
||||
super(captionId, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JFreeChart createChart(@NonNull Map<Date, Long> map) {
|
||||
TimeSeries series = new TimeSeries(getI18n().get(Messages.DATE));
|
||||
TimeSeries series = new TimeSeries("Date");
|
||||
series.add(new Day(new Date()), 0);
|
||||
map.forEach((date, count) -> series.addOrUpdate(new Day(date), count));
|
||||
JFreeChart chart = ChartFactory.createXYBarChart("",
|
||||
getI18n().get(Messages.DATE),
|
||||
"Date",
|
||||
true,
|
||||
getI18n().get(Messages.REPORTS),
|
||||
"Reports",
|
||||
new TimeSeriesCollection(series),
|
||||
PlotOrientation.VERTICAL,
|
||||
false,
|
||||
|
@ -68,11 +65,15 @@ class TimeChart extends Chart<Date> {
|
|||
plot.setRangeGridlinePaint(foregroundColor);
|
||||
ValueAxis domainAxis = plot.getDomainAxis();
|
||||
domainAxis.setLabelPaint(foregroundColor);
|
||||
domainAxis.setLabelFont(Statistics.LABEL_FONT);
|
||||
domainAxis.setTickLabelFont(Statistics.LABEL_FONT);
|
||||
domainAxis.setTickLabelPaint(foregroundColor);
|
||||
domainAxis.setAxisLinePaint(foregroundColor);
|
||||
domainAxis.setTickMarkPaint(foregroundColor);
|
||||
ValueAxis rangeAxis = plot.getRangeAxis();
|
||||
rangeAxis.setLabelPaint(foregroundColor);
|
||||
rangeAxis.setLabelFont(Statistics.LABEL_FONT);
|
||||
rangeAxis.setTickLabelFont(Statistics.LABEL_FONT);
|
||||
rangeAxis.setTickLabelPaint(foregroundColor);
|
||||
rangeAxis.setAxisLinePaint(foregroundColor);
|
||||
rangeAxis.setTickMarkPaint(foregroundColor);
|
||||
|
@ -83,15 +84,4 @@ class TimeChart extends Chart<Date> {
|
|||
barRenderer.setMargin(0.2);
|
||||
return chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
super.updateMessageStrings(locale);
|
||||
if(getChart() != null) {
|
||||
XYPlot plot = getChart().getXYPlot();
|
||||
plot.getDomainAxis().setLabel(getI18n().get(Messages.DATE));
|
||||
plot.getRangeAxis().setLabel(getI18n().get(Messages.REPORTS));
|
||||
markAsDirtyRecursive();
|
||||
}
|
||||
}
|
||||
}
|
94
src/main/java/com/faendir/acra/ui/component/Card.java
Normal file
94
src/main/java/com/faendir/acra/ui/component/Card.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.HasStyle;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 18.10.18
|
||||
*/
|
||||
public class Card extends Composite<Div> implements HasSize, HasStyle {
|
||||
private final Div header;
|
||||
private final Div content;
|
||||
private boolean allowCollapse = false;
|
||||
|
||||
public Card() {
|
||||
getContent().getStyle().set("box-shadow", "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)");
|
||||
getContent().getStyle().set("border-radius", "2px");
|
||||
getContent().getStyle().set("margin", "1rem");
|
||||
getContent().getStyle().set("display", "inline-block");
|
||||
header = new Div();
|
||||
header.getStyle().set("padding", "1rem");
|
||||
header.getStyle().set("box-sizing", "border-box");
|
||||
header.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
|
||||
header.getStyle().set("display", "inline-block");
|
||||
header.setWidth("100%");
|
||||
header.addClickListener(e -> {
|
||||
if (allowCollapse) {
|
||||
if (isCollapsed()) {
|
||||
expand();
|
||||
} else {
|
||||
collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
content = new Div();
|
||||
content.getStyle().set("padding", "1rem");
|
||||
content.getStyle().set("box-sizing", "border-box");
|
||||
content.getStyle().set("display", "inline-block");
|
||||
content.setSizeFull();
|
||||
getContent().add(header, content);
|
||||
}
|
||||
|
||||
public Card(Component... components) {
|
||||
this();
|
||||
add(components);
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
content.removeAll();
|
||||
}
|
||||
|
||||
public void addComponentAtIndex(int index, Component component) {
|
||||
content.addComponentAtIndex(index, component);
|
||||
}
|
||||
|
||||
public void addComponentAsFirst(Component component) {
|
||||
content.addComponentAsFirst(component);
|
||||
}
|
||||
|
||||
public void add(Component... components) {
|
||||
content.add(components);
|
||||
}
|
||||
|
||||
public void remove(Component... components) {
|
||||
content.remove(components);
|
||||
}
|
||||
|
||||
public void setHeader(Component... components) {
|
||||
header.removeAll();
|
||||
header.add(components);
|
||||
}
|
||||
|
||||
public boolean allowsCollapse() {
|
||||
return allowCollapse;
|
||||
}
|
||||
|
||||
public void setAllowCollapse(boolean allowCollapse) {
|
||||
this.allowCollapse = allowCollapse;
|
||||
}
|
||||
|
||||
public void collapse() {
|
||||
content.getStyle().set("display", "none");
|
||||
}
|
||||
|
||||
public void expand() {
|
||||
content.getStyle().set("display", "inline-block");
|
||||
}
|
||||
|
||||
public boolean isCollapsed() {
|
||||
return content.getStyle().get("display").equals("none");
|
||||
}
|
||||
}
|
61
src/main/java/com/faendir/acra/ui/component/CssGrid.java
Normal file
61
src/main/java/com/faendir/acra/ui/component/CssGrid.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
public class CssGrid extends Div implements HasSize, HasStyle {
|
||||
public CssGrid() {
|
||||
getStyle().set("display", "grid");
|
||||
}
|
||||
|
||||
public CssGrid(Component... components) {
|
||||
this();
|
||||
add(components);
|
||||
}
|
||||
|
||||
public void setTemplateColumns(String template) {
|
||||
getStyle().set("grid-template-columns", template);
|
||||
}
|
||||
|
||||
public void setColumnGap(int size, Unit unit) {
|
||||
getStyle().set("grid-column-gap", size + unit.getText());
|
||||
}
|
||||
|
||||
public void setJustifyItems(JustifyMode justifyMode) {
|
||||
getStyle().set("justify-items", justifyMode.value);
|
||||
}
|
||||
|
||||
public void setAlignItems(AlignMode alignMode) {
|
||||
getStyle().set("align-items", alignMode.value);
|
||||
}
|
||||
|
||||
public void alignItems(AlignMode alignMode, com.vaadin.flow.component.HasStyle... components) {
|
||||
for (com.vaadin.flow.component.HasStyle component : components) {
|
||||
component.getStyle().set("align-self", alignMode.value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum JustifyMode {
|
||||
START("start");
|
||||
private final String value;
|
||||
|
||||
JustifyMode(@NonNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AlignMode {
|
||||
CENTER("center"),
|
||||
FIRST_BASELINE("first baseline");
|
||||
private final String value;
|
||||
|
||||
AlignMode(@NonNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.html.Anchor;
|
||||
import com.vaadin.flow.server.AbstractStreamResource;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.11.18
|
||||
*/
|
||||
public class DownloadButton extends Anchor {
|
||||
public DownloadButton(@NonNull AbstractStreamResource href, @NonNull String captionId, @NonNull Object... params) {
|
||||
super(href, "");
|
||||
getElement().setAttribute("download", true);
|
||||
add(Translatable.createButton(captionId, params));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.HasComponents;
|
||||
import com.vaadin.flow.component.Tag;
|
||||
import com.vaadin.flow.component.dependency.HtmlImport;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.11.18
|
||||
*/
|
||||
@Tag("simple-dropdown")
|
||||
@HtmlImport("bower_components/simple-dropdown/simple-dropdown.html")
|
||||
public class DropdownMenu extends Component implements HasComponents, HasSize, HasStyle {
|
||||
public DropdownMenu() {
|
||||
}
|
||||
|
||||
public DropdownMenu(Component... components) {
|
||||
this();
|
||||
add(components);
|
||||
}
|
||||
|
||||
public enum Origin {
|
||||
LEFT, TOP, RIGHT, BOTTOM, CENTER;
|
||||
}
|
||||
|
||||
public void setOpen(boolean open) {
|
||||
getElement().setProperty("active", open);
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return getElement().getProperty("active", false);
|
||||
}
|
||||
|
||||
public void setOrigin(Origin... origin) {
|
||||
getElement().setProperty("origin", Stream.of(origin).map(Origin::name).map(String::toLowerCase).collect(Collectors.joining(" ")));
|
||||
}
|
||||
|
||||
public Origin[] getOrigin() {
|
||||
return Stream.of(getElement().getProperty("origin").split(" ")).map(String::toUpperCase).map(Origin::valueOf).toArray(Origin[]::new);
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
getElement().setProperty("label", label);
|
||||
}
|
||||
|
||||
public String getLabel(){
|
||||
return getElement().getProperty("label");
|
||||
}
|
||||
}
|
44
src/main/java/com/faendir/acra/ui/component/FlexLayout.java
Normal file
44
src/main/java/com/faendir/acra/ui/component/FlexLayout.java
Normal file
|
@ -0,0 +1,44 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 14.11.18
|
||||
*/
|
||||
public class FlexLayout extends com.vaadin.flow.component.orderedlayout.FlexLayout implements HasSize, HasStyle {
|
||||
public FlexLayout(Component... components) {
|
||||
super(components);
|
||||
}
|
||||
|
||||
public FlexLayout() {
|
||||
}
|
||||
|
||||
public void setFlexDirection(FlexDirection flexDirection) {
|
||||
getStyle().set("flex-direction", flexDirection.value);
|
||||
}
|
||||
|
||||
public void setFlexWrap(FlexWrap flexWrap) {
|
||||
getStyle().set("flex-wrap", flexWrap.value);
|
||||
}
|
||||
|
||||
public enum FlexWrap {
|
||||
WRAP("wrap");
|
||||
private final String value;
|
||||
|
||||
FlexWrap(@NonNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FlexDirection {
|
||||
COLUMN("column"),
|
||||
ROW("row");
|
||||
private final String value;
|
||||
|
||||
FlexDirection(@NonNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
73
src/main/java/com/faendir/acra/ui/component/HasSize.java
Normal file
73
src/main/java/com/faendir/acra/ui/component/HasSize.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.HasStyle;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 14.11.18
|
||||
*/
|
||||
public interface HasSize extends com.vaadin.flow.component.HasSize, HasStyle {
|
||||
enum Unit {
|
||||
PERCENTAGE("%"),
|
||||
PIXEL("px"),
|
||||
REM("rem"),
|
||||
EM("em");
|
||||
private final String text;
|
||||
|
||||
Unit(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
default void setWidthFull() {
|
||||
setWidth(100, Unit.PERCENTAGE);
|
||||
}
|
||||
|
||||
default void setWidth(int value, Unit unit) {
|
||||
setWidth(value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMaxWidth(int value, Unit unit) {
|
||||
getStyle().set("max-width", value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMaxWidthFull(){
|
||||
setMaxWidth(100, Unit.PERCENTAGE);
|
||||
}
|
||||
|
||||
default void setMinWidth(int value, Unit unit) {
|
||||
getStyle().set("min-width", value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMinWidthFull(){
|
||||
setMinWidth(100, Unit.PERCENTAGE);
|
||||
}
|
||||
|
||||
default void setHeightFull() {
|
||||
setHeight(100, Unit.PERCENTAGE);
|
||||
}
|
||||
|
||||
default void setHeight(int value, Unit unit) {
|
||||
setHeight(value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMaxHeight(int value, Unit unit) {
|
||||
getStyle().set("max-height", value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMaxHeightFull(){
|
||||
setMaxHeight(100, Unit.PERCENTAGE);
|
||||
}
|
||||
|
||||
default void setMinHeight(int value, Unit unit) {
|
||||
getStyle().set("min-height", value + unit.getText());
|
||||
}
|
||||
|
||||
default void setMinHeightFull(){
|
||||
setMinHeight(100, Unit.PERCENTAGE);
|
||||
}
|
||||
}
|
24
src/main/java/com/faendir/acra/ui/component/HasStyle.java
Normal file
24
src/main/java/com/faendir/acra/ui/component/HasStyle.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.11.18
|
||||
*/
|
||||
public interface HasStyle extends com.vaadin.flow.component.HasStyle {
|
||||
default void preventWhiteSpaceBreaking() {
|
||||
getStyle().set("white-space", "nowrap");
|
||||
}
|
||||
|
||||
default void setMargin(int value, HasSize.Unit unit) {
|
||||
getStyle().set("margin", value + unit.getText());
|
||||
}
|
||||
|
||||
default void setDefaultTextStyle() {
|
||||
getStyle().set("text-decoration","none");
|
||||
getStyle().set("color","inherit");
|
||||
}
|
||||
|
||||
default void setPadding(int value, HasSize.Unit unit) {
|
||||
getStyle().set("padding", value + unit.getText());
|
||||
}
|
||||
}
|
24
src/main/java/com/faendir/acra/ui/component/Label.java
Normal file
24
src/main/java/com/faendir/acra/ui/component/Label.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
public class Label extends com.vaadin.flow.component.html.Label {
|
||||
public Label() {
|
||||
}
|
||||
|
||||
public Label(String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
public Label secondary() {
|
||||
getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Label honorWhitespaces() {
|
||||
getStyle().set("white-space","pre");
|
||||
return this;
|
||||
}
|
||||
}
|
24
src/main/java/com/faendir/acra/ui/component/Tab.java
Normal file
24
src/main/java/com/faendir/acra/ui/component/Tab.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
||||
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 15.11.18
|
||||
*/
|
||||
public class Tab extends com.vaadin.flow.component.tabs.Tab implements LocaleChangeObserver {
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
|
||||
public Tab(@NonNull String captionId, @NonNull Object... params) {
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localeChange(LocaleChangeEvent event) {
|
||||
setLabel(getTranslation(captionId, params));
|
||||
}
|
||||
}
|
111
src/main/java/com/faendir/acra/ui/component/Translatable.java
Normal file
111
src/main/java/com/faendir/acra/ui/component/Translatable.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
package com.faendir.acra.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.ClickEvent;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.HasText;
|
||||
import com.vaadin.flow.component.Text;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextArea;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
||||
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.RouterLink;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 14.11.18
|
||||
*/
|
||||
public class Translatable<T extends Component> extends Composite<T> implements LocaleChangeObserver, HasSize, HasStyle {
|
||||
private final T t;
|
||||
private final Consumer<T> setter;
|
||||
|
||||
protected Translatable(@NonNull T t, @NonNull Consumer<T> setter) {
|
||||
this.t = t;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T initContent() {
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localeChange(LocaleChangeEvent event) {
|
||||
setter.accept(getContent());
|
||||
}
|
||||
|
||||
public Translatable<T> with(Consumer<T> consumer) {
|
||||
consumer.accept(t);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<Text> createText(@NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new Text(""), captionId, params);
|
||||
}
|
||||
|
||||
private static <T extends Component & HasText> Translatable<T> create(T component, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(component, text -> text.setText(text.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<Button> createButton(@NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new Button(), captionId, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<Button> createButton(@NonNull ComponentEventListener<ClickEvent<Button>> clickListener, @NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new Button("", clickListener), captionId, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<TextField> createTextField(@NonNull String initialValue, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new TextField("", initialValue, ""), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<PasswordField> createPasswordField(@NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new PasswordField(), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<TextArea> createTextArea(@NonNull String initialValue, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new TextArea("", initialValue, ""), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
public static <T> Translatable<ComboBox<T>> createComboBox(@NonNull Collection<T> items, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new ComboBox<>("", items), tComboBox -> tComboBox.setLabel(tComboBox.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Translatable<Checkbox> createCheckbox(boolean initialValue, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new Checkbox(initialValue), checkbox -> checkbox.setLabel(checkbox.getTranslation(captionId, params)));
|
||||
}
|
||||
|
||||
public static Translatable<RouterLink> createRouterLink(@NonNull Class<? extends Component> target, @NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new RouterLink("", target), captionId, params);
|
||||
}
|
||||
|
||||
public static <T extends Component & HasUrlParameter<P>, P> Translatable<RouterLink> createRouterLink(@NonNull Class<T> target, @NonNull P parameter, @NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new RouterLink("", target, parameter), captionId, params);
|
||||
}
|
||||
|
||||
public static Translatable<Label> createLabel(@NonNull String captionId, @NonNull Object... params) {
|
||||
return create(new Label(), captionId, params);
|
||||
}
|
||||
|
||||
public static Translatable<Image> createImage(@NonNull String src, @NonNull String captionId, @NonNull Object... params) {
|
||||
return new Translatable<>(new Image(src, ""), image -> image.setAlt(image.getTranslation(captionId, params)));
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.navigation;
|
||||
|
||||
import com.vaadin.navigator.View;
|
||||
import com.vaadin.navigator.ViewChangeListener;
|
||||
import com.vaadin.navigator.ViewProvider;
|
||||
import com.vaadin.shared.util.SharedUtil;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.spring.navigator.SpringNavigator;
|
||||
import com.vaadin.spring.navigator.ViewActivationListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 21.06.2017
|
||||
*/
|
||||
@Component
|
||||
@UIScope
|
||||
public class MyNavigator extends SpringNavigator {
|
||||
public static final char SEPARATOR_CHAR = '/';
|
||||
public static final String SEPARATOR = "" + SEPARATOR_CHAR;
|
||||
@NonNull private final List<SingleViewProvider<?>> providers;
|
||||
@NonNull private final List<ActivationListenerWrapper> activationListenerWrappers;
|
||||
@NonNull private String navState;
|
||||
@Nullable private ViewProvider errorProvider;
|
||||
|
||||
@Autowired
|
||||
public MyNavigator(@NonNull List<SingleViewProvider<?>> providers) {
|
||||
this.providers = providers;
|
||||
this.activationListenerWrappers = new ArrayList<>();
|
||||
navState = "";
|
||||
providers.forEach(this::addProvider);
|
||||
}
|
||||
|
||||
public Optional<SingleViewProvider<?>> getViewProvider(@NonNull Class<? extends View> clazz) {
|
||||
return providers.stream().filter(p -> p.getClazz().equals(clazz)).findAny();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(@Nullable String navigationState) {
|
||||
if (navigationState == null) {
|
||||
return;
|
||||
}
|
||||
String oldNavState = navState;
|
||||
navState = navigationState;
|
||||
View view = null;
|
||||
String viewName = "";
|
||||
HierarchyElement findResult = findViewProvider(navigationState);
|
||||
if (findResult != null) {
|
||||
viewName = findResult.getProvider().getViewName(findResult.getNavState());
|
||||
view = findResult.getProvider().getView(viewName);
|
||||
}
|
||||
if (view == null) {
|
||||
if (errorProvider != null) {
|
||||
view = errorProvider.getView(navigationState);
|
||||
}
|
||||
if (view == null) {
|
||||
throw new IllegalArgumentException("Trying to navigate to an unknown state '" + navigationState + "' and an error view provider not present");
|
||||
}
|
||||
}
|
||||
String parameters = "";
|
||||
if (navState.length() > navState.indexOf(viewName) + viewName.length() + 1) {
|
||||
parameters = navState.substring(navState.indexOf(viewName) + viewName.length() + 1);
|
||||
} else if (navState.endsWith("/")) {
|
||||
navState = navState.substring(0, navState.length() - 1);
|
||||
}
|
||||
if (getCurrentView() == null || !SharedUtil.equals(getCurrentView(), view) || !SharedUtil.equals(oldNavState, navState)) {
|
||||
navigateTo(view, navState, parameters);
|
||||
} else {
|
||||
updateNavigationState(new ViewChangeListener.ViewChangeEvent(this, getCurrentView(), view, navState, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private HierarchyElement findViewProvider(String navigationState) {
|
||||
List<String> fragments = Arrays.asList(navigationState.split(SEPARATOR));
|
||||
if(fragments.isEmpty()) return null;
|
||||
int size = fragments.size();
|
||||
int fragmentIndex;
|
||||
String newNavState;
|
||||
ViewProvider viewProvider;
|
||||
fragmentIndex = size;
|
||||
do {
|
||||
fragmentIndex--;
|
||||
newNavState = String.join(SEPARATOR, fragments.subList(fragmentIndex, size));
|
||||
viewProvider = getViewProvider(newNavState);
|
||||
} while (fragmentIndex > 0 && viewProvider == null);
|
||||
if (viewProvider != null) return new HierarchyElement(newNavState, viewProvider);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorProvider(ViewProvider provider) {
|
||||
super.setErrorProvider(provider);
|
||||
errorProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addViewActivationListener(ViewActivationListener listener) {
|
||||
ActivationListenerWrapper wrapper = new ActivationListenerWrapper(listener);
|
||||
activationListenerWrappers.add(wrapper);
|
||||
super.addViewActivationListener(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeViewActivationListener(ViewActivationListener listener) {
|
||||
activationListenerWrappers.stream().filter(w -> w.getListener().equals(listener)).findAny().ifPresent(super::removeViewActivationListener);
|
||||
}
|
||||
|
||||
public Deque<HierarchyElement> getHierarchy() {
|
||||
Deque<HierarchyElement> hierarchy = new ArrayDeque<>();
|
||||
String navPart = navState;
|
||||
while (!navPart.isEmpty()) {
|
||||
HierarchyElement element = findViewProvider(navPart);
|
||||
if (element == null) break;
|
||||
hierarchy.addFirst(element);
|
||||
if (navPart.length() == element.getNavState().length()) break;
|
||||
navPart = navPart.substring(0, navPart.length() - element.getNavState().length() - 1);
|
||||
}
|
||||
HierarchyElement top = findViewProvider("");
|
||||
if (top != null) hierarchy.addFirst(top);
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
public static class HierarchyElement {
|
||||
@NonNull private final String navState;
|
||||
@NonNull private final ViewProvider provider;
|
||||
|
||||
private HierarchyElement(@NonNull String navState, @NonNull ViewProvider provider) {
|
||||
this.navState = navState;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getNavState() {
|
||||
return navState;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ViewProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivationListenerWrapper implements ViewActivationListener {
|
||||
private final ViewActivationListener listener;
|
||||
|
||||
private ActivationListenerWrapper(ViewActivationListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewActivated(ViewActivationEvent event) {
|
||||
HierarchyElement hierarchyElement = findViewProvider(event.getViewName());
|
||||
if (hierarchyElement != null) {
|
||||
listener.viewActivated(new ViewActivationEvent(MyNavigator.this, event.isActivated(), hierarchyElement.getNavState()));
|
||||
}
|
||||
}
|
||||
|
||||
public ViewActivationListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.navigation;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.ui.view.ErrorView;
|
||||
import com.faendir.acra.ui.view.base.navigation.BaseView;
|
||||
import com.faendir.acra.ui.view.base.navigation.Path;
|
||||
import com.faendir.acra.util.Utils;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.UI;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 14.05.2017
|
||||
*/
|
||||
@UIScope
|
||||
@Component
|
||||
@Configurable
|
||||
public class NavigationManager implements Serializable {
|
||||
@NonNull private final Path path;
|
||||
@NonNull private final MyNavigator navigator;
|
||||
|
||||
@Autowired
|
||||
public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator, @NonNull I18N i18n) {
|
||||
this.path = mainPath;
|
||||
this.navigator = navigator;
|
||||
this.navigator.init(ui, mainView);
|
||||
this.navigator.addViewActivationListener(e -> {
|
||||
Deque<MyNavigator.HierarchyElement> hierarchy = navigator.getHierarchy();
|
||||
path.set(hierarchy);
|
||||
SingleViewProvider provider = (SingleViewProvider) hierarchy.getLast().getProvider();
|
||||
String title = provider.getTitle(provider.getParameters(hierarchy.getLast().getNavState()));
|
||||
if (hierarchy.size() > 1) {
|
||||
title += " - " + i18n.get(Messages.ACRARIUM);
|
||||
}
|
||||
ui.getPage().setTitle(title);
|
||||
});
|
||||
navigator.setErrorView(ErrorView.class);
|
||||
String target = Optional.ofNullable(ui.getPage().getLocation().getFragment()).orElse("").replace("!", "");
|
||||
ui.access(() -> navigateTo(target));
|
||||
}
|
||||
|
||||
public void navigateTo(@NonNull Class<? extends BaseView> namedView, @Nullable String parameters, boolean newTab) {
|
||||
String target = Stream.of(path.asUrlFragment(), navigator.getViewProvider(namedView).map(SingleViewProvider::getId).orElse(""), parameters)
|
||||
.filter(s -> s != null && !s.isEmpty())
|
||||
.collect(Collectors.joining(MyNavigator.SEPARATOR));
|
||||
if (newTab) {
|
||||
navigator.getUI().getPage().open(Utils.getUrlWithFragment(target), "_blank", false);
|
||||
} else if (path.isEmpty() || !path.getLast().getId().equals(target)) {
|
||||
navigateTo(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanNavigateTo(@NonNull Class<? extends BaseView> namedView) {
|
||||
cleanHistory();
|
||||
navigateTo(namedView, "", false);
|
||||
}
|
||||
|
||||
private void navigateTo(@NonNull String fragment) {
|
||||
navigator.navigateTo(fragment);
|
||||
}
|
||||
|
||||
private void cleanHistory() {
|
||||
path.clear();
|
||||
}
|
||||
|
||||
public void navigateBack() {
|
||||
if (path.getSize() < 2) {
|
||||
if (path.isEmpty() || !"".equals(path.getLast().getId())) {
|
||||
path.goUp();
|
||||
navigateTo("");
|
||||
}
|
||||
} else {
|
||||
path.goUp();
|
||||
navigateTo(path.asUrlFragment());
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePageParameters(@Nullable String parameters) {
|
||||
String currentView = navigator.getViewProvider(navigator.getCurrentView().getClass()).map(SingleViewProvider::getId).orElse("");
|
||||
Path.Element element = path.goUp();
|
||||
String hierarchy = path.asUrlFragment();
|
||||
String target = "!" + (hierarchy.isEmpty() ? "" : hierarchy + MyNavigator.SEPARATOR_CHAR) + currentView + (parameters == null ?
|
||||
"" :
|
||||
MyNavigator.SEPARATOR_CHAR + parameters);
|
||||
path.goTo(element.getLabel(), currentView + (parameters == null ? "" : MyNavigator.SEPARATOR_CHAR + parameters));
|
||||
navigator.getUI().getPage().setUriFragment(target, false);
|
||||
}
|
||||
|
||||
public void openInNewTab(@Nullable String parameters) {
|
||||
String currentView = navigator.getViewProvider(navigator.getCurrentView().getClass()).map(SingleViewProvider::getId).orElse("");
|
||||
Path.Element element = path.goUp();
|
||||
String hierarchy = path.asUrlFragment();
|
||||
path.goTo(element);
|
||||
String target = "!" + (hierarchy.isEmpty() ? "" : hierarchy + MyNavigator.SEPARATOR_CHAR) + currentView + (parameters == null ?
|
||||
"" :
|
||||
MyNavigator.SEPARATOR_CHAR + parameters);
|
||||
navigator.getUI().getPage().open(Utils.getUrlWithFragment(target), "_blank", false);
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.navigation;
|
||||
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.ui.annotation.RequiresAppPermission;
|
||||
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 24.03.2018
|
||||
*/
|
||||
public abstract class SingleParametrizedViewProvider<T, V extends ParametrizedBaseView<T>> extends SingleViewProvider<V> {
|
||||
private String lastParameter;
|
||||
private T lastT;
|
||||
|
||||
public SingleParametrizedViewProvider(Class<V> clazz) {
|
||||
super(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidParameter(String parameter) {
|
||||
T t = parseParameterInternal(parameter);
|
||||
RequiresAppPermission annotation;
|
||||
return isValidParameter(t) && ((annotation = getClazz().getAnnotation(RequiresAppPermission.class)) == null || SecurityUtils.hasPermission(toApp(t), annotation.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getView(String viewName) {
|
||||
V view = super.getView(viewName);
|
||||
if (view != null) {
|
||||
view.setParameterParser(e -> {
|
||||
String parameters = null;
|
||||
Matcher matcher = Pattern.compile("(^|/)(" + getId() + ")($|/)").matcher(e.getViewName());
|
||||
if (matcher.find()) {
|
||||
parameters = getParameters(e.getViewName().substring(matcher.start(2)) + (e.getParameters().isEmpty() ? "" : MyNavigator.SEPARATOR_CHAR + e.getParameters()));
|
||||
}
|
||||
return parameters != null ? parseParameterInternal(parameters) : null;
|
||||
});
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getTitle(String parameter) {
|
||||
return getTitle(parseParameterInternal(parameter));
|
||||
}
|
||||
|
||||
protected abstract String getTitle(T parameter);
|
||||
|
||||
protected abstract boolean isValidParameter(T parameter);
|
||||
|
||||
protected abstract T parseParameter(String parameter);
|
||||
|
||||
protected abstract App toApp(T parameter);
|
||||
|
||||
private T parseParameterInternal(String parameter) {
|
||||
if (parameter.equals(lastParameter)) {
|
||||
return lastT;
|
||||
}
|
||||
T t = parseParameter(parameter);
|
||||
lastParameter = parameter;
|
||||
lastT = t;
|
||||
return t;
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.navigation;
|
||||
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.ui.annotation.RequiresRole;
|
||||
import com.faendir.acra.ui.view.base.navigation.BaseView;
|
||||
import com.vaadin.navigator.ViewProvider;
|
||||
import com.vaadin.spring.internal.ViewCache;
|
||||
import com.vaadin.spring.internal.ViewScopeImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 24.03.2018
|
||||
*/
|
||||
public abstract class SingleViewProvider<V extends BaseView> implements ViewProvider {
|
||||
private final Class<V> clazz;
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
protected SingleViewProvider(Class<V> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getViewName(String viewAndParameters) {
|
||||
return getParameters(viewAndParameters) != null ? viewAndParameters : null;
|
||||
}
|
||||
|
||||
public String getParameters(String viewAndParameters) {
|
||||
String result = null;
|
||||
String id = getId();
|
||||
if (viewAndParameters.startsWith(id)) {
|
||||
String params = viewAndParameters.substring(id.length());
|
||||
RequiresRole annotation = getClazz().getAnnotation(RequiresRole.class);
|
||||
if (annotation == null || SecurityUtils.hasRole(annotation.value())) {
|
||||
if (params.isEmpty()) {
|
||||
result = "";
|
||||
} else if (params.charAt(0) == MyNavigator.SEPARATOR_CHAR && isValidParameter(params.substring(1))) {
|
||||
result = params.substring(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Class<V> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
protected boolean isValidParameter(String parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getView(String viewName) {
|
||||
V view = null;
|
||||
String id = getId();
|
||||
if (viewName.startsWith(id)) {
|
||||
String params = viewName.substring(id.length());
|
||||
if (params.isEmpty() || params.charAt(0) == MyNavigator.SEPARATOR_CHAR && isValidParameter(params.substring(1))) {
|
||||
RequiresRole annotation = getClazz().getAnnotation(RequiresRole.class);
|
||||
if (annotation == null || SecurityUtils.hasRole(annotation.value())) {
|
||||
if (ViewScopeImpl.VAADIN_VIEW_SCOPE_NAME.equals(((BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory()).getBeanDefinition(
|
||||
applicationContext.getBeanNamesForType(getClazz())[0]).getScope())) {
|
||||
final ViewCache viewCache = ViewScopeImpl.getViewCacheRetrievalStrategy().getViewCache(applicationContext);
|
||||
viewCache.creatingView(viewName);
|
||||
try {
|
||||
view = applicationContext.getBean(getClazz());
|
||||
} finally {
|
||||
viewCache.viewCreated(viewName, view);
|
||||
}
|
||||
} else {
|
||||
view = applicationContext.getBean(getClazz());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
public abstract String getTitle(String parameter);
|
||||
|
||||
public abstract String getId();
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.vaadin.navigator.View;
|
||||
import com.vaadin.navigator.ViewChangeListener;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Composite;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 21.05.2017
|
||||
*/
|
||||
@SpringComponent
|
||||
@UIScope
|
||||
public class ErrorView extends Composite implements View {
|
||||
@Autowired
|
||||
public ErrorView(I18N i18n) {
|
||||
VerticalLayout layout = new VerticalLayout();
|
||||
Label label = new I18nLabel(i18n, Messages.ERROR4XX);
|
||||
layout.addComponent(label);
|
||||
layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER);
|
||||
layout.setSizeFull();
|
||||
setCompositionRoot(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter(ViewChangeListener.ViewChangeEvent event) {
|
||||
}
|
||||
}
|
140
src/main/java/com/faendir/acra/ui/view/MainView.java
Normal file
140
src/main/java/com/faendir/acra/ui/view/MainView.java
Normal file
|
@ -0,0 +1,140 @@
|
|||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.User;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.ui.base.ParentLayout;
|
||||
import com.faendir.acra.ui.base.Path;
|
||||
import com.faendir.acra.ui.base.popup.Popup;
|
||||
import com.faendir.acra.ui.component.DropdownMenu;
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.faendir.acra.ui.view.user.ChangePasswordView;
|
||||
import com.faendir.acra.ui.view.user.UserManager;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.RouterLink;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import com.vaadin.flow.theme.lumo.Lumo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 13.07.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
public class MainView extends ParentLayout {
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final ApplicationContext applicationContext;
|
||||
private ParentLayout layout;
|
||||
|
||||
@Autowired
|
||||
public MainView(AuthenticationManager authenticationManager, ApplicationContext applicationContext) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.applicationContext = applicationContext;
|
||||
setAlignItems(Alignment.CENTER);
|
||||
setJustifyContentMode(JustifyContentMode.CENTER);
|
||||
setSizeFull();
|
||||
getStyle().set("flex-direction", "column");
|
||||
layout = new ParentLayout();
|
||||
layout.setWidth("100%");
|
||||
expand(layout);
|
||||
layout.getStyle().set("overflow", "hidden");
|
||||
setRouterRoot(layout);
|
||||
if (SecurityUtils.isLoggedIn()) {
|
||||
showMain();
|
||||
} else {
|
||||
showLogin();
|
||||
}
|
||||
}
|
||||
|
||||
private void showMain() {
|
||||
Translatable<Checkbox> darkTheme = Translatable.createCheckbox(false, Messages.DARK_THEME);
|
||||
darkTheme.getContent().addValueChangeListener(e -> {
|
||||
UI.getCurrent().getElement().setAttribute("theme", e.getValue() ? Lumo.DARK : Lumo.LIGHT);
|
||||
});
|
||||
Translatable<RouterLink> userManager = Translatable.createRouterLink(UserManager.class, Messages.USER_MANAGER);
|
||||
userManager.setDefaultTextStyle();
|
||||
Translatable<RouterLink> changePassword = Translatable.createRouterLink(ChangePasswordView.class, Messages.CHANGE_PASSWORD);
|
||||
changePassword.setDefaultTextStyle();
|
||||
Translatable<Button> logout = Translatable.createButton(e -> logout(), Messages.LOGOUT);
|
||||
logout.getElement().setAttribute("theme", "tertiary");
|
||||
logout.setDefaultTextStyle();
|
||||
Translatable<Button> about = Translatable.createButton(e -> {
|
||||
Div content = new Div();
|
||||
content.getElement().setProperty("innerHTML", getTranslation(Messages.FOOTER));
|
||||
new Popup().addComponent(content).addCloseButton().show();
|
||||
}, Messages.ABOUT);
|
||||
about.getElement().setAttribute("theme", "tertiary");
|
||||
about.setDefaultTextStyle();
|
||||
DropdownMenu menu = new DropdownMenu(new FormLayout(darkTheme, userManager, changePassword, logout, about));
|
||||
menu.getStyle().set("margin", "1rem");
|
||||
menu.setLabel(SecurityUtils.getUsername());
|
||||
menu.setMinWidth(130, Unit.PIXEL);
|
||||
menu.setOrigin(DropdownMenu.Origin.RIGHT);
|
||||
Path path = new Path(applicationContext);
|
||||
FlexLayout header = new FlexLayout(path, menu);
|
||||
header.expand(path);
|
||||
header.setWidthFull();
|
||||
header.getStyle().set("box-shadow", "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)");
|
||||
header.getStyle().set("border-radius", "2px");
|
||||
removeAll();
|
||||
add(header, layout);
|
||||
}
|
||||
|
||||
private void showLogin() {
|
||||
Translatable<Image> logo = Translatable.createImage("frontend/logo.png", Messages.ACRARIUM);
|
||||
logo.setWidth(0, Unit.PIXEL);
|
||||
logo.setPadding(1, Unit.REM);
|
||||
FlexLayout logoWrapper = new FlexLayout(logo);
|
||||
logoWrapper.expand(logo);
|
||||
TextField username = new TextField();
|
||||
PasswordField password = new PasswordField();
|
||||
Translatable<Button> login = Translatable.createButton(event -> login(username.getValue(), password.getValue()), Messages.LOGIN);
|
||||
login.setWidthFull();
|
||||
FlexLayout loginForm = new FlexLayout(logoWrapper, username, password, login);
|
||||
loginForm.setFlexDirection(FlexDirection.COLUMN);
|
||||
loginForm.setSizeUndefined();
|
||||
setContent(loginForm);
|
||||
}
|
||||
|
||||
private void login(@NonNull String username, @NonNull String password) {
|
||||
try {
|
||||
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
|
||||
if (!token.getAuthorities().contains(User.Role.USER)) {
|
||||
throw new InsufficientAuthenticationException("Missing required role");
|
||||
}
|
||||
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
UI.getCurrent().getPage().reload();
|
||||
} catch (AuthenticationException ex) {
|
||||
Notification.show(getTranslation(Messages.LOGIN_FAILED), 5000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
SecurityContextHolder.clearContext();
|
||||
getUI().ifPresent(ui -> {
|
||||
ui.getPage().reload();
|
||||
ui.getSession().close();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,146 +1,57 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nCheckBox;
|
||||
import com.faendir.acra.i18n.I18nIntStepper;
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.I18nTextField;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.QApp;
|
||||
import com.faendir.acra.model.QBug;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.model.User;
|
||||
import com.faendir.acra.model.view.VApp;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.SingleViewProvider;
|
||||
import com.faendir.acra.ui.view.app.AppView;
|
||||
import com.faendir.acra.ui.view.base.ConfigurationLabel;
|
||||
import com.faendir.acra.ui.view.base.layout.MyGrid;
|
||||
import com.faendir.acra.ui.view.base.navigation.BaseView;
|
||||
import com.faendir.acra.ui.view.base.popup.Popup;
|
||||
import com.faendir.acra.ui.view.base.popup.ValidatedField;
|
||||
import com.faendir.acra.util.ImportResult;
|
||||
import com.vaadin.navigator.ViewChangeListener;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.TextField;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.faendir.acra.ui.base.HasRoute;
|
||||
import com.faendir.acra.ui.base.MyGrid;
|
||||
import com.faendir.acra.ui.base.Path;
|
||||
import com.faendir.acra.ui.view.app.tabs.BugTab;
|
||||
import com.vaadin.flow.component.AttachEvent;
|
||||
import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 23.03.2017
|
||||
* @author lukas
|
||||
* @since 13.07.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class Overview extends BaseView {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
private MyGrid<VApp> grid;
|
||||
@Route(value = "", layout = MainView.class)
|
||||
public class Overview extends VerticalLayout implements ComponentEventListener<AttachEvent>, HasRoute {
|
||||
private final DataService dataService;
|
||||
|
||||
@Autowired
|
||||
public Overview(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
public Overview(DataService dataService) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
addAttachListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter(ViewChangeListener.ViewChangeEvent event) {
|
||||
grid = new MyGrid<>(dataService.getAppProvider(), i18n, Messages.APPS);
|
||||
grid.setResponsive(true);
|
||||
grid.setSizeToRows();
|
||||
public void onComponentEvent(AttachEvent event) {
|
||||
removeAll();
|
||||
MyGrid<VApp> grid = new MyGrid<>(dataService.getAppProvider());
|
||||
grid.setSelectionMode(Grid.SelectionMode.NONE);
|
||||
grid.addColumn(VApp::getName, QApp.app.name, Messages.NAME);
|
||||
grid.addColumn(VApp::getBugCount, QBug.bug.countDistinct(), Messages.BUGS);
|
||||
grid.addColumn(VApp::getReportCount, QReport.report.count(), Messages.REPORTS);
|
||||
grid.addOnClickNavigation(getNavigationManager(), AppView.class, e -> String.valueOf(e.getItem().getId()));
|
||||
VerticalLayout layout = new VerticalLayout();
|
||||
if (SecurityUtils.hasRole(User.Role.ADMIN)) {
|
||||
Button add = new I18nButton(i18n, Messages.NEW_APP);
|
||||
add.addClickListener(e -> {
|
||||
TextField name = new I18nTextField(i18n, Messages.NAME);
|
||||
new Popup(i18n, Messages.NEW_APP).addComponent(name).addCreateButton(popup -> {
|
||||
popup.clear().addComponent(new ConfigurationLabel(dataService.createNewApp(name.getValue()), i18n)).addCloseButton().show();
|
||||
grid.getDataProvider().refreshAll();
|
||||
}).show();
|
||||
});
|
||||
Button importButton = new I18nButton(i18n, Messages.IMPORT_ACRALYZER);
|
||||
importButton.addClickListener(e -> {
|
||||
I18nTextField host = new I18nTextField("localhost", i18n, Messages.HOST);
|
||||
I18nIntStepper port = new I18nIntStepper(5984, i18n, Messages.PORT);
|
||||
port.setMinValue(0);
|
||||
port.setMaxValue(65535);
|
||||
I18nCheckBox ssl = new I18nCheckBox(i18n, Messages.SSL);
|
||||
I18nTextField databaseName = new I18nTextField("acra-myapp", i18n, Messages.DATABASE_NAME);
|
||||
new Popup(i18n, Messages.IMPORT_ACRALYZER).addValidatedField(ValidatedField.of(host), true)
|
||||
.addValidatedField(ValidatedField.of(port), true)
|
||||
.addValidatedField(ValidatedField.of(ssl), true)
|
||||
.addValidatedField(ValidatedField.of(databaseName), true)
|
||||
.addCreateButton(popup -> {
|
||||
ImportResult importResult = dataService.importFromAcraStorage(host.getValue(), port.getValue(), ssl.getValue(), databaseName.getValue());
|
||||
popup.clear()
|
||||
.addComponent(new I18nLabel(i18n, Messages.IMPORT_SUCCESS, importResult.getSuccessCount(), importResult.getTotalCount()))
|
||||
.addComponent(new ConfigurationLabel(importResult.getUser(), i18n))
|
||||
.addCloseButton()
|
||||
.show();
|
||||
grid.getDataProvider().refreshAll();
|
||||
})
|
||||
.show();
|
||||
});
|
||||
layout.addComponent(new HorizontalLayout(add, importButton));
|
||||
}
|
||||
layout.addComponent(grid);
|
||||
layout.addStyleNames(AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM, AcraTheme.MAX_WIDTH_728);
|
||||
VerticalLayout root = new VerticalLayout(layout);
|
||||
root.setSpacing(false);
|
||||
root.setSizeFull();
|
||||
root.setComponentAlignment(layout, Alignment.TOP_CENTER);
|
||||
root.addStyleName(AcraTheme.NO_PADDING);
|
||||
setCompositionRoot(root);
|
||||
grid.addOnClickNavigation(BugTab.class, VApp::getId);
|
||||
setSizeFull();
|
||||
add(grid);
|
||||
}
|
||||
|
||||
@SpringComponent
|
||||
@UIScope
|
||||
public static class Provider extends SingleViewProvider<Overview> {
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
protected Provider(@NonNull I18N i18n) {
|
||||
super(Overview.class);
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle(String parameter) {
|
||||
return i18n.get(Messages.ACRARIUM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "";
|
||||
}
|
||||
@Override
|
||||
@NonNull
|
||||
public Path.Element<?> getPathElement() {
|
||||
return new Path.ImageElement<>(getClass(), "frontend/logo.png", Messages.ACRARIUM);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,111 +1,68 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.app;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.annotation.RequiresAppPermission;
|
||||
import com.faendir.acra.ui.navigation.MyNavigator;
|
||||
import com.faendir.acra.ui.navigation.SingleParametrizedViewProvider;
|
||||
import com.faendir.acra.ui.base.ActiveChildAware;
|
||||
import com.faendir.acra.ui.base.ParentLayout;
|
||||
import com.faendir.acra.ui.component.Tab;
|
||||
import com.faendir.acra.ui.view.MainView;
|
||||
import com.faendir.acra.ui.view.app.tabs.AdminTab;
|
||||
import com.faendir.acra.ui.view.app.tabs.AppTab;
|
||||
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
|
||||
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.faendir.acra.ui.view.app.tabs.BugTab;
|
||||
import com.faendir.acra.ui.view.app.tabs.ReportTab;
|
||||
import com.faendir.acra.ui.view.app.tabs.StatisticsTab;
|
||||
import com.vaadin.flow.component.tabs.Tabs;
|
||||
import com.vaadin.flow.router.RoutePrefix;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 13.05.2017
|
||||
* @author lukas
|
||||
* @since 13.07.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
@RequiresAppPermission(Permission.Level.VIEW)
|
||||
public class AppView extends ParametrizedBaseView<Pair<App, String>> {
|
||||
private final List<AppTab> tabs;
|
||||
@RoutePrefix("app")
|
||||
@com.vaadin.flow.router.ParentLayout(MainView.class)
|
||||
public class AppView extends ParentLayout implements ActiveChildAware<AppTab<?>, App> {
|
||||
private App app;
|
||||
private final Tabs tabs;
|
||||
|
||||
@Autowired
|
||||
public AppView(@Lazy List<AppTab> tabs) {
|
||||
this.tabs = tabs;
|
||||
public AppView() {
|
||||
tabs = new Tabs(Stream.of(TabDef.values()).map(def -> new Tab(def.labelId)).toArray(Tab[]::new));
|
||||
tabs.addSelectedChangeListener(e -> getUI().ifPresent(ui -> ui.navigate(TabDef.values()[e.getSource().getSelectedIndex()].tabClass, app.getId())));
|
||||
setSizeFull();
|
||||
ParentLayout content = new ParentLayout();
|
||||
content.setWidthFull();
|
||||
expand(content);
|
||||
content.getStyle().set("overflow","auto");
|
||||
setRouterRoot(content);
|
||||
getStyle().set("flex-direction","column");
|
||||
removeAll();
|
||||
add(tabs, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enter(@NonNull Pair<App, String> parameter) {
|
||||
MyTabSheet<App> tabSheet = new MyTabSheet<>(parameter.getFirst(), getNavigationManager(), tabs);
|
||||
tabSheet.setSizeFull();
|
||||
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(parameter.getFirst().getId() + "/" + e.getTabSheet().getSelectedTab().getId()));
|
||||
tabSheet.addMiddleClickListener(e -> getNavigationManager().openInNewTab(parameter.getFirst().getId() + "/" + e.getTab().getId()));
|
||||
tabSheet.guessInitialTab(parameter.getSecond());
|
||||
Panel panel = new Panel(tabSheet);
|
||||
panel.setSizeFull();
|
||||
panel.addStyleNames(AcraTheme.NO_BORDER, AcraTheme.NO_BACKGROUND, AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
|
||||
setCompositionRoot(panel);
|
||||
public void setActiveChild(AppTab<?> child, App parameter) {
|
||||
app = parameter;
|
||||
Stream.of(TabDef.values()).filter(def -> def.tabClass.equals(child.getClass())).findAny().ifPresent(def -> tabs.setSelectedIndex(def.ordinal()));
|
||||
}
|
||||
|
||||
@SpringComponent
|
||||
@UIScope
|
||||
public static class Provider extends SingleParametrizedViewProvider<Pair<App, String>, AppView> {
|
||||
@NonNull private final DataService dataService;
|
||||
private enum TabDef {
|
||||
BUG(Messages.BUGS, BugTab.class),
|
||||
REPORT(Messages.REPORTS, ReportTab.class),
|
||||
STATISTICS(Messages.STATISTICS, StatisticsTab.class),
|
||||
ADMIN(Messages.ADMIN, AdminTab.class);
|
||||
private String labelId;
|
||||
private Class<? extends AppTab<?>> tabClass;
|
||||
|
||||
@Autowired
|
||||
public Provider(@NonNull DataService dataService) {
|
||||
super(AppView.class);
|
||||
this.dataService = dataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidParameter(Pair<App, String> parameter) {
|
||||
return parameter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pair<App, String> parseParameter(String parameter) {
|
||||
String[] parameters = parameter.split(MyNavigator.SEPARATOR);
|
||||
if (parameters.length > 0) {
|
||||
Optional<App> app = dataService.findApp(parameters[0]);
|
||||
if (app.isPresent()) {
|
||||
return Pair.of(app.get(), parameters.length == 1 ? "" : parameters[1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected App toApp(Pair<App, String> parameter) {
|
||||
return parameter.getFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle(Pair<App, String> parameter) {
|
||||
return parameter.getFirst().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app";
|
||||
TabDef(String labelId, Class<? extends AppTab<?>> tabClass) {
|
||||
this.labelId = labelId;
|
||||
this.tabClass = tabClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,179 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.app.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.ui.annotation.RequiresAppPermission;
|
||||
import com.faendir.acra.ui.view.app.tabs.panels.AdminPanel;
|
||||
import com.faendir.acra.ui.view.base.layout.PanelFlexTab;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.faendir.acra.model.ProguardMapping;
|
||||
import com.faendir.acra.model.QProguardMapping;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.component.Card;
|
||||
import com.faendir.acra.ui.base.ConfigurationLabel;
|
||||
import com.faendir.acra.ui.base.MyGrid;
|
||||
import com.faendir.acra.ui.base.popup.Popup;
|
||||
import com.faendir.acra.ui.component.DownloadButton;
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.faendir.acra.ui.view.Overview;
|
||||
import com.faendir.acra.ui.view.app.AppView;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.html.Input;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.component.upload.Upload;
|
||||
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.faendir.acra.model.QReport.report;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
* @author lukas
|
||||
* @since 18.10.18
|
||||
*/
|
||||
@RequiresAppPermission(Permission.Level.ADMIN)
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class AdminTab extends PanelFlexTab<App> implements AppTab {
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Route(value = "admin", layout = AppView.class)
|
||||
public class AdminTab extends AppTab<FlexLayout> {
|
||||
@Autowired
|
||||
public AdminTab(@NonNull List<AdminPanel> panels, @NonNull I18N i18n) {
|
||||
super(panels);
|
||||
this.i18n = i18n;
|
||||
public AdminTab(DataService dataService) {
|
||||
super(dataService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.ADMIN);
|
||||
}
|
||||
void init(App app) {
|
||||
getContent().removeAll();
|
||||
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
|
||||
getContent().setWidthFull();
|
||||
MyGrid<ProguardMapping> mappingGrid = new MyGrid<>(getDataService().getMappingProvider(app));
|
||||
mappingGrid.setHeightToRows();
|
||||
mappingGrid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, Messages.VERSION);
|
||||
Card mappingCard = new Card(mappingGrid);
|
||||
mappingCard.setHeader(Translatable.createText(Messages.DE_OBFUSCATION));
|
||||
mappingCard.setWidth(500, HasSize.Unit.PIXEL);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
mappingGrid.addColumn(new ComponentRenderer<>(mapping -> new Button(new Icon(VaadinIcon.TRASH), e -> new Popup().addComponent(Translatable.createText(Messages.DELETE_MAPPING_CONFIRM, mapping.getVersionCode())).addYesNoButtons(p -> {
|
||||
getDataService().delete(mapping);
|
||||
mappingGrid.getDataProvider().refreshAll();
|
||||
}, true).show())));
|
||||
mappingCard.add(Translatable.createButton(e -> {
|
||||
Translatable<TextField> version = Translatable.createTextField(String.valueOf(getDataService().getMaximumMappingVersion(app).map(i -> i + 1).orElse(1)), Messages.VERSION_CODE);
|
||||
MemoryBuffer buffer = new MemoryBuffer();
|
||||
Upload upload = new Upload(buffer);
|
||||
new Popup()
|
||||
.setTitle(Messages.NEW_MAPPING)
|
||||
.addComponent(version)
|
||||
.addComponent(upload)
|
||||
.addCreateButton(popup -> {
|
||||
try {
|
||||
getDataService().store(new ProguardMapping(app, Integer.valueOf(version.getContent().getValue()), StreamUtils.copyToString(buffer.getInputStream(), Charset.defaultCharset())));
|
||||
} catch (Exception ex) {
|
||||
//TODO
|
||||
}
|
||||
mappingGrid.getDataProvider().refreshAll();
|
||||
}, true)
|
||||
.show();
|
||||
}, Messages.NEW_FILE));
|
||||
}
|
||||
getContent().add(mappingCard);
|
||||
getContent().expand(mappingCard);
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "admin";
|
||||
}
|
||||
Translatable<ComboBox<String>> mailBox = Translatable.createComboBox(getDataService().getFromReports(app, null, QReport.report.userEmail), Messages.BY_MAIL);
|
||||
mailBox.setWidthFull();
|
||||
Translatable<ComboBox<String>> idBox = Translatable.createComboBox(getDataService().getFromReports(app, null, QReport.report.installationId), Messages.BY_ID);
|
||||
idBox.setWidthFull();
|
||||
DownloadButton download = new DownloadButton(new StreamResource("reports.json", () -> {
|
||||
BooleanExpression where = null;
|
||||
String name = "";
|
||||
String mail = mailBox.getContent().getValue();
|
||||
String id = idBox.getContent().getValue();
|
||||
if (mail != null && !mail.isEmpty()) {
|
||||
where = report.userEmail.eq(mail);
|
||||
name += "_" + mail;
|
||||
}
|
||||
if (id != null && !id.isEmpty()) {
|
||||
where = report.installationId.eq(id).and(where);
|
||||
name += "_" + id;
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
return new ByteArrayInputStream(new byte[0]);
|
||||
}
|
||||
return new ByteArrayInputStream(getDataService().getFromReports(app, where, report.content, report.id).stream().collect(Collectors.joining(", ", "[", "]")).getBytes(StandardCharsets.UTF_8));
|
||||
}), Messages.DOWNLOAD);
|
||||
download.setSizeFull();
|
||||
Card exportCard = new Card(mailBox, idBox, download);
|
||||
exportCard.setHeader(Translatable.createText(Messages.EXPORT));
|
||||
exportCard.setWidth(500, HasSize.Unit.PIXEL);
|
||||
getContent().add(exportCard);
|
||||
getContent().expand(exportCard);
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 4;
|
||||
Translatable<Button> configButton = Translatable.createButton(e -> new Popup().setTitle(Messages.NEW_ACRA_CONFIG_CONFIRM)
|
||||
.addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(getDataService().recreateReporterUser(app))).addCloseButton().show())
|
||||
.show(), Messages.NEW_ACRA_CONFIG);
|
||||
configButton.setWidthFull();
|
||||
Translatable<Button> matchingButton = Translatable.createButton(e -> {
|
||||
App.Configuration configuration = app.getConfiguration();
|
||||
Input score = new Input();
|
||||
score.setType("range");
|
||||
score.getElement().setProperty("min", 0);
|
||||
score.getElement().setProperty("max", 100);
|
||||
score.setValue(String.valueOf(configuration.getMinScore()));
|
||||
new Popup().addComponent(score)
|
||||
.setTitle(Messages.NEW_BUG_CONFIG_CONFIRM)
|
||||
.addYesNoButtons(p -> getDataService().changeConfiguration(app, new App.Configuration(Integer.parseInt(score.getValue()))), true)
|
||||
.show();
|
||||
}, Messages.NEW_BUG_CONFIG);
|
||||
matchingButton.setWidthFull();
|
||||
TextField age = new TextField();
|
||||
age.setValue("30");
|
||||
age.setWidth("100%");
|
||||
FlexLayout purgeAge = new FlexLayout();
|
||||
purgeAge.setWidthFull();
|
||||
purgeAge.preventWhiteSpaceBreaking();
|
||||
purgeAge.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
purgeAge.add(Translatable.createButton(e -> getDataService().deleteReportsOlderThanDays(app, Integer.parseInt(age.getValue())), Messages.PURGE),
|
||||
Translatable.createLabel(Messages.REPORTS_OLDER_THAN1),
|
||||
age, Translatable.createLabel(Messages.REPORTS_OLDER_THAN2));
|
||||
purgeAge.expand(age);
|
||||
ComboBox<Integer> versionBox = new ComboBox<>(null, getDataService().getFromReports(app, null, QReport.report.stacktrace.version.code));
|
||||
versionBox.setWidth("100%");
|
||||
FlexLayout purgeVersion = new FlexLayout();
|
||||
purgeVersion.setWidthFull();
|
||||
purgeVersion.preventWhiteSpaceBreaking();
|
||||
purgeVersion.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
purgeVersion.add(Translatable.createButton(e -> {
|
||||
if (versionBox.getValue() != null) {
|
||||
getDataService().deleteReportsBeforeVersion(app, versionBox.getValue());
|
||||
}
|
||||
}, Messages.PURGE), Translatable.createLabel(Messages.REPORTS_BEFORE_VERSION), versionBox);
|
||||
purgeVersion.expand(versionBox);
|
||||
Translatable<Button> deleteButton = Translatable.createButton(e -> new Popup().setTitle(Messages.DELETE_APP_CONFIRM).addYesNoButtons(popup -> {
|
||||
getDataService().delete(app);
|
||||
UI.getCurrent().navigate(Overview.class);
|
||||
}, true).show(), Messages.DELETE_APP);
|
||||
deleteButton.setWidthFull();
|
||||
Card dangerCard = new Card(configButton, matchingButton, purgeAge, purgeVersion, deleteButton);
|
||||
dangerCard.setHeader(Translatable.createText(Messages.DANGER_ZONE));
|
||||
dangerCard.setWidth(500, HasSize.Unit.PIXEL);
|
||||
getContent().add(dangerCard);
|
||||
getContent().expand(dangerCard);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,73 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.ui.view.base.layout.ComponentFactory;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.base.ActiveChildAware;
|
||||
import com.faendir.acra.ui.base.HasRoute;
|
||||
import com.faendir.acra.ui.base.HasSecureIntParameter;
|
||||
import com.faendir.acra.ui.base.Path;
|
||||
import com.faendir.acra.ui.view.Overview;
|
||||
import com.vaadin.flow.component.AttachEvent;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 27.03.2018
|
||||
* @author lukas
|
||||
* @since 14.07.18
|
||||
*/
|
||||
public interface AppTab extends ComponentFactory<App>, Serializable {
|
||||
public abstract class AppTab<T extends Component> extends Composite<T> implements HasSecureIntParameter, HasRoute {
|
||||
private final DataService dataService;
|
||||
private App app;
|
||||
|
||||
public AppTab(DataService dataService) {
|
||||
this.dataService = dataService;
|
||||
}
|
||||
|
||||
public DataService getDataService() {
|
||||
return dataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameterSecure(BeforeEvent event, Integer parameter) {
|
||||
Optional<App> app = dataService.findApp(parameter);
|
||||
if (app.isPresent()) {
|
||||
this.app = app.get();
|
||||
} else {
|
||||
event.rerouteToError(IllegalArgumentException.class);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void init(App app);
|
||||
|
||||
@Override
|
||||
protected void onAttach(AttachEvent attachEvent) {
|
||||
super.onAttach(attachEvent);
|
||||
init(this.app);
|
||||
Optional<Component> parent = getParent();
|
||||
while (parent.isPresent()) {
|
||||
if (parent.get() instanceof ActiveChildAware) {
|
||||
//noinspection unchecked
|
||||
((ActiveChildAware<AppTab<?>, App>) parent.get()).setActiveChild(this, app);
|
||||
break;
|
||||
}
|
||||
parent = parent.get().getParent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path.Element<?> getPathElement() {
|
||||
//noinspection unchecked
|
||||
return new Path.ParametrizedTextElement<>((Class<? extends AppTab<?>>) getClass(), app.getId(), Messages.ONE_ARG, app.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent<?> getLogicalParent() {
|
||||
return new Parent<>(Overview.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,5 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.app.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nCheckBox;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
|
@ -25,110 +8,89 @@ import com.faendir.acra.model.QReport;
|
|||
import com.faendir.acra.model.view.VBug;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.MyCheckBox;
|
||||
import com.faendir.acra.ui.view.base.layout.MyGrid;
|
||||
import com.faendir.acra.ui.view.base.popup.Popup;
|
||||
import com.faendir.acra.ui.base.MyGrid;
|
||||
import com.faendir.acra.ui.base.popup.Popup;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.faendir.acra.ui.view.app.AppView;
|
||||
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
|
||||
import com.faendir.acra.util.TimeSpanRenderer;
|
||||
import com.vaadin.server.Sizeable;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Notification;
|
||||
import com.vaadin.ui.RadioButtonGroup;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.renderers.ComponentRenderer;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 17.05.2017
|
||||
* @author lukas
|
||||
* @since 14.07.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class BugTab implements AppTab {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Route(value = "bug", layout = AppView.class)
|
||||
public class BugTab extends AppTab<VerticalLayout> {
|
||||
@Autowired
|
||||
public BugTab(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
public BugTab(DataService dataService) {
|
||||
super(dataService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.BUGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "bugs";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
VerticalLayout layout = new VerticalLayout();
|
||||
void init(App app) {
|
||||
HorizontalLayout header = new HorizontalLayout();
|
||||
header.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
header.addStyleName(AcraTheme.PADDING_TOP);
|
||||
layout.addComponent(header);
|
||||
layout.setComponentAlignment(header, Alignment.MIDDLE_LEFT);
|
||||
CheckBox hideSolved = new I18nCheckBox(true, i18n, Messages.HIDE_SOLVED);
|
||||
MyGrid<VBug> bugs = new MyGrid<>(dataService.getBugProvider(app, hideSolved::getValue), i18n);
|
||||
header.setWidth("100%");
|
||||
getContent().add(header);
|
||||
getContent().setAlignItems(FlexComponent.Alignment.START);
|
||||
Translatable<Checkbox> hideSolved = Translatable.createCheckbox(true, Messages.HIDE_SOLVED);
|
||||
MyGrid<VBug> bugs = new MyGrid<>(getDataService().getBugProvider(app, () -> hideSolved.getContent().getValue()));
|
||||
bugs.setSelectionMode(Grid.SelectionMode.MULTI);
|
||||
hideSolved.addValueChangeListener(e -> layout.getUI().access(() -> {
|
||||
hideSolved.getContent().addValueChangeListener(e -> getUI().ifPresent(ui -> ui.access(() -> {
|
||||
bugs.deselectAll();
|
||||
bugs.getDataProvider().refreshAll();
|
||||
}));
|
||||
Button merge = new I18nButton(e -> {
|
||||
})));
|
||||
Translatable<Button> merge = Translatable.createButton(e -> {
|
||||
List<VBug> selectedItems = new ArrayList<>(bugs.getSelectedItems());
|
||||
if (selectedItems.size() > 1) {
|
||||
RadioButtonGroup<String> titles = new RadioButtonGroup<>("", selectedItems.stream().map(bug -> bug.getBug().getTitle()).collect(Collectors.toList()));
|
||||
titles.setSelectedItem(selectedItems.get(0).getBug().getTitle());
|
||||
new Popup(i18n, Messages.CHOOSE_BUG_GROUP_TITLE).addComponent(titles).addCreateButton(p -> {
|
||||
dataService.mergeBugs(selectedItems.stream().map(VBug::getBug).collect(Collectors.toList()), titles.getSelectedItem().orElseThrow(IllegalStateException::new));
|
||||
RadioButtonGroup<String> titles = new RadioButtonGroup<>();
|
||||
titles.setItems(selectedItems.stream().map(bug -> bug.getBug().getTitle()).collect(Collectors.toList()));
|
||||
titles.setValue(selectedItems.get(0).getBug().getTitle());
|
||||
new Popup().setTitle(Messages.CHOOSE_BUG_GROUP_TITLE).addComponent(titles).addCreateButton(p -> {
|
||||
getDataService().mergeBugs(selectedItems.stream().map(VBug::getBug).collect(Collectors.toList()), titles.getValue());
|
||||
bugs.deselectAll();
|
||||
bugs.getDataProvider().refreshAll();
|
||||
}, true).show();
|
||||
} else {
|
||||
Notification.show(i18n.get(Messages.ONLY_ONE_BUG_SELECTED), Notification.Type.ERROR_MESSAGE);
|
||||
Notification.show(Messages.ONLY_ONE_BUG_SELECTED);
|
||||
}
|
||||
}, i18n, Messages.MERGE_BUGS);
|
||||
header.addComponent(merge);
|
||||
header.addComponent(hideSolved);
|
||||
header.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
|
||||
}, Messages.MERGE_BUGS);
|
||||
header.add(merge, hideSolved);
|
||||
header.setSpacing(false);
|
||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
bugs.addColumn(VBug::getReportCount, QReport.report.count(), Messages.REPORTS);
|
||||
bugs.sort(bugs.addColumn(VBug::getLastReport, new TimeSpanRenderer(), QReport.report.date.max(), Messages.LATEST_REPORT), SortDirection.DESCENDING);
|
||||
bugs.addColumn(new TimeSpanRenderer<>(VBug::getLastReport), QReport.report.date.max(), Messages.LATEST_REPORT);
|
||||
bugs.addColumn(VBug::getHighestVersionCode, QReport.report.stacktrace.version.code.max(), Messages.LATEST_VERSION);
|
||||
bugs.addColumn(VBug::getUserCount, QReport.report.installationId.countDistinct(), Messages.AFFECTED_USERS);
|
||||
bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, Messages.TITLE).setExpandRatio(1).setMinimumWidthFromContent(false);
|
||||
bugs.addOnClickNavigation(navigationManager, com.faendir.acra.ui.view.bug.BugView.class, bugItemClick -> String.valueOf(bugItemClick.getItem().getBug().getId()));
|
||||
bugs.addColumn(bug -> new MyCheckBox(bug.getBug().isSolved(),
|
||||
SecurityUtils.hasPermission(app, Permission.Level.EDIT),
|
||||
e -> dataService.setBugSolved(bug.getBug(), e.getValue())), new ComponentRenderer(), QBug.bug.solved, Messages.SOLVED);
|
||||
layout.addComponent(bugs);
|
||||
layout.setExpandRatio(bugs, 1);
|
||||
layout.setSizeFull();
|
||||
layout.addStyleName(AcraTheme.NO_PADDING);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, Messages.TITLE).setFlexGrow(1);
|
||||
bugs.addColumn(new ComponentRenderer<>(bug -> {
|
||||
Checkbox checkbox = new Checkbox(bug.getBug().isSolved());
|
||||
checkbox.setEnabled(SecurityUtils.hasPermission(app, Permission.Level.EDIT));
|
||||
checkbox.addValueChangeListener(e -> getDataService().setBugSolved(bug.getBug(), e.getValue()));
|
||||
return checkbox;
|
||||
}), QBug.bug.solved, Messages.SOLVED);
|
||||
bugs.addOnClickNavigation(ReportTab.class, bug -> bug.getBug().getId());
|
||||
getContent().removeAll();
|
||||
getContent().add(bugs);
|
||||
getContent().setFlexGrow(1, bugs);
|
||||
getContent().setSizeFull();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,37 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.service.AvatarService;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.ReportList;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.faendir.acra.ui.base.ReportList;
|
||||
import com.faendir.acra.ui.view.app.AppView;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 29.05.2017
|
||||
* @author lukas
|
||||
* @since 14.07.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class ReportTab implements AppTab {
|
||||
@NonNull private final DataService dataService;
|
||||
@Route(value = "report", layout = AppView.class)
|
||||
public class ReportTab extends AppTab<Div> {
|
||||
@NonNull private final AvatarService avatarService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Autowired
|
||||
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
|
||||
super(dataService);
|
||||
this.avatarService = avatarService;
|
||||
this.i18n = i18n;
|
||||
getContent().setSizeFull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app), i18n);
|
||||
content.setSizeFull();
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.REPORTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "reports";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
void init(App app) {
|
||||
getContent().removeAll();
|
||||
getContent().add(new ReportList(app, getDataService().getReportProvider(app), avatarService, getDataService()::delete));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,33 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.statistics.Statistics;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.faendir.acra.ui.base.statistics.Statistics;
|
||||
import com.faendir.acra.ui.view.app.AppView;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 22.05.2017
|
||||
* @author lukas
|
||||
* @since 11.10.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class StatisticsTab implements AppTab {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Route(value = "statistics", layout = AppView.class)
|
||||
public class StatisticsTab extends AppTab<Div> {
|
||||
@Autowired
|
||||
public StatisticsTab(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
public StatisticsTab(@NonNull DataService dataService) {
|
||||
super(dataService);
|
||||
getContent().setSizeFull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
Panel root = new Panel(new Statistics(app, null, dataService, i18n));
|
||||
root.setSizeFull();
|
||||
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.STATISTICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "statistics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 2;
|
||||
void init(App app) {
|
||||
getContent().removeAll();
|
||||
getContent().add(new Statistics(app, null, getDataService()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs.panels;
|
||||
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.ui.view.base.layout.PanelContentFactory;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
public interface AdminPanel extends PanelContentFactory<App> {
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.app.tabs.panels;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.I18nSlider;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.ConfigurationLabel;
|
||||
import com.faendir.acra.ui.view.base.popup.Popup;
|
||||
import com.faendir.acra.ui.view.base.popup.ValidatedField;
|
||||
import com.vaadin.icons.VaadinIcons;
|
||||
import com.vaadin.server.Resource;
|
||||
import com.vaadin.server.Sizeable;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.risto.stepper.IntStepper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class DangerPanel implements AdminPanel {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Autowired
|
||||
public DangerPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
Button configButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(ContentMode.HTML, i18n, Messages.NEW_ACRA_CONFIG_CONFIRM))
|
||||
.addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(dataService.recreateReporterUser(app), i18n)).addCloseButton().show())
|
||||
.show(), i18n, Messages.NEW_ACRA_CONFIG);
|
||||
configButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
Button matchingButton = new I18nButton(e -> {
|
||||
App.Configuration configuration = app.getConfiguration();
|
||||
I18nSlider score = new I18nSlider(0, 100, i18n, Messages.MIN_SCORE);
|
||||
score.setValue((double) configuration.getMinScore());
|
||||
new Popup(i18n, Messages.CONFIRM).addValidatedField(ValidatedField.of(score), true)
|
||||
.addComponent(new I18nLabel(i18n, Messages.NEW_BUG_CONFIG_CONFIRM))
|
||||
.addYesNoButtons(p -> dataService.changeConfiguration(app, new App.Configuration(score.getValue().intValue())), true)
|
||||
.show();
|
||||
}, i18n, Messages.NEW_BUG_CONFIG);
|
||||
matchingButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
IntStepper age = new IntStepper();
|
||||
age.setValue(30);
|
||||
age.setMinValue(0);
|
||||
age.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
HorizontalLayout purgeAge = new HorizontalLayout();
|
||||
purgeAge.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
|
||||
purgeAge.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
purgeAge.addStyleName(AcraTheme.NO_MARGIN);
|
||||
purgeAge.addComponents(new I18nButton(e -> dataService.deleteReportsOlderThanDays(app, age.getValue()), i18n, Messages.PURGE),
|
||||
new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN1),
|
||||
age,
|
||||
new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN2));
|
||||
purgeAge.setExpandRatio(age, 1);
|
||||
ComboBox<Integer> versionBox = new ComboBox<>(null, dataService.getFromReports(app, null, QReport.report.stacktrace.version.code));
|
||||
versionBox.setEmptySelectionAllowed(false);
|
||||
versionBox.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
HorizontalLayout purgeVersion = new HorizontalLayout();
|
||||
purgeVersion.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
purgeVersion.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
|
||||
purgeVersion.addStyleName(AcraTheme.NO_MARGIN);
|
||||
purgeVersion.addComponents(new I18nButton(e -> {
|
||||
if (versionBox.getValue() != null) {
|
||||
dataService.deleteReportsBeforeVersion(app, versionBox.getValue());
|
||||
}
|
||||
}, i18n, Messages.PURGE), new I18nLabel(i18n, Messages.REPORTS_BEFORE_VERSION), versionBox);
|
||||
purgeVersion.setExpandRatio(versionBox, 1);
|
||||
Button deleteButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(i18n, Messages.DELETE_APP_CONFIRM)).addYesNoButtons(popup -> {
|
||||
dataService.delete(app);
|
||||
navigationManager.navigateBack();
|
||||
}, true).show(), i18n, Messages.DELETE_APP);
|
||||
VerticalLayout layout = new VerticalLayout(configButton, matchingButton, purgeAge, purgeVersion, deleteButton);
|
||||
deleteButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
layout.setSizeFull();
|
||||
layout.addStyleName(AcraTheme.NO_PADDING);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.DANGER_ZONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "danger-zone";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource getIcon() {
|
||||
return VaadinIcons.EXCLAMATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStyleName() {
|
||||
return AcraTheme.RED_PANEL_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 2;
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs.panels;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nComboBox;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.rest.RestReportInterface;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.vaadin.server.Page;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Notification;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class ExportPanel implements AdminPanel {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Autowired
|
||||
public ExportPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
ComboBox<String> mailBox = new I18nComboBox<>(dataService.getFromReports(app, null, QReport.report.userEmail), i18n, Messages.BY_MAIL);
|
||||
mailBox.setEmptySelectionAllowed(true);
|
||||
mailBox.setSizeFull();
|
||||
ComboBox<String> idBox = new I18nComboBox<>(dataService.getFromReports(app, null, QReport.report.installationId), i18n, Messages.BY_ID);
|
||||
idBox.setEmptySelectionAllowed(true);
|
||||
idBox.setSizeFull();
|
||||
Button download = new I18nButton(e -> {
|
||||
if (idBox.getValue() == null && mailBox.getValue() == null) {
|
||||
Notification.show(i18n.get(Messages.NOTHING_SELECTED), Notification.Type.WARNING_MESSAGE);
|
||||
} else {
|
||||
Page page = UI.getCurrent().getPage();
|
||||
page.open(UriComponentsBuilder.fromUri(page.getLocation())
|
||||
.fragment(null)
|
||||
.pathSegment(RestReportInterface.EXPORT_PATH)
|
||||
.queryParam(RestReportInterface.PARAM_APP, app.getId())
|
||||
.queryParam(RestReportInterface.PARAM_ID, idBox.getValue())
|
||||
.queryParam(RestReportInterface.PARAM_MAIL, mailBox.getValue())
|
||||
.build()
|
||||
.toUriString(), null);
|
||||
}
|
||||
}, i18n, Messages.DOWNLOAD);
|
||||
download.setSizeFull();
|
||||
return new VerticalLayout(mailBox, idBox, download);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.EXPORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "export";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.app.tabs.panels;
|
||||
|
||||
import com.faendir.acra.i18n.I18nButton;
|
||||
import com.faendir.acra.i18n.I18nIntStepper;
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.model.ProguardMapping;
|
||||
import com.faendir.acra.model.QProguardMapping;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.InMemoryUpload;
|
||||
import com.faendir.acra.ui.view.base.layout.MyGrid;
|
||||
import com.faendir.acra.ui.view.base.popup.Popup;
|
||||
import com.faendir.acra.ui.view.base.popup.ValidatedField;
|
||||
import com.vaadin.icons.VaadinIcons;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.ProgressBar;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.renderers.ButtonRenderer;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.risto.stepper.IntStepper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class ProguardPanel implements AdminPanel{
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final I18N i18n;
|
||||
|
||||
@Autowired
|
||||
public ProguardPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
@Override
|
||||
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
|
||||
VerticalLayout layout = new VerticalLayout();
|
||||
MyGrid<ProguardMapping> grid = new MyGrid<>(dataService.getMappingProvider(app), i18n);
|
||||
grid.setSizeToRows();
|
||||
grid.sort(grid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, Messages.VERSION), SortDirection.ASCENDING);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
ButtonRenderer<ProguardMapping> renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM)
|
||||
.addComponent(new I18nLabel(i18n, Messages.DELETE_MAPPING_CONFIRM, e.getItem().getVersionCode()))
|
||||
.addYesNoButtons(p -> {
|
||||
dataService.delete(e.getItem());
|
||||
grid.getDataProvider().refreshAll();
|
||||
}, true)
|
||||
.show());
|
||||
renderer.setHtmlContentAllowed(true);
|
||||
grid.addColumn(report -> VaadinIcons.TRASH.getHtml(),
|
||||
renderer);
|
||||
}
|
||||
layout.addComponent(grid);
|
||||
layout.addStyleName(AcraTheme.NO_PADDING);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
layout.addComponent(new I18nButton(e -> {
|
||||
IntStepper version = new I18nIntStepper(dataService.getMaximumMappingVersion(app).map(i -> i + 1).orElse(1), i18n, Messages.VERSION_CODE);
|
||||
InMemoryUpload upload = new InMemoryUpload(i18n, Messages.MAPPING_FILE);
|
||||
ProgressBar progressBar = new ProgressBar();
|
||||
upload.addProgressListener((readBytes, contentLength) -> layout.getUI().access(() -> progressBar.setValue((float) readBytes / contentLength)));
|
||||
new Popup(i18n, Messages.NEW_MAPPING)
|
||||
.addComponent(version)
|
||||
.addValidatedField(ValidatedField.of(upload, () -> upload, consumer -> upload.addFinishedListener(event -> consumer.accept(upload)))
|
||||
.addValidator(InMemoryUpload::isUploaded, Messages.ERROR_UPLOAD))
|
||||
.addComponent(progressBar)
|
||||
.addCreateButton(popup -> {
|
||||
dataService.store(new ProguardMapping(app, version.getValue(), upload.getUploadedString()));
|
||||
grid.getDataProvider().refreshAll();
|
||||
}, true)
|
||||
.show();
|
||||
}, i18n, Messages.NEW_FILE));
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.DE_OBFUSCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "proguard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.rest.RestReportInterface;
|
||||
import com.faendir.acra.util.PlainTextUser;
|
||||
import com.faendir.acra.util.Utils;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 18.12.2017
|
||||
*/
|
||||
public class ConfigurationLabel extends I18nLabel {
|
||||
public ConfigurationLabel(PlainTextUser user, I18N i18n) {
|
||||
super(i18n, Messages.CONFIGURATION_LABEL, Utils.getUrlWithFragment(null), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword());
|
||||
setContentMode(ContentMode.HTML);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.faendir.acra.i18n.HasI18n;
|
||||
import com.vaadin.ui.Upload;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.12.2017
|
||||
*/
|
||||
public class InMemoryUpload extends Upload implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
private final ByteArrayOutputStream outputStream;
|
||||
private boolean finished;
|
||||
|
||||
public InMemoryUpload(I18N i18n, String captionId, Object... params) {
|
||||
super();
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
finished = false;
|
||||
setReceiver((filename, mimeType) -> outputStream);
|
||||
addSucceededListener(event -> finished = true);
|
||||
addFailedListener(event -> finished = false);
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
public boolean isUploaded() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public String getUploadedString() {
|
||||
return outputStream.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.faendir.acra.client.MiddleClickExtensionConnector;
|
||||
import com.vaadin.event.MouseEvents;
|
||||
import com.vaadin.server.AbstractExtension;
|
||||
import com.vaadin.ui.AbstractComponent;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 19.06.18
|
||||
*/
|
||||
public class MiddleClickExtension extends AbstractExtension {
|
||||
private MiddleClickExtension(AbstractComponent component) {
|
||||
super(component);
|
||||
registerRpc(details -> fireEvent(new MouseEvents.ClickEvent(component, details)), MiddleClickExtensionConnector.Rpc.class);
|
||||
}
|
||||
|
||||
public static void extend(AbstractComponent component, MouseEvents.ClickListener listener) {
|
||||
new MiddleClickExtension(component).addListener(MouseEvents.ClickEvent.class, listener, MouseEvents.ClickListener.clickMethod);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 31.05.2017
|
||||
*/
|
||||
public class MyCheckBox extends CheckBox {
|
||||
public MyCheckBox(boolean value, boolean enabled, @NonNull ValueChangeListener<Boolean> changeListener) {
|
||||
setValue(value);
|
||||
setEnabled(enabled);
|
||||
addValueChangeListener(changeListener);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.faendir.acra.dataprovider.QueryDslDataProvider;
|
||||
import com.faendir.acra.i18n.I18nLabel;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.model.QReport;
|
||||
import com.faendir.acra.model.Report;
|
||||
import com.faendir.acra.security.SecurityUtils;
|
||||
import com.faendir.acra.service.AvatarService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.layout.MyGrid;
|
||||
import com.faendir.acra.ui.view.base.popup.Popup;
|
||||
import com.faendir.acra.ui.view.report.ReportView;
|
||||
import com.faendir.acra.util.TimeSpanRenderer;
|
||||
import com.vaadin.icons.VaadinIcons;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.renderers.ButtonRenderer;
|
||||
import com.vaadin.ui.renderers.ImageRenderer;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 14.05.2017
|
||||
*/
|
||||
public class ReportList extends MyGrid<Report> {
|
||||
|
||||
public ReportList(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull AvatarService avatarService, @NonNull Consumer<Report> reportDeleter, @NonNull QueryDslDataProvider<Report> reportProvider, I18N i18n) {
|
||||
super(reportProvider, i18n, Messages.REPORTS);
|
||||
setSelectionMode(Grid.SelectionMode.NONE);
|
||||
addColumn(avatarService::getAvatar, new ImageRenderer<>(), QReport.report.installationId, Messages.USER);
|
||||
sort(addColumn(Report::getDate, new TimeSpanRenderer(), QReport.report.date, Messages.DATE), SortDirection.DESCENDING);
|
||||
addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, Messages.APP_VERSION);
|
||||
addColumn(Report::getAndroidVersion, QReport.report.androidVersion, Messages.ANDROID_VERSION);
|
||||
addColumn(Report::getPhoneModel, QReport.report.phoneModel, Messages.DEVICE);
|
||||
addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, Messages.STACKTRACE).setExpandRatio(1)
|
||||
.setMinimumWidthFromContent(false);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
ButtonRenderer<Report> renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM)
|
||||
.addComponent(new I18nLabel(i18n, Messages.DELETE_REPORT_CONFIRM))
|
||||
.addYesNoButtons(p -> {
|
||||
reportDeleter.accept(e.getItem());
|
||||
getDataProvider().refreshAll();
|
||||
}, true)
|
||||
.show());
|
||||
renderer.setHtmlContentAllowed(true);
|
||||
addColumn(report -> VaadinIcons.TRASH.getHtml(),
|
||||
renderer).setSortable(false);
|
||||
}
|
||||
addOnClickNavigation(navigationManager, ReportView.class, e -> e.getItem().getId());
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.vaadin.ui.Component;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
public interface ComponentFactory<T> extends Ordered {
|
||||
Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager);
|
||||
|
||||
String getCaption();
|
||||
|
||||
String getId();
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.CssLayout;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 28.05.18
|
||||
*/
|
||||
public class FlexLayout extends CssLayout {
|
||||
public FlexLayout(Component... children) {
|
||||
super(children);
|
||||
setResponsive(true);
|
||||
addStyleName(AcraTheme.FLEX_LAYOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addComponent(Component c) {
|
||||
super.addComponent(c);
|
||||
c.addStyleName(AcraTheme.FLEX_ITEM);
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.faendir.acra.client.mygrid.GridMiddleClickExtensionConnector;
|
||||
import com.faendir.acra.dataprovider.QueryDslDataProvider;
|
||||
import com.faendir.acra.i18n.HasI18n;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.navigation.BaseView;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.vaadin.data.ValueProvider;
|
||||
import com.vaadin.data.provider.DataProvider;
|
||||
import com.vaadin.shared.MouseEventDetails;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.ui.Composite;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.renderers.AbstractRenderer;
|
||||
import com.vaadin.ui.renderers.TextRenderer;
|
||||
import elemental.json.JsonObject;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EventObject;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 14.05.2017
|
||||
*/
|
||||
public class MyGrid<T> extends Composite implements Translatable, HasI18n {
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
private final Map<Grid.Column<T, ?>, Pair<String, Object[]>> columnCaptions;
|
||||
private final List<Translatable> translatables;
|
||||
private final ExposingGrid<T> grid;
|
||||
private final QueryDslDataProvider<T> dataProvider;
|
||||
|
||||
public MyGrid(QueryDslDataProvider<T> dataProvider, I18N i18n) {
|
||||
this(dataProvider, i18n, Messages.BLANK);
|
||||
}
|
||||
|
||||
public MyGrid(QueryDslDataProvider<T> dataProvider, I18N i18n, String captionId, Object... params) {
|
||||
this.dataProvider = dataProvider;
|
||||
grid = new ExposingGrid<>(dataProvider);
|
||||
setCompositionRoot(grid);
|
||||
setSizeFull();
|
||||
MiddleClickExtension.extend(this);
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
columnCaptions = new HashMap<>();
|
||||
translatables = new ArrayList<>();
|
||||
this.params = params;
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull String captionId, Object... params) {
|
||||
Grid.Column<T, R> column = addColumn(valueProvider, new TextRenderer(), i18n.get(captionId, params));
|
||||
columnCaptions.put(column, Pair.of(captionId, params));
|
||||
return column;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer, @NonNull String captionId,
|
||||
Object... params) {
|
||||
Grid.Column<T, R> column = addColumn(valueProvider, renderer).setCaption(i18n.get(captionId, params));
|
||||
columnCaptions.put(column, Pair.of(captionId, params));
|
||||
return column;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId,
|
||||
Object... params) {
|
||||
return addColumn(valueProvider, new TextRenderer(), sort, captionId, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer,
|
||||
@NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
|
||||
Grid.Column<T, R> column = addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(i18n.get(captionId, params)).setSortable(true);
|
||||
columnCaptions.put(column, Pair.of(captionId, params));
|
||||
return column;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer) {
|
||||
if(renderer instanceof Translatable) {
|
||||
((Translatable) renderer).updateMessageStrings(i18n.getLocale());
|
||||
translatables.add((Translatable) renderer);
|
||||
}
|
||||
return grid.addColumn(valueProvider, renderer).setSortable(false);
|
||||
}
|
||||
|
||||
public void setItems(@NonNull Collection<T> items) {
|
||||
grid.setItems(items);
|
||||
setHeightByRows(items.size());
|
||||
}
|
||||
|
||||
public void setHeightByRows(double rows) {
|
||||
grid.setHeightByRows(rows >= 1 ? rows : 1);
|
||||
}
|
||||
|
||||
public void setSizeToRows() {
|
||||
dataProvider.addSizeListener(rows -> getUI().access(() -> setHeightByRows(rows)));
|
||||
}
|
||||
|
||||
public void setBodyRowHeight(double rowHeight) {
|
||||
grid.setBodyRowHeight(rowHeight);
|
||||
}
|
||||
|
||||
public void setSelectionMode(Grid.SelectionMode selectionMode) {
|
||||
grid.setSelectionMode(selectionMode);
|
||||
}
|
||||
|
||||
public void sort(Grid.Column<T, ?> column, SortDirection direction) {
|
||||
grid.sort(column, direction);
|
||||
}
|
||||
|
||||
public Set<T> getSelectedItems() {
|
||||
return grid.getSelectedItems();
|
||||
}
|
||||
|
||||
public void select(T item) {
|
||||
grid.select(item);
|
||||
}
|
||||
|
||||
public void deselectAll() {
|
||||
grid.deselectAll();
|
||||
}
|
||||
|
||||
public void addOnClickNavigation(@NonNull NavigationManager navigationManager, Class<? extends BaseView> namedView, Function<Grid.ItemClick<T>, String> parameterGetter) {
|
||||
grid.addItemClickListener(e -> {
|
||||
boolean newTab = e.getMouseEventDetails().getButton() == MouseEventDetails.MouseButton.MIDDLE || e.getMouseEventDetails().isCtrlKey();
|
||||
navigationManager.navigateTo(namedView, parameterGetter.apply(e), newTab);
|
||||
});
|
||||
}
|
||||
|
||||
public QueryDslDataProvider<T> getDataProvider() {
|
||||
return dataProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
columnCaptions.forEach((column, caption) -> column.setCaption(i18n.get(caption.getFirst(), locale, caption.getSecond())));
|
||||
translatables.forEach(translatable -> translatable.updateMessageStrings(locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
|
||||
public static class MiddleClickExtension<T> extends Grid.AbstractGridExtension<T> {
|
||||
private MiddleClickExtension(MyGrid<T> myGrid) {
|
||||
ExposingGrid<T> grid = myGrid.grid;
|
||||
super.extend(grid);
|
||||
registerRpc((rowIndex, rowKey, columnInternalId, details) -> grid.fireEvent(new Grid.ItemClick<>(grid,
|
||||
grid.getColumnByInternalId(columnInternalId),
|
||||
grid.getDataCommunicator().getKeyMapper().get(rowKey),
|
||||
details,
|
||||
rowIndex)), GridMiddleClickExtensionConnector.Rpc.class);
|
||||
}
|
||||
|
||||
public static void extend(MyGrid<?> grid) {
|
||||
new MiddleClickExtension<>(grid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateData(Object item, JsonObject jsonObject) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyData(Object item) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyAllData() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshData(Object item) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExposingGrid<T> extends Grid<T> {
|
||||
ExposingGrid(DataProvider<T, ?> dataProvider) {
|
||||
super(dataProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Column<T, ?> getColumnByInternalId(String columnId) {
|
||||
return super.getColumnByInternalId(columnId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireEvent(EventObject event) {
|
||||
super.fireEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.faendir.acra.client.mytabsheet.TabSheetMiddleClickExtensionConnector;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.vaadin.event.ConnectorEventListener;
|
||||
import com.vaadin.event.MouseEvents;
|
||||
import com.vaadin.server.AbstractExtension;
|
||||
import com.vaadin.shared.MouseEventDetails;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.CustomComponent;
|
||||
import com.vaadin.ui.TabSheet;
|
||||
import com.vaadin.util.ReflectTools;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 12.12.2017
|
||||
*/
|
||||
public class MyTabSheet<T> extends TabSheet {
|
||||
private final T t;
|
||||
private final NavigationManager navigationManager;
|
||||
|
||||
public MyTabSheet(@NonNull T t, @NonNull NavigationManager navigationManager, Collection<? extends ComponentFactory<T>> tabs) {
|
||||
this.t = t;
|
||||
this.navigationManager = navigationManager;
|
||||
for (ComponentFactory<T> tab : tabs) {
|
||||
addTab(tab);
|
||||
}
|
||||
MiddleClickExtension.extend(this);
|
||||
}
|
||||
|
||||
public void addTab(ComponentFactory<T> tab) {
|
||||
TabWrapper<T> wrapper = new TabWrapper<>(t, navigationManager, tab);
|
||||
addComponent(wrapper);
|
||||
addSelectedTabChangeListener(wrapper);
|
||||
}
|
||||
|
||||
public void guessInitialTab(String id) {
|
||||
Component component = StreamSupport.stream(spliterator(), false).filter(c -> c.getId().equals(id)).findAny().orElseGet(() -> getTab(0).getComponent());
|
||||
if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) {
|
||||
((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true));
|
||||
}
|
||||
setSelectedTab(component);
|
||||
}
|
||||
|
||||
public void addMiddleClickListener(ClickListener listener) {
|
||||
addListener(ClickEvent.class, listener, ClickListener.clickMethod);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ClickListener extends ConnectorEventListener {
|
||||
Method clickMethod = ReflectTools.findMethod(ClickListener.class, "click", ClickEvent.class);
|
||||
|
||||
void click(ClickEvent event);
|
||||
}
|
||||
|
||||
private static class TabWrapper<T> extends CustomComponent implements SelectedTabChangeListener {
|
||||
private final T t;
|
||||
private final NavigationManager navigationManager;
|
||||
private final ComponentFactory<T> tab;
|
||||
|
||||
private TabWrapper(@NonNull T t, @NonNull NavigationManager navigationManager, @NonNull ComponentFactory<T> tab) {
|
||||
this.t = t;
|
||||
this.navigationManager = navigationManager;
|
||||
this.tab = tab;
|
||||
setSizeFull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectedTabChange(@NonNull SelectedTabChangeEvent event) {
|
||||
if (this == event.getTabSheet().getSelectedTab()) {
|
||||
setCompositionRoot(tab.createContent(t, navigationManager));
|
||||
} else {
|
||||
setCompositionRoot(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return tab.getCaption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return tab.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MiddleClickExtension extends AbstractExtension {
|
||||
private MiddleClickExtension(MyTabSheet component) {
|
||||
super(component);
|
||||
registerRpc((tabIndex, details) -> component.fireEvent(new ClickEvent(component, ((TabWrapper) component.getTab(tabIndex).getComponent()).tab, details)),
|
||||
TabSheetMiddleClickExtensionConnector.Rpc.class);
|
||||
}
|
||||
|
||||
public static void extend(MyTabSheet component) {
|
||||
new MiddleClickExtension(component);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClickEvent extends MouseEvents.ClickEvent {
|
||||
private final ComponentFactory tab;
|
||||
|
||||
public ClickEvent(MyTabSheet source, ComponentFactory tab, MouseEventDetails mouseEventDetails) {
|
||||
super(source, mouseEventDetails);
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
public ComponentFactory getTab() {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.vaadin.server.Resource;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
public interface PanelContentFactory<T> extends ComponentFactory<T> {
|
||||
default Resource getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getStyleName() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.layout;
|
||||
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.vaadin.server.Sizeable;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 17.06.18
|
||||
*/
|
||||
public abstract class PanelFlexTab<T> implements ComponentFactory<T> {
|
||||
@NonNull private final List<? extends PanelContentFactory<T>> factories;
|
||||
|
||||
public PanelFlexTab(@NonNull List<? extends PanelContentFactory<T>> factories) {
|
||||
this.factories = factories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager) {
|
||||
FlexLayout layout = new FlexLayout(factories.stream().map(factory -> {
|
||||
Panel panel = new Panel(factory.createContent(t, navigationManager));
|
||||
panel.setCaption(factory.getCaption());
|
||||
panel.setIcon(factory.getIcon());
|
||||
panel.addStyleName(AcraTheme.NO_BACKGROUND);
|
||||
if (factory.getStyleName() != null) {
|
||||
panel.addStyleName(factory.getStyleName());
|
||||
}
|
||||
return panel;
|
||||
}).toArray(Component[]::new));
|
||||
layout.setWidth(100, Sizeable.Unit.PERCENTAGE);
|
||||
Panel root = new Panel(layout);
|
||||
root.setSizeFull();
|
||||
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
|
||||
return root;
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.navigation;
|
||||
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.vaadin.navigator.View;
|
||||
import com.vaadin.ui.Composite;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* Non-abstract subclasses must be annotated with {@link com.vaadin.spring.annotation.SpringView}
|
||||
*
|
||||
* @author Lukas
|
||||
* @since 14.05.2017
|
||||
*/
|
||||
public abstract class BaseView extends Composite implements View {
|
||||
private NavigationManager navigationManager;
|
||||
|
||||
protected NavigationManager getNavigationManager() {
|
||||
return navigationManager;
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
public void setNavigationManager(NavigationManager navigationManager) {
|
||||
this.navigationManager = navigationManager;
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.navigation;
|
||||
|
||||
import com.vaadin.navigator.ViewChangeListener;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 12.12.2017
|
||||
*/
|
||||
public abstract class ParametrizedBaseView<T> extends BaseView {
|
||||
private Function<ViewChangeListener.ViewChangeEvent, T> parameterParser;
|
||||
|
||||
@Override
|
||||
public final void enter(ViewChangeListener.ViewChangeEvent event) {
|
||||
enter(parameterParser.apply(event));
|
||||
}
|
||||
|
||||
protected abstract void enter(@NonNull T parameter);
|
||||
|
||||
public void setParameterParser(Function<ViewChangeListener.ViewChangeEvent, T> parameterParser) {
|
||||
this.parameterParser = parameterParser;
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.base.navigation;
|
||||
|
||||
import com.faendir.acra.ui.navigation.MyNavigator;
|
||||
import com.faendir.acra.ui.navigation.SingleViewProvider;
|
||||
import com.faendir.acra.ui.view.base.MiddleClickExtension;
|
||||
import com.faendir.acra.util.Utils;
|
||||
import com.vaadin.icons.VaadinIcons;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.Composite;
|
||||
import com.vaadin.ui.CssLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.vaadin.ui.themes.ValoTheme;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 24.03.2018
|
||||
*/
|
||||
public class Path extends Composite {
|
||||
private final Deque<Element> elements;
|
||||
private final CssLayout layout;
|
||||
|
||||
public Path() {
|
||||
elements = new ArrayDeque<>();
|
||||
layout = new CssLayout();
|
||||
layout.addStyleName(AcraTheme.BASIC_FLEX);
|
||||
setCompositionRoot(layout);
|
||||
}
|
||||
|
||||
public void set(Deque<MyNavigator.HierarchyElement> hierarchy) {
|
||||
elements.clear();
|
||||
layout.removeAllComponents();
|
||||
hierarchy.forEach(e -> {
|
||||
SingleViewProvider provider = (SingleViewProvider) e.getProvider();
|
||||
goTo(provider.getTitle(provider.getParameters(e.getNavState())), e.getNavState());
|
||||
});
|
||||
}
|
||||
|
||||
public void goTo(String label, String id) {
|
||||
goTo(new Element(label, id));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Button button = new Button(element.getLabel());
|
||||
button.addClickListener(e -> {
|
||||
while (getLast() != element) goUp();
|
||||
getUI().getNavigator().navigateTo(asUrlFragment());
|
||||
});
|
||||
MiddleClickExtension.extend(button, e -> {
|
||||
while (getLast() != element) goUp();
|
||||
getUI().getPage().open(Utils.getUrlWithFragment(asUrlFragment()), "_blank", false);
|
||||
});
|
||||
button.addStyleNames(ValoTheme.BUTTON_BORDERLESS, AcraTheme.PATH_ELEMENT);
|
||||
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();
|
||||
}
|
||||
|
||||
public String asUrlFragment() {
|
||||
return elements.stream().map(Element::getId).collect(Collectors.joining(MyNavigator.SEPARATOR));
|
||||
}
|
||||
|
||||
private void removeLastComponent() {
|
||||
layout.removeComponent(layout.getComponent(layout.getComponentCount() - 1));
|
||||
}
|
||||
|
||||
public static class Element {
|
||||
private final String label;
|
||||
private final String id;
|
||||
|
||||
public Element(String label, String id) {
|
||||
this.label = label;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.base.statistics;
|
||||
|
||||
import com.faendir.acra.i18n.HasI18n;
|
||||
import com.vaadin.ui.Composite;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.vaadin.addon.JFreeChartWrapper;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
import org.vaadin.spring.i18n.support.Translatable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 01.06.18
|
||||
*/
|
||||
abstract class Chart<T> extends Composite implements Translatable, HasI18n {
|
||||
private final Panel panel;
|
||||
private final I18N i18n;
|
||||
private final String captionId;
|
||||
private final Object[] params;
|
||||
private JFreeChart chart;
|
||||
|
||||
Chart(I18N i18n, String captionId, Object... params) {
|
||||
this.i18n = i18n;
|
||||
this.captionId = captionId;
|
||||
this.params = params;
|
||||
panel = new Panel();
|
||||
panel.addStyleName(AcraTheme.NO_BACKGROUND);
|
||||
setCompositionRoot(panel);
|
||||
updateMessageStrings(i18n.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessageStrings(Locale locale) {
|
||||
setCaption(i18n.get(captionId, locale, params));
|
||||
}
|
||||
|
||||
public void setContent(@NonNull Map<T, Long> map) {
|
||||
chart = createChart(map);
|
||||
JFreeChartWrapper content = new JFreeChartWrapper(chart);
|
||||
content.setWidth(100, Unit.PERCENTAGE);
|
||||
content.setHeight(100, Unit.PERCENTAGE);
|
||||
panel.setContent(content);
|
||||
}
|
||||
|
||||
Paint getForegroundColor() {
|
||||
return UI.getCurrent().getTheme().toLowerCase().contains("dark") ? Statistics.FOREGROUND_DARK : Statistics.FOREGROUND_LIGHT;
|
||||
}
|
||||
|
||||
protected abstract JFreeChart createChart(@NonNull Map<T, Long> map);
|
||||
|
||||
@Nullable
|
||||
JFreeChart getChart() {
|
||||
return chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18N getI18n() {
|
||||
return i18n;
|
||||
}
|
||||
}
|
|
@ -1,113 +1,68 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.bug;
|
||||
|
||||
import com.faendir.acra.model.App;
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.Bug;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.annotation.RequiresAppPermission;
|
||||
import com.faendir.acra.ui.navigation.MyNavigator;
|
||||
import com.faendir.acra.ui.navigation.SingleParametrizedViewProvider;
|
||||
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
|
||||
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
|
||||
import com.faendir.acra.ui.base.ActiveChildAware;
|
||||
import com.faendir.acra.ui.base.ParentLayout;
|
||||
import com.faendir.acra.ui.component.Tab;
|
||||
import com.faendir.acra.ui.view.MainView;
|
||||
import com.faendir.acra.ui.view.bug.tabs.AdminTab;
|
||||
import com.faendir.acra.ui.view.bug.tabs.BugTab;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.themes.AcraTheme;
|
||||
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
|
||||
import com.faendir.acra.ui.view.bug.tabs.StacktraceTab;
|
||||
import com.faendir.acra.ui.view.bug.tabs.StatisticsTab;
|
||||
import com.vaadin.flow.component.tabs.Tabs;
|
||||
import com.vaadin.flow.router.RoutePrefix;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 21.03.2018
|
||||
* @author lukas
|
||||
* @since 08.09.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
@RequiresAppPermission(Permission.Level.VIEW)
|
||||
public class BugView extends ParametrizedBaseView<Pair<Bug, String>> {
|
||||
private final List<BugTab> tabs;
|
||||
@RoutePrefix("bug")
|
||||
@com.vaadin.flow.router.ParentLayout(MainView.class)
|
||||
public class BugView extends ParentLayout implements ActiveChildAware<BugTab<?>, Bug> {
|
||||
private Bug bug;
|
||||
private final Tabs tabs;
|
||||
|
||||
@Autowired
|
||||
public BugView(@NonNull @Lazy List<BugTab> tabs) {
|
||||
this.tabs = tabs;
|
||||
public BugView() {
|
||||
tabs = new Tabs(Stream.of(TabDef.values()).map(def -> new Tab(def.labelId)).toArray(Tab[]::new));
|
||||
tabs.addSelectedChangeListener(e -> getUI().ifPresent(ui -> ui.navigate(TabDef.values()[e.getSource().getSelectedIndex()].tabClass, bug.getId())));
|
||||
setSizeFull();
|
||||
ParentLayout content = new ParentLayout();
|
||||
content.setWidthFull();
|
||||
expand(content);
|
||||
content.getStyle().set("overflow", "auto");
|
||||
setRouterRoot(content);
|
||||
getStyle().set("flex-direction", "column");
|
||||
removeAll();
|
||||
add(tabs, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enter(@NonNull Pair<Bug, String> parameter) {
|
||||
Bug bug = parameter.getFirst();
|
||||
MyTabSheet<Bug> tabSheet = new MyTabSheet<>(bug, getNavigationManager(), tabs);
|
||||
tabSheet.setSizeFull();
|
||||
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(bug.getId() + "/" + e.getTabSheet().getSelectedTab().getCaption()));
|
||||
tabSheet.guessInitialTab(parameter.getSecond());
|
||||
Panel root = new Panel(tabSheet);
|
||||
root.setSizeFull();
|
||||
root.addStyleNames( AcraTheme.NO_BORDER, AcraTheme.NO_BACKGROUND, AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
|
||||
setCompositionRoot(root);
|
||||
public void setActiveChild(BugTab<?> child, Bug parameter) {
|
||||
bug = parameter;
|
||||
Stream.of(TabDef.values()).filter(def -> def.tabClass.equals(child.getClass())).findAny().ifPresent(def -> tabs.setSelectedIndex(def.ordinal()));
|
||||
}
|
||||
|
||||
@SpringComponent
|
||||
@UIScope
|
||||
public static class Provider extends SingleParametrizedViewProvider<Pair<Bug, String>, BugView> {
|
||||
@NonNull private final DataService dataService;
|
||||
private enum TabDef {
|
||||
REPORT(Messages.REPORTS, ReportTab.class),
|
||||
STACKTRACE(Messages.STACKTRACES, StacktraceTab.class),
|
||||
STATISTICS(Messages.STATISTICS, StatisticsTab.class),
|
||||
ADMIN(Messages.ADMIN, AdminTab.class);
|
||||
private String labelId;
|
||||
private Class<? extends BugTab<?>> tabClass;
|
||||
|
||||
@Autowired
|
||||
public Provider(@NonNull DataService dataService) {
|
||||
super(BugView.class);
|
||||
this.dataService = dataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle(Pair<Bug, String> parameter) {
|
||||
return parameter.getFirst().getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidParameter(Pair<Bug, String> parameter) {
|
||||
return parameter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pair<Bug, String> parseParameter(String parameter) {
|
||||
String[] parameters = parameter.split(MyNavigator.SEPARATOR);
|
||||
if (parameters.length > 0) {
|
||||
Optional<Bug> bug = dataService.findBug(parameters[0]);
|
||||
if (bug.isPresent()) {
|
||||
return Pair.of(bug.get(), parameters.length == 1 ? "" : parameters[1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected App toApp(Pair<Bug, String> parameter) {
|
||||
return parameter.getFirst().getApp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "bug";
|
||||
TabDef(String labelId, Class<? extends BugTab<?>> tabClass) {
|
||||
this.labelId = labelId;
|
||||
this.tabClass = tabClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,69 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.faendir.acra.ui.view.bug.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.Bug;
|
||||
import com.faendir.acra.model.Permission;
|
||||
import com.faendir.acra.ui.annotation.RequiresAppPermission;
|
||||
import com.faendir.acra.ui.view.base.layout.PanelFlexTab;
|
||||
import com.faendir.acra.ui.view.bug.tabs.panels.AdminPanel;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.base.popup.Popup;
|
||||
import com.faendir.acra.ui.component.Card;
|
||||
import com.faendir.acra.ui.component.FlexLayout;
|
||||
import com.faendir.acra.ui.component.HasSize;
|
||||
import com.faendir.acra.ui.component.Translatable;
|
||||
import com.faendir.acra.ui.view.Overview;
|
||||
import com.faendir.acra.ui.view.bug.BugView;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.textfield.TextArea;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lukas
|
||||
* @since 04.06.18
|
||||
* @since 18.10.18
|
||||
*/
|
||||
@RequiresAppPermission(Permission.Level.ADMIN)
|
||||
@UIScope
|
||||
@SpringComponent("bugAdminTab")
|
||||
@ViewScope
|
||||
public class AdminTab extends PanelFlexTab<Bug> implements BugTab {
|
||||
private final I18N i18n;
|
||||
|
||||
@Route(value = "admin", layout = BugView.class)
|
||||
public class AdminTab extends BugTab<FlexLayout> {
|
||||
@Autowired
|
||||
public AdminTab(@NonNull List<AdminPanel> panels, I18N i18n) {
|
||||
super(panels);
|
||||
this.i18n = i18n;
|
||||
public AdminTab(DataService dataService) {
|
||||
super(dataService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.ADMIN);
|
||||
}
|
||||
void init(Bug bug) {
|
||||
getContent().removeAll();
|
||||
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
|
||||
getContent().setWidthFull();
|
||||
Translatable<TextArea> title = Translatable.createTextArea(bug.getTitle(), Messages.TITLE);
|
||||
title.setWidthFull();
|
||||
Translatable<Button> save = Translatable.createButton(e -> {
|
||||
bug.setTitle(title.getContent().getValue());
|
||||
getDataService().store(bug);
|
||||
}, Messages.SAVE);
|
||||
save.setWidthFull();
|
||||
Card propertiesCard = new Card(title, save);
|
||||
propertiesCard.setHeader(Translatable.createText(Messages.PROPERTIES));
|
||||
propertiesCard.setWidth(500, HasSize.Unit.PIXEL);
|
||||
getContent().add(propertiesCard);
|
||||
getContent().expand(propertiesCard);
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "admin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 3;
|
||||
Translatable<Button> unmergeButton = Translatable.createButton(e -> new Popup().setTitle(Messages.UNMERGE_BUG_CONFIRM).addYesNoButtons(p -> {
|
||||
getDataService().unmergeBug(bug);
|
||||
UI.getCurrent().navigate(Overview.class);
|
||||
}, true), Messages.UNMERGE_BUG);
|
||||
unmergeButton.setWidthFull();
|
||||
Translatable<Button> deleteButton = Translatable.createButton(e -> new Popup().setTitle(Messages.DELETE_BUG_CONFIRM).addYesNoButtons(popup -> {
|
||||
getDataService().delete(bug);
|
||||
UI.getCurrent().navigate(Overview.class);
|
||||
}, true).show(), Messages.DELETE_BUG);
|
||||
deleteButton.setWidthFull();
|
||||
Card dangerCard = new Card(unmergeButton, deleteButton);
|
||||
dangerCard.setHeader(Translatable.createText(Messages.DANGER_ZONE));
|
||||
dangerCard.setWidth(500, HasSize.Unit.PIXEL);
|
||||
getContent().add(dangerCard);
|
||||
getContent().expand(dangerCard);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,72 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.bug.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.Bug;
|
||||
import com.faendir.acra.ui.view.base.layout.ComponentFactory;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.base.ActiveChildAware;
|
||||
import com.faendir.acra.ui.base.HasRoute;
|
||||
import com.faendir.acra.ui.base.HasSecureIntParameter;
|
||||
import com.faendir.acra.ui.base.Path;
|
||||
import com.vaadin.flow.component.AttachEvent;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 27.03.2018
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
public interface BugTab extends ComponentFactory<Bug>, Serializable {
|
||||
public abstract class BugTab <T extends Component> extends Composite<T> implements HasSecureIntParameter, HasRoute {
|
||||
private final DataService dataService;
|
||||
private Bug bug;
|
||||
|
||||
public BugTab(DataService dataService) {
|
||||
this.dataService = dataService;
|
||||
}
|
||||
|
||||
public DataService getDataService() {
|
||||
return dataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameterSecure(BeforeEvent event, Integer parameter) {
|
||||
Optional<Bug> bug = dataService.findBug(parameter);
|
||||
if (bug.isPresent()) {
|
||||
this.bug = bug.get();
|
||||
} else {
|
||||
event.rerouteToError(IllegalArgumentException.class);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void init(Bug bug);
|
||||
|
||||
@Override
|
||||
protected void onAttach(AttachEvent attachEvent) {
|
||||
super.onAttach(attachEvent);
|
||||
init(this.bug);
|
||||
Optional<Component> parent = getParent();
|
||||
while (parent.isPresent()) {
|
||||
if (parent.get() instanceof ActiveChildAware) {
|
||||
//noinspection unchecked
|
||||
((ActiveChildAware<BugTab<?>, Bug>) parent.get()).setActiveChild(this, bug);
|
||||
break;
|
||||
}
|
||||
parent = parent.get().getParent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path.Element<?> getPathElement() {
|
||||
//noinspection unchecked
|
||||
return new Path.ParametrizedTextElement<>((Class<? extends BugTab<?>>) getClass(), bug.getId(), Messages.ONE_ARG, bug.getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent<?> getLogicalParent() {
|
||||
return new ParametrizedParent<>(com.faendir.acra.ui.view.app.tabs.BugTab.class, bug.getApp().getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,38 @@
|
|||
/*
|
||||
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.faendir.acra.ui.view.bug.tabs;
|
||||
|
||||
import com.faendir.acra.i18n.Messages;
|
||||
import com.faendir.acra.model.Bug;
|
||||
import com.faendir.acra.service.AvatarService;
|
||||
import com.faendir.acra.service.DataService;
|
||||
import com.faendir.acra.ui.navigation.NavigationManager;
|
||||
import com.faendir.acra.ui.view.base.ReportList;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.faendir.acra.ui.base.ReportList;
|
||||
import com.faendir.acra.ui.view.bug.BugView;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import com.vaadin.flow.spring.annotation.UIScope;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.vaadin.spring.i18n.I18N;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 21.03.2018
|
||||
* @author lukas
|
||||
* @since 19.11.18
|
||||
*/
|
||||
@UIScope
|
||||
@SpringComponent("bugReportTab")
|
||||
@ViewScope
|
||||
public class ReportTab implements BugTab {
|
||||
@NonNull private final DataService dataService;
|
||||
@NonNull private final AvatarService avatarService;
|
||||
@NonNull private final I18N i18n;
|
||||
@Route(value = "report", layout = BugView.class)
|
||||
public class ReportTab extends BugTab<Div> {
|
||||
@NonNull
|
||||
private final AvatarService avatarService;
|
||||
|
||||
@Autowired
|
||||
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
|
||||
this.dataService = dataService;
|
||||
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
|
||||
super(dataService);
|
||||
this.avatarService = avatarService;
|
||||
this.i18n = i18n;
|
||||
getContent().setSizeFull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
|
||||
Component content = new ReportList(bug.getApp(), navigationManager, avatarService, dataService::delete, dataService.getReportProvider(bug), i18n);
|
||||
content.setSizeFull();
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaption() {
|
||||
return i18n.get(Messages.REPORTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "report";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
void init(Bug bug) {
|
||||
getContent().removeAll();
|
||||
getContent().add(new ReportList(bug.getApp(), getDataService().getReportProvider(bug), avatarService, getDataService()::delete));
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue