Update Vaadin and Spring Boot version
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -1,9 +1,3 @@
|
|||||||
#
|
|
||||||
# https://help.github.com/articles/dealing-with-line-endings/
|
|
||||||
#
|
|
||||||
# Linux start script should use lf
|
|
||||||
/gradlew text eol=lf
|
/gradlew text eol=lf
|
||||||
|
|
||||||
# These are Windows script files and should use crlf
|
|
||||||
*.bat text eol=crlf
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
|
|||||||
+34
-29
@@ -1,33 +1,38 @@
|
|||||||
.gradle/
|
node_modules
|
||||||
.settings/
|
HELP.md
|
||||||
|
.gradle
|
||||||
build/
|
build/
|
||||||
bin/
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
### STS ###
|
||||||
gradle-app.setting
|
.apt_generated
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
|
||||||
!gradle-wrapper.jar
|
|
||||||
|
|
||||||
.project
|
|
||||||
.classpath
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
|
||||||
*.lock
|
|
||||||
logs/
|
|
||||||
frontend/generated
|
|
||||||
frontend/index.html
|
|
||||||
package*.json
|
|
||||||
tsconfig.json
|
|
||||||
types.d.ts
|
|
||||||
node_modules/
|
|
||||||
vite.*
|
|
||||||
kontor*Db
|
|
||||||
tags*
|
|
||||||
kontorHSQLDB*
|
|
||||||
.vs/
|
|
||||||
.winget
|
|
||||||
src/main/resources/application-local.properties
|
|
||||||
src/main/resources/application-prod.properties
|
|
||||||
src/main/resources/application-*.yml
|
|
||||||
/uploaded-files/
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ----------------------------------------------------------------------- #
|
# ----------------------------------------------------------------------- #
|
||||||
FROM docker.io/library/gradle:8.7-jdk AS builder
|
FROM docker.io/library/gradle:9.5-jdk AS builder
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY ./src/main/ ./src/main/
|
COPY ./src/main/ ./src/main/
|
||||||
COPY ./frontend/ ./frontend/
|
COPY ./frontend/ ./frontend/
|
||||||
@@ -10,7 +10,7 @@ COPY ./gradle/libs.versions.toml ./gradle/
|
|||||||
RUN gradle bootJar --no-daemon
|
RUN gradle bootJar --no-daemon
|
||||||
|
|
||||||
# ----------------------------------------------------------------------- #
|
# ----------------------------------------------------------------------- #
|
||||||
FROM docker.io/alpine/java:21-jdk AS run
|
FROM docker.io/alpine/java:25-jdk AS run
|
||||||
|
|
||||||
RUN mkdir -p /logs
|
RUN mkdir -p /logs
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
.PYHONY: all
|
|
||||||
|
|
||||||
all:
|
|
||||||
./gradlew build
|
|
||||||
|
|
||||||
docker:
|
|
||||||
./gradlew dockerImage
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# kontor-spring
|
|
||||||
|
|
||||||
Kontor Anwendung mit Spring Boot und Vaadin
|
|
||||||
+29
-169
@@ -1,97 +1,54 @@
|
|||||||
buildscript {
|
|
||||||
configurations.classpath {
|
|
||||||
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
|
|
||||||
if (details.requested.group == 'com.burgstaller' && details.requested.name == 'okhttp-digest' && details.requested.version == '1.10') {
|
|
||||||
details.useTarget "io.github.rburgst:${details.requested.name}:1.21"
|
|
||||||
details.because 'Dependency has moved'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven { setUrl("https://nexus.thpeetz.de/repository/maven-central") }
|
|
||||||
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
|
|
||||||
maven { setUrl("https://repo.spring.io/milestone") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.0"
|
|
||||||
id 'jvm-test-suite'
|
|
||||||
id 'jacoco'
|
|
||||||
id 'test-report-aggregation'
|
|
||||||
id 'jacoco-report-aggregation'
|
|
||||||
alias(libs.plugins.spring.boot)
|
alias(libs.plugins.spring.boot)
|
||||||
alias(libs.plugins.spring.dependencies)
|
alias(libs.plugins.spring.dependencies)
|
||||||
alias(libs.plugins.vaadin)
|
alias(libs.plugins.vaadin)
|
||||||
alias(libs.plugins.lombok)
|
}
|
||||||
//alias(libs.plugins.asciidoctorPdf)
|
|
||||||
//alias(libs.plugins.asciidoctorConvert)
|
java {
|
||||||
//alias(libs.plugins.asciidoctorGems)
|
toolchain {
|
||||||
//id "de.infolektuell.typst" version "0.8.0"
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { setUrl("https://nexus.thpeetz.de/repository/maven-central") }
|
maven { setUrl("https://nexus.thpeetz.de/repository/maven-central") }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
//ruby.gems()
|
|
||||||
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
|
|
||||||
maven { setUrl("https://repo.spring.io/milestone") }
|
|
||||||
maven { setUrl("https://maven.vaadin.com/vaadin-addons") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
ext {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
set('vaadinVersion', "25.2.6")
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
developmentOnly
|
|
||||||
runtimeClasspath {
|
|
||||||
extendsFrom developmentOnly
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.vaadin:vaadin-core'
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-artemis'
|
implementation 'org.springframework.boot:spring-boot-starter-artemis'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.apache.camel.springboot:camel-spring-boot-starter'
|
developmentOnly 'com.vaadin:vaadin-dev'
|
||||||
|
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
||||||
|
implementation 'org.apache.camel.springboot:camel-spring-boot-starter:4.22.0'
|
||||||
implementation 'org.apache.camel.springboot:camel-jms-starter'
|
implementation 'org.apache.camel.springboot:camel-jms-starter'
|
||||||
implementation 'org.apache.activemq:artemis-jakarta-client'
|
implementation 'org.apache.activemq:artemis-jakarta-client'
|
||||||
//implementation libs.artemis
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
|
||||||
implementation 'io.micrometer:micrometer-registry-prometheus'
|
|
||||||
implementation 'org.springframework.security:spring-security-oauth2-jose'
|
|
||||||
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
|
|
||||||
implementation 'com.h2database:h2'
|
|
||||||
implementation libs.hsqldb
|
|
||||||
implementation 'org.postgresql:postgresql'
|
|
||||||
//runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
|
|
||||||
implementation libs.hypersistence
|
implementation libs.hypersistence
|
||||||
implementation libs.mail
|
implementation libs.mail
|
||||||
implementation libs.jackson
|
|
||||||
implementation libs.gson
|
|
||||||
implementation libs.json
|
|
||||||
implementation 'org.hibernate.orm:hibernate-community-dialects'
|
|
||||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
|
||||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
|
||||||
}
|
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
|
||||||
testImplementation 'com.vaadin:vaadin-testbench-junit5'
|
|
||||||
testImplementation 'io.projectreactor:reactor-test'
|
|
||||||
testImplementation 'org.apache.camel:camel-test-spring-junit5'
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
|
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
|
||||||
|
implementation 'org.springframework.security:spring-security-oauth2-jose'
|
||||||
|
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
|
||||||
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
|
runtimeOnly 'org.xerial:sqlite-jdbc'
|
||||||
|
//implementation 'org.hibernate.orm:hibernate-community-dialects'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
//asciidoctorGems libs.rouge
|
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||||
//asciidoctorGems libs.diagram
|
testImplementation 'org.springframework.boot:spring-boot-starter-artemis-test'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-security-test'
|
||||||
|
testCompileOnly 'org.projectlombok:lombok'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
@@ -117,112 +74,15 @@ publishing {
|
|||||||
password = project.findProperty('nexusPassword')
|
password = project.findProperty('nexusPassword')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// maven {
|
|
||||||
// name = "gitlabPackageRegistry"
|
|
||||||
// url = uri("https://gitlab.com/api/v4/projects/64726715/packages/maven")
|
|
||||||
// credentials(PasswordCredentials)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass = 'de.thpeetz.kontor.Application'
|
|
||||||
}
|
|
||||||
|
|
||||||
bootRun {
|
bootRun {
|
||||||
args = ["--spring.profiles.active=${project.properties['profile'] ?: 'prod'}"]
|
args = ["--spring.profiles.active=${project.properties['profile'] ?: 'prod'}"]
|
||||||
}
|
}
|
||||||
|
|
||||||
task dockerImage(type: Exec) {
|
tasks.named('test') {
|
||||||
dependsOn(bootJar)
|
useJUnitPlatform()
|
||||||
commandLine "docker", "build", ".", "-t", "kontor:${project.version}"
|
|
||||||
}
|
|
||||||
|
|
||||||
vaadin {
|
|
||||||
productionMode = true
|
|
||||||
}
|
|
||||||
|
|
||||||
testing {
|
|
||||||
suites {
|
|
||||||
configureEach {
|
|
||||||
useJUnitJupiter()
|
|
||||||
dependencies {
|
|
||||||
implementation project()
|
|
||||||
implementation 'com.vaadin:vaadin-core'
|
|
||||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
||||||
implementation 'com.h2database:h2'
|
|
||||||
implementation libs.hsqldb
|
|
||||||
implementation libs.sqlite.jdbc
|
|
||||||
//runtimeOnly 'com.mysql:mysql-connector-j'
|
|
||||||
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
|
|
||||||
implementation('org.springframework.boot:spring-boot-starter-test') {
|
|
||||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
|
||||||
}
|
|
||||||
implementation 'org.springframework.security:spring-security-test'
|
|
||||||
implementation 'com.vaadin:vaadin-testbench-junit5'
|
|
||||||
implementation 'io.projectreactor:reactor-test'
|
|
||||||
runtimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test(JvmTestSuite) {
|
|
||||||
testType = TestSuiteType.UNIT_TEST
|
|
||||||
targets {
|
|
||||||
all {
|
|
||||||
testTask.configure {
|
|
||||||
reports {
|
|
||||||
junitXml {
|
|
||||||
outputPerTestCase = true // defaults to false
|
|
||||||
mergeReruns = true // defaults to false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalizedBy(jacocoTestReport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
integrationTest(JvmTestSuite) {
|
|
||||||
testType = "view-test"
|
|
||||||
targets {
|
|
||||||
all {
|
|
||||||
testTask.configure {
|
|
||||||
shouldRunAfter(test)
|
|
||||||
finalizedBy(jacocoTestReport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('check') {
|
|
||||||
dependsOn(testing.suites.integrationTest)
|
|
||||||
dependsOn(testing.suites.test)
|
|
||||||
dependsOn tasks.named('testAggregateTestReport', TestReport)
|
|
||||||
dependsOn tasks.named('integrationTestAggregateTestReport', TestReport)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
dependsOn test, integrationTest
|
|
||||||
reports {
|
|
||||||
xml.required = true
|
|
||||||
csv.required = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reporting {
|
|
||||||
reports {
|
|
||||||
testAggregateTestReport(AggregateTestReport) {
|
|
||||||
testType = TestSuiteType.UNIT_TEST
|
|
||||||
}
|
|
||||||
integrationTestAggregateTestReport(AggregateTestReport) {
|
|
||||||
testType = "view-test"
|
|
||||||
}
|
|
||||||
integrationTestCodeCoverageReport(JacocoCoverageReport) {
|
|
||||||
testType = "view-test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
|
|
||||||
}
|
|
||||||
@@ -3,3 +3,4 @@ version=0.3.0-SNAPSHOT
|
|||||||
group=de.thpeetz
|
group=de.thpeetz
|
||||||
nexusUser=kontor
|
nexusUser=kontor
|
||||||
nexusPassword=kontorNexus
|
nexusPassword=kontorNexus
|
||||||
|
profile=local
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
[versions]
|
[versions]
|
||||||
gradle = "8.6"
|
gradle = "9.5.1"
|
||||||
args4j = "2.33"
|
springdependencies = "1.1.7"
|
||||||
commonscli = "1.5.0"
|
springboot = "4.1.0"
|
||||||
|
vaadin = "25.2.6"
|
||||||
junit = "5.8.2"
|
junit = "5.8.2"
|
||||||
logback = "1.1.2"
|
logback = "1.1.2"
|
||||||
mockito = "1.9.5"
|
|
||||||
picoli = "4.7.0"
|
|
||||||
slf4j = "1.7.22"
|
slf4j = "1.7.22"
|
||||||
hsqldb = "2.7.1"
|
hsqldb = "2.7.1"
|
||||||
sqlite = "3.25.2"
|
sqlite = "3.25.2"
|
||||||
spotbugs = "6.0.7"
|
spotbugs = "6.0.7"
|
||||||
#asciidoctor = "4.0.2"
|
|
||||||
#rouge = "3.15.0"
|
|
||||||
#diagram = "2.2.2"
|
|
||||||
#diagram = "2.3.1"
|
|
||||||
sonarqube = "3.3"
|
sonarqube = "3.3"
|
||||||
cimtConventions = "1.0.0-SNAPSHOT"
|
|
||||||
springboot = "3.2.5"
|
|
||||||
springdependencies = "1.1.4"
|
|
||||||
vaadin = "24.4.23"
|
|
||||||
camel = "4.10.6"
|
camel = "4.10.6"
|
||||||
artemis = "2.41.0"
|
artemis = "2.41.0"
|
||||||
lombok = "8.11"
|
lombok = "8.11"
|
||||||
@@ -26,16 +17,15 @@ gson = "2.9.0"
|
|||||||
jackson = "2.16.1"
|
jackson = "2.16.1"
|
||||||
json_simple = "1.1.1"
|
json_simple = "1.1.1"
|
||||||
mail = "1.6.2"
|
mail = "1.6.2"
|
||||||
hypersistence = "3.9.10"
|
hypersistence = "3.15.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
args4j = { module = "args4j:args4j", version.ref = "args4j" }
|
vaadin-bom = { group = "com.vaadin", name = "vaadin-bom", version.ref = "vaadin" }
|
||||||
commonscli = { module = "commons-cli:commons-cli", version.ref = "commonscli" }
|
camel-bom = { module = "org.apache.camel.springboot:camel-spring-boot-bom", version.ref = "camel"}
|
||||||
|
artemis = { module = "org.apache.activemq:artemis-jms-server", version.ref = "artemis" }
|
||||||
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||||
logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
||||||
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
mockito = { module = "org.mockito:mockito-all", version.ref = "mockito" }
|
|
||||||
picocli = { module = "info.picocli:picocli", version.ref = "picoli" }
|
|
||||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||||
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
@@ -43,15 +33,7 @@ jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref
|
|||||||
json = { module = "com.googlecode.json-simple:json-simple", version.ref ="json_simple" }
|
json = { module = "com.googlecode.json-simple:json-simple", version.ref ="json_simple" }
|
||||||
mail = { module = "com.sun.mail:javax.mail", version.ref ="mail" }
|
mail = { module = "com.sun.mail:javax.mail", version.ref ="mail" }
|
||||||
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
||||||
hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-63", version.ref = "hypersistence" }
|
hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-73", version.ref = "hypersistence" }
|
||||||
vaadin-bom = { module = "com.vaadin:vaadin-bom", version.ref = "vaadin" }
|
|
||||||
camel-bom = { module = "org.apache.camel.springboot:camel-spring-boot-bom", version.ref = "camel"}
|
|
||||||
artemis = { module = "org.apache.activemq:artemis-jms-server", version.ref = "artemis" }
|
|
||||||
#asciidoctorGradleJvmGems = { module = "org.asciidoctor:asciidoctor-gradle-jvm-gems", version.ref= "asciidoctor" }
|
|
||||||
#asciidoctorGradleJvm = { module = "org.asciidoctor:asciidoctor-gradle-jvm", version.ref= "asciidoctor" }
|
|
||||||
#asciidoctorGradleJvmPdf = { module = "org.asciidoctor:asciidoctor-gradle-jvm-pdf", version.ref= "asciidoctor" }
|
|
||||||
#rouge = { module = "rubygems:rouge", version.ref = "rouge" }
|
|
||||||
#diagram = { module = "rubygems:asciidoctor-diagram", version.ref = "diagram" }
|
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
logback = ["logbackCore", "logbackClassic"]
|
logback = ["logbackCore", "logbackClassic"]
|
||||||
@@ -59,13 +41,6 @@ logback = ["logbackCore", "logbackClassic"]
|
|||||||
[plugins]
|
[plugins]
|
||||||
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" }
|
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" }
|
||||||
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
|
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
|
||||||
#asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor" }
|
|
||||||
#asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor" }
|
|
||||||
#asciidoctorGems = { id = "org.asciidoctor.jvm.gems", version.ref = "asciidoctor" }
|
|
||||||
javaConvention = { id = "de.cimt.java-conventions", version.ref = "cimtConventions" }
|
|
||||||
applicationConvention = { id = "de.cimt.application-conventions", version.ref = "cimtConventions" }
|
|
||||||
libraryConvention = { id = "de.cimt.library-conventions", version.ref = "cimtConventions" }
|
|
||||||
#asciidoctorConvention = { id = "de.cimt.asciidoctor-conventions", version.ref = "cimtConventions" }
|
|
||||||
spring-boot = { id = "org.springframework.boot", version.ref = "springboot"}
|
spring-boot = { id = "org.springframework.boot", version.ref = "springboot"}
|
||||||
spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" }
|
spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" }
|
||||||
vaadin = { id = "com.vaadin", version.ref = "vaadin" }
|
vaadin = { id = "com.vaadin", version.ref = "vaadin" }
|
||||||
|
|||||||
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+7
-8
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -84,7 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -170,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -203,15 +203,14 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
Vendored
+12
-22
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -21,8 +23,8 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -49,7 +51,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -63,30 +65,18 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1 @@
|
|||||||
pluginManagement {
|
|
||||||
resolutionStrategy {
|
|
||||||
eachPlugin {
|
|
||||||
if (requested.id.id == 'org.springframework.boot') {
|
|
||||||
useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
|
|
||||||
}
|
|
||||||
if (requested.id.id == 'org.gradle.toolchains.foojay-resolver') {
|
|
||||||
useModule("org.gradle.toolchains.foojay-resolver-convention:0.4.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
maven { setUrl("https://nexus.thpeetz.de/repository/maven-central") }
|
|
||||||
mavenCentral()
|
|
||||||
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
|
|
||||||
maven { setUrl("https://repo.spring.io/milestone") }
|
|
||||||
maven { url 'https://plugins.gradle.org/m2/' }
|
|
||||||
}
|
|
||||||
// plugins {
|
|
||||||
// id 'com.vaadin' version "${vaadinVersion}"
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = 'kontor-spring'
|
rootProject.name = 'kontor-spring'
|
||||||
|
|||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Artist;
|
|
||||||
import de.thpeetz.kontor.views.comics.ArtistForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.ArtistView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class ArtistViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ArtistView artistView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenArtistSelected() {
|
|
||||||
Grid<Artist> grid = artistView.getGrid();
|
|
||||||
Artist firstArtist = getFirstItem(grid);
|
|
||||||
|
|
||||||
ArtistForm form = artistView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstArtist);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstArtist.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Artist getFirstItem(Grid<Artist> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Artist> artists = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(5, count);
|
|
||||||
return artists.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Artist;
|
|
||||||
import de.thpeetz.kontor.views.comics.ArtistForm;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class ArtistformTest {
|
|
||||||
|
|
||||||
private Artist artist1;
|
|
||||||
private static final String ARTISTNAME= "Lee, Stan";
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setupData() {
|
|
||||||
artist1 = new Artist();
|
|
||||||
artist1.setName(ARTISTNAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formFieldsPopulated() {
|
|
||||||
ArtistForm form = new ArtistForm();
|
|
||||||
form.setArtist(artist1);
|
|
||||||
assertEquals(ARTISTNAME, form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void saveEventHasCorrectValues() {
|
|
||||||
ArtistForm form = new ArtistForm();
|
|
||||||
Artist artist = new Artist();
|
|
||||||
form.setArtist(artist);
|
|
||||||
form.name.setValue(ARTISTNAME);
|
|
||||||
|
|
||||||
AtomicReference<Artist> savedArtistReference = new AtomicReference<>(null);
|
|
||||||
form.addSaveListener(e -> {
|
|
||||||
savedArtistReference.set(e.getArtist());
|
|
||||||
});
|
|
||||||
form.save.click();
|
|
||||||
Artist savedArtist = savedArtistReference.get();
|
|
||||||
assertEquals(ARTISTNAME, savedArtist.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deleteEventHasCorrectValues() {
|
|
||||||
ArtistForm form = new ArtistForm();
|
|
||||||
Artist artist = new Artist();
|
|
||||||
form.setArtist(artist);
|
|
||||||
form.name.setValue(ARTISTNAME);
|
|
||||||
|
|
||||||
AtomicReference<Artist> deletedArtistReference = new AtomicReference<>(null);
|
|
||||||
form.addDeleteListener(e -> {
|
|
||||||
deletedArtistReference.set(e.getArtist());
|
|
||||||
});
|
|
||||||
form.delete.click();
|
|
||||||
Artist deletedArtist = deletedArtistReference.get();
|
|
||||||
assertEquals(ARTISTNAME, deletedArtist.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Comic;
|
|
||||||
import de.thpeetz.kontor.views.comics.ComicForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.ComicView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
public class ComicViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ComicView comicView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenComicSelected() {
|
|
||||||
Grid<Comic> grid = comicView.getGrid();
|
|
||||||
Comic firstComic = getFirstItem(grid);
|
|
||||||
|
|
||||||
ComicForm form = comicView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstComic);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstComic.getTitle(), form.title.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comic getFirstItem(Grid<Comic> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Comic> comics = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(169, count);
|
|
||||||
return comics.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.ComicWork;
|
|
||||||
import de.thpeetz.kontor.views.comics.ComicWorkForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.ComicWorkView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class ComicWorkViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ComicWorkView comicWorkView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenComicSelected() {
|
|
||||||
Grid<ComicWork> grid = comicWorkView.getGrid();
|
|
||||||
ComicWork firstComicWork = getFirstItem(grid);
|
|
||||||
|
|
||||||
ComicWorkForm form = comicWorkView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstComicWork);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstComicWork.getComic(), form.comic.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ComicWork getFirstItem(Grid<ComicWork> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<ComicWork> comicWorks = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(18, count);
|
|
||||||
return comicWorks.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Issue;
|
|
||||||
import de.thpeetz.kontor.views.comics.IssueForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.IssueView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
public class IssueViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IssueView issueView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenIssueSelected() {
|
|
||||||
Grid<Issue> grid = issueView.getGrid();
|
|
||||||
Issue firstIssue = getFirstItem(grid);
|
|
||||||
|
|
||||||
IssueForm form = issueView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstIssue);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstIssue.getIssueNumber(), form.issueNumber.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Issue getFirstItem(Grid<Issue> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Issue> issues = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(750, count);
|
|
||||||
return issues.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Publisher;
|
|
||||||
import de.thpeetz.kontor.views.comics.PublisherForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.PublisherView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class PublisherViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PublisherView publisherView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenPublisherSelected() {
|
|
||||||
Grid<Publisher> grid = publisherView.getGrid();
|
|
||||||
Publisher firstPublisher = getFirstItem(grid);
|
|
||||||
|
|
||||||
PublisherForm form = publisherView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstPublisher);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstPublisher.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Publisher getFirstItem(Grid<Publisher> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Publisher> publishers = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(18, count);
|
|
||||||
return publishers.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.StoryArc;
|
|
||||||
import de.thpeetz.kontor.views.comics.StoryArcForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.StoryArcView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class StoryArcViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private StoryArcView storyArcView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenStoryArcSelected() {
|
|
||||||
Grid<StoryArc> grid = storyArcView.getGrid();
|
|
||||||
StoryArc firstStoryArc = getFirstItem(grid);
|
|
||||||
|
|
||||||
StoryArcForm form = storyArcView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstStoryArc);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstStoryArc.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private StoryArc getFirstItem(Grid<StoryArc> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<StoryArc> storyArcs = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(3, count);
|
|
||||||
return storyArcs.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.TradePaperback;
|
|
||||||
import de.thpeetz.kontor.views.comics.TradePaperBackForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.TradePaperbackView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class TradePaperbackViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TradePaperbackView tradePaperbackView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenVolumeSelected() {
|
|
||||||
Grid<TradePaperback> grid = tradePaperbackView.getGrid();
|
|
||||||
|
|
||||||
TradePaperback firstTradePaperback = getFirstItem(grid);
|
|
||||||
|
|
||||||
TradePaperBackForm form = tradePaperbackView.getForm();
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
|
|
||||||
if (firstTradePaperback != null) {
|
|
||||||
grid.asSingleSelect().setValue(firstTradePaperback);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstTradePaperback.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TradePaperback getFirstItem(Grid<TradePaperback> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<TradePaperback> tradePaperbacks = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(40, count);
|
|
||||||
return tradePaperbacks.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Volume;
|
|
||||||
import de.thpeetz.kontor.views.comics.VolumeForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.VolumeView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class VolumeViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private VolumeView volumeView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenVolumeSelected() {
|
|
||||||
Grid<Volume> grid = volumeView.getGrid();
|
|
||||||
|
|
||||||
Volume firstVolume = getFirstItem(grid);
|
|
||||||
|
|
||||||
VolumeForm form = volumeView.getForm();
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
|
|
||||||
if (firstVolume != null) {
|
|
||||||
grid.asSingleSelect().setValue(firstVolume);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstVolume.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Volume getFirstItem(Grid<Volume> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Volume> volumes = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(0, count);
|
|
||||||
if (count > 0) {
|
|
||||||
return volumes.get(0);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.views;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.comics.Worktype;
|
|
||||||
import de.thpeetz.kontor.views.comics.WorktypeForm;
|
|
||||||
import de.thpeetz.kontor.views.comics.WorktypeView;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@SpringBootTest
|
|
||||||
class WorktypeViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private WorktypeView worktypeView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenWorktypeSelected() {
|
|
||||||
Grid<Worktype> grid = worktypeView.getGrid();
|
|
||||||
|
|
||||||
Worktype firstWorktype = getFirstItem(grid);
|
|
||||||
|
|
||||||
WorktypeForm form = worktypeView.getForm();
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
|
|
||||||
if (firstWorktype != null) {
|
|
||||||
grid.asSingleSelect().setValue(firstWorktype);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstWorktype.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Worktype getFirstItem(Grid<Worktype> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Worktype> worktypes = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
log.info("found worktypes: {}", worktypes);
|
|
||||||
assertEquals(3, count);
|
|
||||||
return worktypes.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.CardSet;
|
|
||||||
import de.thpeetz.kontor.views.tysc.CardSetForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.CardSetView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class CardSetViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private CardSetView cardSetView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenCardSetSelected() {
|
|
||||||
Grid<CardSet> grid = cardSetView.getGrid();
|
|
||||||
CardSet firstCardSet = getFirstItem(grid);
|
|
||||||
|
|
||||||
CardSetForm form = cardSetView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstCardSet);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstCardSet.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private CardSet getFirstItem(Grid<CardSet> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<CardSet> cardSets = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(15, count);
|
|
||||||
return cardSets.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Card;
|
|
||||||
import de.thpeetz.kontor.views.tysc.CardForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.CardView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class CardViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private CardView cardView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenCardSelected() {
|
|
||||||
Grid<Card> grid = cardView.getGrid();
|
|
||||||
Card firstCard = getFirstItem(grid);
|
|
||||||
|
|
||||||
CardForm form = cardView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstCard);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(String.valueOf(firstCard.getCardNumber()), form.cardNumber.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Card getFirstItem(Grid<Card> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Card> cards = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(10, count);
|
|
||||||
return cards.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.FieldPosition;
|
|
||||||
import de.thpeetz.kontor.views.tysc.PositionForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.PositionView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class FieldPositionViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PositionView positionView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenPositionSelected() {
|
|
||||||
Grid<FieldPosition> grid = positionView.getGrid();
|
|
||||||
FieldPosition firstFieldPosition = getFirstItem(grid);
|
|
||||||
|
|
||||||
PositionForm form = positionView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstFieldPosition);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstFieldPosition.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private FieldPosition getFirstItem(Grid<FieldPosition> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<FieldPosition> positions = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(44, count);
|
|
||||||
return positions.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Player;
|
|
||||||
import de.thpeetz.kontor.views.tysc.PlayerForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.PlayerView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class PlayerViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PlayerView playerView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenPlayerSelected() {
|
|
||||||
Grid<Player> grid = playerView.getGrid();
|
|
||||||
Player firstPlayer = getFirstItem(grid);
|
|
||||||
|
|
||||||
PlayerForm form = playerView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstPlayer);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstPlayer.getLastName(), form.lastName.getValue());
|
|
||||||
assertEquals(firstPlayer.getFirstName(), form.firstName.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Player getFirstItem(Grid<Player> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Player> players = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(38, count);
|
|
||||||
return players.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Rooster;
|
|
||||||
import de.thpeetz.kontor.views.tysc.RoosterForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.RoosterView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class RoosterViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RoosterView roosterView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenRoosterSelected() {
|
|
||||||
Grid<Rooster> grid = roosterView.getGrid();
|
|
||||||
Rooster firstRooster = getFirstItem(grid);
|
|
||||||
|
|
||||||
RoosterForm form = roosterView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstRooster);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstRooster.getYear(), form.year.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rooster getFirstItem(Grid<Rooster> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Rooster> roosters = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(11, count);
|
|
||||||
return roosters.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Sport;
|
|
||||||
import de.thpeetz.kontor.views.tysc.SportForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.SportView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class SportViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SportView sportView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenSportSelected() {
|
|
||||||
Grid<Sport> grid = sportView.getGrid();
|
|
||||||
Sport firstSport = getFirstItem(grid);
|
|
||||||
|
|
||||||
SportForm form = sportView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstSport);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstSport.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Sport getFirstItem(Grid<Sport> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Sport> sports = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(4, count);
|
|
||||||
return sports.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Team;
|
|
||||||
import de.thpeetz.kontor.views.tysc.TeamForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.TeamView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class TeamViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TeamView teamView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenTeamSelected() {
|
|
||||||
Grid<Team> grid = teamView.getGrid();
|
|
||||||
Team firstTeam = getFirstItem(grid);
|
|
||||||
|
|
||||||
TeamForm form = teamView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstTeam);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstTeam.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Team getFirstItem(Grid<Team> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Team> teams = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(122, count);
|
|
||||||
return teams.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package de.thpeetz.kontor.views.tysc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
|
|
||||||
import de.thpeetz.kontor.data.tysc.Vendor;
|
|
||||||
import de.thpeetz.kontor.views.tysc.VendorForm;
|
|
||||||
import de.thpeetz.kontor.views.tysc.VendorView;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class VendorViewTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private VendorView vendorView;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void formShownWhenVendorSelected() {
|
|
||||||
Grid<Vendor> grid = vendorView.getGrid();
|
|
||||||
Vendor firstVendor = getFirstItem(grid);
|
|
||||||
|
|
||||||
VendorForm form = vendorView.getForm();
|
|
||||||
|
|
||||||
assertFalse(form.isVisible());
|
|
||||||
grid.asSingleSelect().setValue(firstVendor);
|
|
||||||
assertTrue(form.isVisible());
|
|
||||||
assertEquals(firstVendor.getName(), form.name.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vendor getFirstItem(Grid<Vendor> grid) {
|
|
||||||
int count = grid.getListDataView().getItemCount();
|
|
||||||
List<Vendor> vendors = grid.getListDataView().getItems().collect(Collectors.toList());
|
|
||||||
assertEquals(9, count);
|
|
||||||
return vendors.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
server.port=8085
|
|
||||||
|
|
||||||
spring.hibernate.dialect=org.hibernate.dialect.HSQLDialect
|
|
||||||
spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
|
|
||||||
spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver
|
|
||||||
spring.datasource.url=jdbc:hsqldb:mem:itDb
|
|
||||||
spring.datasource.username=sa
|
|
||||||
spring.datasource.password=sa
|
|
||||||
|
|
||||||
#spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
|
||||||
#spring.datasource.driverClassName=org.sqlite.JDBC
|
|
||||||
#spring.datasource.url=jdbc:sqlite:file:./kontorITDb?cache=shared
|
|
||||||
#spring.datasource.username=sa
|
|
||||||
#spring.datasource.password=sa
|
|
||||||
|
|
||||||
spring.jpa.defer-datasource-initialization = true
|
|
||||||
#spring.jpa.hibernate.ddl-auto=create-drop
|
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
|
||||||
spring.jpa.show-sql=false
|
|
||||||
spring.sql.init.mode=always
|
|
||||||
|
|
||||||
spring.mustache.check-template-location = false
|
|
||||||
|
|
||||||
logging.level.org.atmosphere=INFO
|
|
||||||
logging.level.org.springframework.web=INFO
|
|
||||||
logging.level.guru.springframework.controllers=DEBUG
|
|
||||||
logging.level.org.hibernate=INFO
|
|
||||||
logging.level.de.thpeetz=DEBUG
|
|
||||||
|
|
||||||
jwt.auth.secret=J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export declare const applyCss: (target: Node) => void;
|
||||||
@@ -0,0 +1,707 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/// <reference lib="es2018" />
|
||||||
|
import { Flow as _Flow } from 'Frontend/generated/jar-resources/Flow.js';
|
||||||
|
import React, { useCallback, useEffect, useReducer, useRef, useState, type ReactNode } from 'react';
|
||||||
|
import { matchRoutes, useBlocker, useLocation, useNavigate, type NavigateOptions, useHref } from 'react-router';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const flow = new _Flow({
|
||||||
|
imports: () => import('Frontend/generated/flow/generated-flow-imports.js')
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
render() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flowReact : { active: boolean } = {
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClickHandler for vaadin-router-go event is copied from vaadin/router click.js
|
||||||
|
// @ts-ignore
|
||||||
|
function getAnchorOrigin(anchor) {
|
||||||
|
// IE11: on HTTP and HTTPS the default port is not included into
|
||||||
|
// window.location.origin, so won't include it here either.
|
||||||
|
const port = anchor.port;
|
||||||
|
const protocol = anchor.protocol;
|
||||||
|
const defaultHttp = protocol === 'http:' && port === '80';
|
||||||
|
const defaultHttps = protocol === 'https:' && port === '443';
|
||||||
|
const host =
|
||||||
|
defaultHttp || defaultHttps
|
||||||
|
? anchor.hostname // does not include the port number (e.g. www.example.org)
|
||||||
|
: anchor.host; // does include the port number (e.g. www.example.org:80)
|
||||||
|
return `${protocol}//${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeURL(url: URL): void | string {
|
||||||
|
// ignore click if baseURI does not match the document (external)
|
||||||
|
if (!url.href.startsWith(document.baseURI)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize path against baseURI
|
||||||
|
return '/' + url.href.slice(document.baseURI.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractURL(event: MouseEvent): void | URL {
|
||||||
|
// ignore the click if the default action is prevented
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if not with the primary mouse button
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if a modifier key is pressed
|
||||||
|
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the <a> element that the click is at (or within)
|
||||||
|
let maybeAnchor = event.target;
|
||||||
|
const path = event.composedPath
|
||||||
|
? event.composedPath()
|
||||||
|
: // @ts-ignore
|
||||||
|
event.path || [];
|
||||||
|
|
||||||
|
// example to check: `for...of` loop here throws the "Not yet implemented" error
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const target = path[i];
|
||||||
|
if (target.nodeName && target.nodeName.toLowerCase() === 'a') {
|
||||||
|
maybeAnchor = target;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
while (maybeAnchor && maybeAnchor.nodeName.toLowerCase() !== 'a') {
|
||||||
|
// @ts-ignore
|
||||||
|
maybeAnchor = maybeAnchor.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if not at an <a> element
|
||||||
|
// @ts-ignore
|
||||||
|
if (!maybeAnchor || maybeAnchor.nodeName.toLowerCase() !== 'a') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchor = maybeAnchor as HTMLAnchorElement;
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has a non-default target
|
||||||
|
if (anchor.target && anchor.target.toLowerCase() !== '_self') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has the 'download' attribute
|
||||||
|
if (anchor.hasAttribute('download')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has the 'router-ignore' attribute
|
||||||
|
if (anchor.hasAttribute('router-ignore')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the target URL is a fragment on the current page
|
||||||
|
if (anchor.pathname === window.location.pathname && anchor.hash !== '') {
|
||||||
|
// @ts-ignore
|
||||||
|
window.location.hash = anchor.hash;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the target is external to the app
|
||||||
|
// In IE11 HTMLAnchorElement does not have the `origin` property
|
||||||
|
// @ts-ignore
|
||||||
|
const origin = anchor.origin || getAnchorOrigin(anchor);
|
||||||
|
if (origin !== window.location.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URL(anchor.href, anchor.baseURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPath(event: MouseEvent): void | string {
|
||||||
|
const url = extractURL(event);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return normalizeURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerGlobalClickHandler = () => {
|
||||||
|
window.addEventListener('click', (event: MouseEvent) => {
|
||||||
|
if (flowReact.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = extractURL(event);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ignore click if baseURI does not match the document (external)
|
||||||
|
if (!url.href.startsWith(document.baseURI)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event && event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize path against baseURI
|
||||||
|
const path = url.pathname + url.search + url.hash;
|
||||||
|
const state = {...window.history.state}
|
||||||
|
if (state.idx !== undefined) {
|
||||||
|
state.idx = state.idx + 1;
|
||||||
|
}
|
||||||
|
window.history.pushState(state, '', path);
|
||||||
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||||
|
}, { capture: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire 'vaadin-navigated' event to inform components of navigation.
|
||||||
|
* @param pathname pathname of navigation
|
||||||
|
* @param search search of navigation
|
||||||
|
*/
|
||||||
|
function fireNavigated(pathname: string, search: string) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('vaadin-navigated', {
|
||||||
|
detail: {
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.Vaadin.Flow.navigation;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function postpone() {}
|
||||||
|
|
||||||
|
const prevent = () => postpone;
|
||||||
|
|
||||||
|
type RouterContainer = Awaited<ReturnType<(typeof flow.serverSideRoutes)[0]['action']>>;
|
||||||
|
|
||||||
|
type PortalEntry = {
|
||||||
|
readonly children: ReactNode;
|
||||||
|
readonly domNode: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlowPortalProps = React.PropsWithChildren<
|
||||||
|
Readonly<{
|
||||||
|
domNode: HTMLElement;
|
||||||
|
onRemove(): void;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
function FlowPortal({ children, domNode, onRemove }: FlowPortalProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
domNode.addEventListener(
|
||||||
|
'flow-portal-remove',
|
||||||
|
(event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onRemove();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return createPortal(children, domNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ADD_FLOW_PORTAL = 'ADD_FLOW_PORTAL';
|
||||||
|
|
||||||
|
type AddFlowPortalAction = Readonly<{
|
||||||
|
type: typeof ADD_FLOW_PORTAL;
|
||||||
|
portal: React.ReactElement<FlowPortalProps>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function addFlowPortal(portal: React.ReactElement<FlowPortalProps>): AddFlowPortalAction {
|
||||||
|
return {
|
||||||
|
type: ADD_FLOW_PORTAL,
|
||||||
|
portal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const REMOVE_FLOW_PORTAL = 'REMOVE_FLOW_PORTAL';
|
||||||
|
|
||||||
|
type RemoveFlowPortalAction = Readonly<{
|
||||||
|
type: typeof REMOVE_FLOW_PORTAL;
|
||||||
|
key: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function removeFlowPortal(key: string): RemoveFlowPortalAction {
|
||||||
|
return {
|
||||||
|
type: REMOVE_FLOW_PORTAL,
|
||||||
|
key
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function flowPortalsReducer(
|
||||||
|
portals: readonly React.ReactElement<FlowPortalProps>[],
|
||||||
|
action: AddFlowPortalAction | RemoveFlowPortalAction
|
||||||
|
) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_FLOW_PORTAL:
|
||||||
|
return [...portals, action.portal];
|
||||||
|
case REMOVE_FLOW_PORTAL:
|
||||||
|
return portals.filter(({ key }) => key !== action.key);
|
||||||
|
default:
|
||||||
|
return portals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NavigateOpts = {
|
||||||
|
to: string;
|
||||||
|
callback: boolean;
|
||||||
|
opts?: NavigateOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NavigateFn = (to: string, callback: boolean, opts?: NavigateOptions) => void;
|
||||||
|
|
||||||
|
let navigateInProgress = false;
|
||||||
|
/**
|
||||||
|
* A hook providing the `navigate(path: string, opts?: NavigateOptions)` function
|
||||||
|
* with React Router API that has more consistent history updates. Uses internal
|
||||||
|
* queue for processing navigate calls.
|
||||||
|
*/
|
||||||
|
function useQueuedNavigate(
|
||||||
|
waitReference: React.MutableRefObject<Promise<void> | undefined>,
|
||||||
|
navigated: React.MutableRefObject<boolean>
|
||||||
|
): NavigateFn {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const navigateQueue = useRef<NavigateOpts[]>([]).current;
|
||||||
|
const [navigateQueueLength, setNavigateQueueLength] = useState(0);
|
||||||
|
|
||||||
|
const dequeueNavigation = useCallback(() => {
|
||||||
|
if (navigateInProgress) {
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateArgs = navigateQueue.shift();
|
||||||
|
if (navigateArgs === undefined) {
|
||||||
|
// Empty queue, do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockingNavigate = async () => {
|
||||||
|
if (waitReference.current) {
|
||||||
|
await waitReference.current;
|
||||||
|
waitReference.current = undefined;
|
||||||
|
}
|
||||||
|
navigated.current = !navigateArgs.callback;
|
||||||
|
navigateInProgress = true;
|
||||||
|
navigate(navigateArgs.to, navigateArgs.opts);
|
||||||
|
setNavigateQueueLength(navigateQueue.length);
|
||||||
|
};
|
||||||
|
blockingNavigate();
|
||||||
|
}, [navigate, setNavigateQueueLength]);
|
||||||
|
|
||||||
|
const dequeueNavigationAfterCurrentTask = useCallback(() => {
|
||||||
|
setTimeout(dequeueNavigation, 0);
|
||||||
|
}, [dequeueNavigation]);
|
||||||
|
|
||||||
|
const enqueueNavigation = useCallback(
|
||||||
|
(to: string, callback: boolean, opts?: NavigateOptions) => {
|
||||||
|
navigateQueue.push({ to: to, callback: callback, opts: opts });
|
||||||
|
setNavigateQueueLength(navigateQueue.length);
|
||||||
|
if (navigateQueue.length === 1) {
|
||||||
|
// The first navigation can be started right after any pending sync
|
||||||
|
// jobs, which could add more navigations to the queue.
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setNavigateQueueLength, dequeueNavigationAfterCurrentTask]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
// The Flow component has rendered, but history might not be
|
||||||
|
// updated yet, as React Router does it asynchronously.
|
||||||
|
// Use microtask callback for history consistency.
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
},
|
||||||
|
[navigateQueueLength, dequeueNavigationAfterCurrentTask]
|
||||||
|
);
|
||||||
|
|
||||||
|
return enqueueNavigation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flowNavigation = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Flow() {
|
||||||
|
const ref = useRef<HTMLOutputElement>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
|
||||||
|
navigated.current =
|
||||||
|
navigated.current ||
|
||||||
|
(nextLocation.pathname === currentLocation.pathname &&
|
||||||
|
nextLocation.search === currentLocation.search &&
|
||||||
|
nextLocation.hash === currentLocation.hash);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const location = useLocation();
|
||||||
|
const navigated = useRef<boolean>(false);
|
||||||
|
const blockerHandled = useRef<boolean>(false);
|
||||||
|
const fromAnchor = useRef<boolean>(false);
|
||||||
|
const containerRef = useRef<RouterContainer | undefined>(undefined);
|
||||||
|
const roundTrip = useRef<Promise<void> | undefined>(undefined);
|
||||||
|
const queuedNavigate = useQueuedNavigate(roundTrip, navigated);
|
||||||
|
const basename = useHref('/');
|
||||||
|
|
||||||
|
// portalsReducer function is used as state outside the Flow component.
|
||||||
|
const [portals, dispatchPortalAction] = useReducer(flowPortalsReducer, []);
|
||||||
|
|
||||||
|
const addPortalEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<PortalEntry>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const key = Math.random().toString(36).slice(2);
|
||||||
|
dispatchPortalAction(
|
||||||
|
addFlowPortal(
|
||||||
|
<FlowPortal
|
||||||
|
key={key}
|
||||||
|
domNode={event.detail.domNode}
|
||||||
|
onRemove={() => dispatchPortalAction(removeFlowPortal(key))}
|
||||||
|
>
|
||||||
|
{event.detail.children}
|
||||||
|
</FlowPortal>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatchPortalAction]
|
||||||
|
);
|
||||||
|
|
||||||
|
const navigateEventHandler = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
const path = extractPath(event);
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event && event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
navigated.current = false;
|
||||||
|
// When navigation is triggered by click on a link, fromAnchor is set to true
|
||||||
|
// in order to get a server round-trip even when navigating to the same URL again
|
||||||
|
fromAnchor.current = true;
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
navigate(path);
|
||||||
|
// Dispatch close event for overlay drawer on click navigation.
|
||||||
|
window.dispatchEvent(new CustomEvent('close-overlay-drawer'));
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const vaadinRouterGoEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<URL>) => {
|
||||||
|
const url = event.detail;
|
||||||
|
const path = normalizeURL(url);
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
navigate(path);
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const vaadinNavigateEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<{ state: unknown; url: string; replace?: boolean; callback: boolean }>) => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
// clean base uri away if for instance redirected to http://localhost/path/user?id=10
|
||||||
|
// else the whole http... will be appended to the url see #19580
|
||||||
|
const path = event.detail.url.startsWith(document.baseURI)
|
||||||
|
? '/' + event.detail.url.slice(document.baseURI.length)
|
||||||
|
: '/' + event.detail.url;
|
||||||
|
fromAnchor.current = false;
|
||||||
|
queuedNavigate(path, event.detail.callback, { state: event.detail.state, replace: event.detail.replace });
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const redirect = useCallback(
|
||||||
|
(path: string) => {
|
||||||
|
return () => {
|
||||||
|
navigate(path, { replace: true });
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener('vaadin-router-go', vaadinRouterGoEventHandler);
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener('vaadin-navigate', vaadinNavigateEventHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener('vaadin-router-go', vaadinRouterGoEventHandler);
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener('vaadin-navigate', vaadinNavigateEventHandler);
|
||||||
|
};
|
||||||
|
}, [vaadinRouterGoEventHandler, vaadinNavigateEventHandler]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener("popstate", flowNavigation);
|
||||||
|
window.addEventListener('click', navigateEventHandler);
|
||||||
|
flowReact.active = true;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
containerRef.current?.parentNode?.removeChild(containerRef.current);
|
||||||
|
containerRef.current?.removeEventListener('flow-portal-add', addPortalEventHandler as EventListener);
|
||||||
|
containerRef.current = undefined;
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener("popstate", flowNavigation);
|
||||||
|
window.removeEventListener('click', navigateEventHandler);
|
||||||
|
flowReact.active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state === 'blocked') {
|
||||||
|
if (blockerHandled.current) {
|
||||||
|
// Blocker is handled and the new navigation
|
||||||
|
// gets queued to be executed after the current handling ends.
|
||||||
|
const { pathname, state } = blocker.location;
|
||||||
|
// Clear base name to not get /baseName/basename/path
|
||||||
|
const pathNoBase = pathname.substring(basename.length);
|
||||||
|
// path should always start with / else react-router will append to current url
|
||||||
|
queuedNavigate(pathNoBase.startsWith('/') ? pathNoBase : '/' + pathNoBase, true, {
|
||||||
|
state: state,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blockerHandled.current = true;
|
||||||
|
let blockingPromise: any;
|
||||||
|
roundTrip.current = new Promise<void>(
|
||||||
|
(resolve, reject) => (blockingPromise = { resolve: resolve, reject: reject })
|
||||||
|
);
|
||||||
|
// Release blocker handling after promise is fulfilled
|
||||||
|
roundTrip.current.then(
|
||||||
|
() => (blockerHandled.current = false),
|
||||||
|
() => (blockerHandled.current = false)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Proceed to the blocked location, unless the navigation originates from a click on a link.
|
||||||
|
// In that case continue with function execution and perform a server round-trip
|
||||||
|
if (navigated.current && !fromAnchor.current) {
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fromAnchor.current = false;
|
||||||
|
const { pathname, search } = blocker.location;
|
||||||
|
const routes = ((window as any)?.Vaadin?.routesConfig || []) as any[];
|
||||||
|
let matched = matchRoutes(Array.from(routes), pathname);
|
||||||
|
|
||||||
|
// Navigation between server routes
|
||||||
|
// @ts-ignore
|
||||||
|
if (matched && matched.filter((path) => path.route?.element?.type?.name === Flow.name).length != 0) {
|
||||||
|
containerRef.current?.onBeforeEnter?.call(
|
||||||
|
containerRef?.current,
|
||||||
|
{ pathname, search },
|
||||||
|
{
|
||||||
|
prevent() {
|
||||||
|
blocker.reset();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
navigated.current = false;
|
||||||
|
},
|
||||||
|
redirect,
|
||||||
|
continue() {
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router
|
||||||
|
);
|
||||||
|
navigated.current = true;
|
||||||
|
} else {
|
||||||
|
// For covering the 'server -> client' use case
|
||||||
|
Promise.resolve(
|
||||||
|
containerRef.current?.onBeforeLeave?.call(
|
||||||
|
containerRef?.current,
|
||||||
|
{
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
|
},
|
||||||
|
{ prevent },
|
||||||
|
router
|
||||||
|
)
|
||||||
|
).then((cmd: unknown) => {
|
||||||
|
if (cmd === postpone && containerRef.current) {
|
||||||
|
// postponed navigation: expose existing blocker to Flow
|
||||||
|
containerRef.current.serverConnected = (cancel) => {
|
||||||
|
if (cancel) {
|
||||||
|
blocker.reset();
|
||||||
|
} else {
|
||||||
|
blocker.proceed();
|
||||||
|
}
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// permitted navigation: proceed with the blocker
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [blocker.state, blocker.location]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state === 'blocked') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (navigated.current) {
|
||||||
|
navigated.current = false;
|
||||||
|
fireNavigated(location.pathname, location.search);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flow.serverSideRoutes[0]
|
||||||
|
.action({ pathname: location.pathname, search: location.search })
|
||||||
|
.then((container) => {
|
||||||
|
const outlet = ref.current?.parentNode;
|
||||||
|
if (outlet && outlet !== container.parentNode) {
|
||||||
|
outlet.append(container);
|
||||||
|
container.addEventListener('flow-portal-add', addPortalEventHandler as EventListener);
|
||||||
|
containerRef.current = container;
|
||||||
|
}
|
||||||
|
return container.onBeforeEnter?.call(
|
||||||
|
container,
|
||||||
|
// Always add base to path as it is cleaned in getFlowRoutePath and will break a route starting with basename
|
||||||
|
{ pathname: basename + location.pathname, search: location.search },
|
||||||
|
{
|
||||||
|
prevent,
|
||||||
|
redirect,
|
||||||
|
continue() {
|
||||||
|
fireNavigated(location.pathname, location.search);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then((result: unknown) => {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<output ref={ref} style={{ display: 'none' }} />
|
||||||
|
{portals}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Flow.type = 'FlowContainer'; // This is for copilot to recognize this
|
||||||
|
|
||||||
|
export const serverSideRoutes = [{ path: '/*', element: <Flow /> }];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the script for an exported WebComponent with the given tag
|
||||||
|
*
|
||||||
|
* @param tag name of the exported web-component to load
|
||||||
|
*
|
||||||
|
* @returns Promise(resolve, reject) that is fulfilled on script load
|
||||||
|
*/
|
||||||
|
export const loadComponentScript = (tag: String): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `/web-component/${tag}.js`;
|
||||||
|
script.onload = function () {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = function (err) {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(script);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Properties {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load WebComponent script and create a React element for the WebComponent.
|
||||||
|
*
|
||||||
|
* @param tag custom web-component tag name.
|
||||||
|
* @param props optional Properties object to create element attributes with
|
||||||
|
* @param onload optional callback to be called for script onload
|
||||||
|
* @param onerror optional callback for error loading the script
|
||||||
|
*/
|
||||||
|
export const reactElement = (tag: string, props?: Properties, onload?: () => void, onerror?: (err: any) => void) => {
|
||||||
|
loadComponentScript(tag).then(
|
||||||
|
() => onload?.(),
|
||||||
|
(err) => {
|
||||||
|
if (onerror) {
|
||||||
|
onerror(err);
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to load script for ${tag}.`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props) {
|
||||||
|
return React.createElement(tag, props);
|
||||||
|
}
|
||||||
|
return React.createElement(tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// @ts-ignore
|
||||||
|
import.meta.hot.accept((newModule) => {
|
||||||
|
// A hot module replace for Flow.tsx happens when any JS/TS imported through @JsModule
|
||||||
|
// or similar is updated because this updates generated-flow-imports.js and that in turn
|
||||||
|
// is imported by this file. We have no means of hot replacing those files, e.g. some
|
||||||
|
// custom lit element so we need to reload the page. */
|
||||||
|
if (newModule) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
import { createRoot, Root } from 'react-dom/client';
|
||||||
|
import { createElement, type Dispatch, type ReactElement, type ReactNode, useEffect, useReducer } from 'react';
|
||||||
|
|
||||||
|
type FlowStateKeyChangedAction<K extends string, V> = Readonly<{
|
||||||
|
type: 'stateKeyChanged';
|
||||||
|
key: K;
|
||||||
|
value: V;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type FlowStateReducerAction = FlowStateKeyChangedAction<string, unknown>;
|
||||||
|
|
||||||
|
function stateReducer<S extends Readonly<Record<string, unknown>>>(state: S, action: FlowStateReducerAction): S {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'stateKeyChanged':
|
||||||
|
const { value } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
key: value
|
||||||
|
} as S;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispatchEvent<T> = T extends undefined ? () => boolean : (value: T) => boolean;
|
||||||
|
|
||||||
|
const emptyAction: Dispatch<unknown> = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with APIs exposed for using in the {@link ReactAdapterElement#render}
|
||||||
|
* implementation.
|
||||||
|
*/
|
||||||
|
export type RenderHooks = {
|
||||||
|
/**
|
||||||
|
* A hook API for using stateful JS properties of the Web Component from
|
||||||
|
* the React `render()`.
|
||||||
|
*
|
||||||
|
* @typeParam T - Type of the state value
|
||||||
|
*
|
||||||
|
* @param key - Web Component property name, which is used for two-way
|
||||||
|
* value propagation from the server and back.
|
||||||
|
* @param initialValue - Fallback initial value (optional). Only applies if
|
||||||
|
* the Java component constructor does not invoke `setState`.
|
||||||
|
* @returns A tuple with two values:
|
||||||
|
* 1. The current state.
|
||||||
|
* 2. The `set` function for changing the state and triggering render
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
readonly useState: ReactAdapterElement['useState'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to simplify dispatching a `CustomEvent` on the Web
|
||||||
|
* Component from React.
|
||||||
|
*
|
||||||
|
* @typeParam T - The type for `event.detail` value (optional).
|
||||||
|
*
|
||||||
|
* @param type - The `CustomEvent` type string.
|
||||||
|
* @param options - The settings for the `CustomEvent`.
|
||||||
|
* @returns The `dispatch` function. The function parameters change
|
||||||
|
* depending on the `T` generic type:
|
||||||
|
* - For `undefined` type (default), has no parameters.
|
||||||
|
* - For other types, has one parameter for the `event.detail` value of that type.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
readonly useCustomEvent: ReactAdapterElement['useCustomEvent'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to generate the content element with name attribute to bind
|
||||||
|
* the server-side Flow element for this component.
|
||||||
|
*
|
||||||
|
* This is used together with {@link ReactAdapterComponent::getContentElement}
|
||||||
|
* to have server-side component attach to the correct client element.
|
||||||
|
*
|
||||||
|
* Usage as follows:
|
||||||
|
*
|
||||||
|
* const content = hooks.useContent('content');
|
||||||
|
* return <>
|
||||||
|
* {content}
|
||||||
|
* </>;
|
||||||
|
*
|
||||||
|
* Note! Not adding the 'content' element into the dom will have the
|
||||||
|
* server throw a IllegalStateException for element with tag name not found.
|
||||||
|
*
|
||||||
|
* @param name - The name attribute of the element
|
||||||
|
*/
|
||||||
|
readonly useContent: ReactAdapterElement['useContent'];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ReadyCallbackFunction {
|
||||||
|
(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for Web Components that render using React. Enables creating
|
||||||
|
* adapters for integrating React components with Flow. Intended for use with
|
||||||
|
* `ReactAdapterComponent` Flow Java class.
|
||||||
|
*/
|
||||||
|
export abstract class ReactAdapterElement extends HTMLElement {
|
||||||
|
#root: Root | undefined = undefined;
|
||||||
|
#rootRendered: boolean = false;
|
||||||
|
#rendering: ReactNode | undefined = undefined;
|
||||||
|
|
||||||
|
#state: Record<string, unknown> = Object.create(null);
|
||||||
|
#stateSetters = new Map<string, Dispatch<unknown>>();
|
||||||
|
#customEvents = new Map<string, DispatchEvent<unknown>>();
|
||||||
|
#dispatchFlowState: Dispatch<FlowStateReducerAction> = emptyAction;
|
||||||
|
|
||||||
|
#readyCallback = new Map<string, ReadyCallbackFunction>();
|
||||||
|
|
||||||
|
readonly #renderHooks: RenderHooks;
|
||||||
|
|
||||||
|
readonly #Wrapper: () => ReactElement | null;
|
||||||
|
|
||||||
|
#unmounting?: Promise<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#renderHooks = {
|
||||||
|
useState: this.useState.bind(this),
|
||||||
|
useCustomEvent: this.useCustomEvent.bind(this),
|
||||||
|
useContent: this.useContent.bind(this)
|
||||||
|
};
|
||||||
|
this.#Wrapper = this.#renderWrapper.bind(this);
|
||||||
|
this.#markAsUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectedCallback() {
|
||||||
|
this.#rendering = createElement(this.#Wrapper);
|
||||||
|
const createNewRoot = this.dispatchEvent(
|
||||||
|
new CustomEvent('flow-portal-add', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
children: this.#rendering,
|
||||||
|
domNode: this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createNewRoot || this.#root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#unmounting;
|
||||||
|
|
||||||
|
this.#root = createRoot(this);
|
||||||
|
this.#maybeRenderRoot();
|
||||||
|
this.#root.render(this.#rendering);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback for specified element identifier to be called when
|
||||||
|
* react element is ready.
|
||||||
|
* <p>
|
||||||
|
* For internal use only. May be renamed or removed in a future release.
|
||||||
|
*
|
||||||
|
* @param id element identifier that callback is for
|
||||||
|
* @param readyCallback callback method to be informed on element ready state
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public addReadyCallback(id: string, readyCallback: ReadyCallbackFunction) {
|
||||||
|
this.#readyCallback.set(id, readyCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disconnectedCallback() {
|
||||||
|
if (!this.#root) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('flow-portal-remove', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
children: this.#rendering,
|
||||||
|
domNode: this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.#unmounting = Promise.resolve();
|
||||||
|
await this.#unmounting;
|
||||||
|
this.#root.unmount();
|
||||||
|
this.#root = undefined;
|
||||||
|
}
|
||||||
|
this.#rootRendered = false;
|
||||||
|
this.#rendering = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook API for using stateful JS properties of the Web Component from
|
||||||
|
* the React `render()`.
|
||||||
|
*
|
||||||
|
* @typeParam T - Type of the state value
|
||||||
|
*
|
||||||
|
* @param key - Web Component property name, which is used for two-way
|
||||||
|
* value propagation from the server and back.
|
||||||
|
* @param initialValue - Fallback initial value (optional). Only applies if
|
||||||
|
* the Java component constructor does not invoke `setState`.
|
||||||
|
* @returns A tuple with two values:
|
||||||
|
* 1. The current state.
|
||||||
|
* 2. The `set` function for changing the state and triggering render
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useState<T>(key: string, initialValue?: T): [value: T, setValue: Dispatch<T>] {
|
||||||
|
if (this.#stateSetters.has(key)) {
|
||||||
|
return [this.#state[key] as T, this.#stateSetters.get(key)!];
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = ((this as Record<string, unknown>)[key] as T) ?? initialValue!;
|
||||||
|
this.#state[key] = value;
|
||||||
|
Object.defineProperty(this, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get(): T {
|
||||||
|
return this.#state[key];
|
||||||
|
},
|
||||||
|
set(nextValue: T) {
|
||||||
|
this.#state[key] = nextValue;
|
||||||
|
this.#dispatchFlowState({ type: 'stateKeyChanged', key, value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatchChangedEvent = this.useCustomEvent<{ value: T }>(`${key}-changed`, { detail: { value } });
|
||||||
|
const setValue = (value: T) => {
|
||||||
|
this.#state[key] = value;
|
||||||
|
dispatchChangedEvent({ value });
|
||||||
|
this.#dispatchFlowState({ type: 'stateKeyChanged', key, value });
|
||||||
|
};
|
||||||
|
this.#stateSetters.set(key, setValue as Dispatch<unknown>);
|
||||||
|
return [value, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to simplify dispatching a `CustomEvent` on the Web
|
||||||
|
* Component from React.
|
||||||
|
*
|
||||||
|
* @typeParam T - The type for `event.detail` value (optional).
|
||||||
|
*
|
||||||
|
* @param type - The `CustomEvent` type string.
|
||||||
|
* @param options - The settings for the `CustomEvent`.
|
||||||
|
* @returns The `dispatch` function. The function parameters change
|
||||||
|
* depending on the `T` generic type:
|
||||||
|
* - For `undefined` type (default), has no parameters.
|
||||||
|
* - For other types, has one parameter for the `event.detail` value of that type.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useCustomEvent<T = undefined>(type: string, options: CustomEventInit<T> = {}): DispatchEvent<T> {
|
||||||
|
if (!this.#customEvents.has(type)) {
|
||||||
|
const dispatch = ((detail?: T) => {
|
||||||
|
const eventInitDict =
|
||||||
|
detail === undefined
|
||||||
|
? options
|
||||||
|
: {
|
||||||
|
...options,
|
||||||
|
detail
|
||||||
|
};
|
||||||
|
const event = new CustomEvent(type, eventInitDict);
|
||||||
|
return this.dispatchEvent(event);
|
||||||
|
}) as DispatchEvent<T>;
|
||||||
|
this.#customEvents.set(type, dispatch as DispatchEvent<unknown>);
|
||||||
|
return dispatch;
|
||||||
|
}
|
||||||
|
return this.#customEvents.get(type)! as DispatchEvent<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Web Component render function. To be implemented by users with React.
|
||||||
|
*
|
||||||
|
* @param hooks - the adapter APIs exposed for the implementation.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected abstract render(hooks: RenderHooks): ReactElement | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare content container for Flow to bind server Element to.
|
||||||
|
*
|
||||||
|
* @param name container name attribute matching server name attribute
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useContent(name: string): ReactElement | null {
|
||||||
|
useEffect(() => {
|
||||||
|
this.#readyCallback.get(name)?.();
|
||||||
|
}, []);
|
||||||
|
return createElement('flow-content-container', { name, style: { display: 'contents' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
#maybeRenderRoot() {
|
||||||
|
if (this.#rootRendered || !this.#root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#root.render(createElement(this.#Wrapper));
|
||||||
|
this.#rootRendered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderWrapper(): ReactElement | null {
|
||||||
|
const [state, dispatchFlowState] = useReducer(stateReducer, this.#state);
|
||||||
|
this.#state = state;
|
||||||
|
this.#dispatchFlowState = dispatchFlowState;
|
||||||
|
return this.render(this.#renderHooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#markAsUsed(): void {
|
||||||
|
// @ts-ignore
|
||||||
|
let vaadinObject = window.Vaadin || {};
|
||||||
|
// @ts-ignore
|
||||||
|
if (vaadinObject.developmentMode) {
|
||||||
|
vaadinObject.registrations = vaadinObject.registrations || [];
|
||||||
|
vaadinObject.registrations.push({
|
||||||
|
is: 'ReactAdapterElement',
|
||||||
|
version: '25.2.6'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import '@vaadin/field-highlighter/src/vaadin-field-highlighter.js';
|
||||||
|
import '@vaadin/common-frontend/ConnectionIndicator.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import '@vaadin/avatar-group/src/vaadin-avatar-group.js';
|
||||||
|
import '@vaadin/badge/src/vaadin-badge.js';
|
||||||
|
import '@vaadin/breadcrumbs/src/vaadin-breadcrumbs-item.js';
|
||||||
|
import '@vaadin/card/src/vaadin-card.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/checkbox-group/src/vaadin-checkbox-group.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
|
import '@vaadin/confirm-dialog/src/vaadin-confirm-dialog.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/custom-field/src/vaadin-custom-field.js';
|
||||||
|
import '@vaadin/date-picker/src/vaadin-date-picker.js';
|
||||||
|
import 'Frontend/generated/jar-resources/datepickerConnector.js';
|
||||||
|
import '@vaadin/date-time-picker/src/vaadin-date-time-picker.js';
|
||||||
|
import '@vaadin/time-picker/src/vaadin-time-picker.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-time-picker/timepickerConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-time-picker/helpers.js';
|
||||||
|
import '@vaadin/dialog/src/vaadin-dialog.js';
|
||||||
|
import 'Frontend/generated/jar-resources/dndConnector.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-form.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-overlay.js';
|
||||||
|
import '@vaadin/markdown/src/vaadin-markdown.js';
|
||||||
|
import '@vaadin/master-detail-layout/src/vaadin-master-detail-layout.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/message-input/src/vaadin-message-input.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/popover/src/vaadin-popover.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-popover/popover.ts';
|
||||||
|
import '@vaadin/progress-bar/src/vaadin-progress-bar.js';
|
||||||
|
import '@vaadin/radio-group/src/vaadin-radio-button.js';
|
||||||
|
import '@vaadin/radio-group/src/vaadin-radio-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/ReactRouterOutletElement.tsx';
|
||||||
|
import '@vaadin/select/src/vaadin-select.js';
|
||||||
|
import 'Frontend/generated/jar-resources/selectConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/tooltip.ts';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/slider/src/vaadin-range-slider.js';
|
||||||
|
import '@vaadin/slider/src/vaadin-slider.js';
|
||||||
|
import '@vaadin/split-layout/src/vaadin-split-layout.js';
|
||||||
|
import '@vaadin/tabs/src/vaadin-tab.js';
|
||||||
|
import '@vaadin/tabsheet/src/vaadin-tabsheet.js';
|
||||||
|
import '@vaadin/tabs/src/vaadin-tabs.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-big-decimal-field.js';
|
||||||
|
import '@vaadin/email-field/src/vaadin-email-field.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/number-field/src/vaadin-number-field.js';
|
||||||
|
import '@vaadin/password-field/src/vaadin-password-field.js';
|
||||||
|
import '@vaadin/text-area/src/vaadin-text-area.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-tree-toggle.js';
|
||||||
|
import 'Frontend/generated/jar-resources/treeGridConnector.ts';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-button.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-drop-zone.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-file-list.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/virtual-list/src/vaadin-virtual-list.js';
|
||||||
|
import 'Frontend/generated/jar-resources/virtualListConnector.js';
|
||||||
|
import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
|
||||||
|
const loadOnDemand = (key) => { return Promise.resolve(0); }
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
window.Vaadin.Flow.loadOnDemand = loadOnDemand;
|
||||||
|
window.Vaadin.Flow.resetFocus = () => {
|
||||||
|
let ae=document.activeElement;
|
||||||
|
while(ae&&ae.shadowRoot) ae = ae.shadowRoot.activeElement;
|
||||||
|
return !ae || ae.blur() || ae.focus() || true;
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
import { injectGlobalWebcomponentCss } from 'Frontend/generated/jar-resources/theme-util.js';
|
||||||
|
|
||||||
|
import '@vaadin/field-highlighter/src/vaadin-field-highlighter.js';
|
||||||
|
import '@vaadin/common-frontend/ConnectionIndicator.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import '@vaadin/avatar-group/src/vaadin-avatar-group.js';
|
||||||
|
import '@vaadin/badge/src/vaadin-badge.js';
|
||||||
|
import '@vaadin/breadcrumbs/src/vaadin-breadcrumbs-item.js';
|
||||||
|
import '@vaadin/card/src/vaadin-card.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/checkbox-group/src/vaadin-checkbox-group.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
|
import '@vaadin/confirm-dialog/src/vaadin-confirm-dialog.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/custom-field/src/vaadin-custom-field.js';
|
||||||
|
import '@vaadin/date-picker/src/vaadin-date-picker.js';
|
||||||
|
import 'Frontend/generated/jar-resources/datepickerConnector.js';
|
||||||
|
import '@vaadin/date-time-picker/src/vaadin-date-time-picker.js';
|
||||||
|
import '@vaadin/time-picker/src/vaadin-time-picker.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-time-picker/timepickerConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-time-picker/helpers.js';
|
||||||
|
import '@vaadin/dialog/src/vaadin-dialog.js';
|
||||||
|
import 'Frontend/generated/jar-resources/dndConnector.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-form.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-overlay.js';
|
||||||
|
import '@vaadin/markdown/src/vaadin-markdown.js';
|
||||||
|
import '@vaadin/master-detail-layout/src/vaadin-master-detail-layout.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/message-input/src/vaadin-message-input.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/popover/src/vaadin-popover.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-popover/popover.ts';
|
||||||
|
import '@vaadin/progress-bar/src/vaadin-progress-bar.js';
|
||||||
|
import '@vaadin/radio-group/src/vaadin-radio-button.js';
|
||||||
|
import '@vaadin/radio-group/src/vaadin-radio-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/ReactRouterOutletElement.tsx';
|
||||||
|
import '@vaadin/select/src/vaadin-select.js';
|
||||||
|
import 'Frontend/generated/jar-resources/selectConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/tooltip.ts';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/slider/src/vaadin-range-slider.js';
|
||||||
|
import '@vaadin/slider/src/vaadin-slider.js';
|
||||||
|
import '@vaadin/split-layout/src/vaadin-split-layout.js';
|
||||||
|
import '@vaadin/tabs/src/vaadin-tab.js';
|
||||||
|
import '@vaadin/tabsheet/src/vaadin-tabsheet.js';
|
||||||
|
import '@vaadin/tabs/src/vaadin-tabs.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-big-decimal-field.js';
|
||||||
|
import '@vaadin/email-field/src/vaadin-email-field.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/number-field/src/vaadin-number-field.js';
|
||||||
|
import '@vaadin/password-field/src/vaadin-password-field.js';
|
||||||
|
import '@vaadin/text-area/src/vaadin-text-area.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-tree-toggle.js';
|
||||||
|
import 'Frontend/generated/jar-resources/treeGridConnector.ts';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-button.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-drop-zone.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload-file-list.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/virtual-list/src/vaadin-virtual-list.js';
|
||||||
|
import 'Frontend/generated/jar-resources/virtualListConnector.js';
|
||||||
|
import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
|
||||||
|
const loadOnDemand = (key) => { return Promise.resolve(0); }
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
window.Vaadin.Flow.loadOnDemand = loadOnDemand;
|
||||||
|
window.Vaadin.Flow.resetFocus = () => {
|
||||||
|
let ae=document.activeElement;
|
||||||
|
while(ae&&ae.shadowRoot) ae = ae.shadowRoot.activeElement;
|
||||||
|
return !ae || ae.blur() || ae.focus() || true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
app-shell-imports.d.ts
|
||||||
|
app-shell-imports.js
|
||||||
|
css.generated.d.ts
|
||||||
|
flow/Flow.tsx
|
||||||
|
flow/ReactAdapter.tsx
|
||||||
|
flow/generated-flow-imports.d.ts
|
||||||
|
flow/generated-flow-imports.js
|
||||||
|
flow/generated-flow-webcomponent-imports.js
|
||||||
|
index.tsx
|
||||||
|
jar-resources/Clipboard.d.ts
|
||||||
|
jar-resources/Clipboard.js
|
||||||
|
jar-resources/Clipboard.js.map
|
||||||
|
jar-resources/Download.d.ts
|
||||||
|
jar-resources/Download.js
|
||||||
|
jar-resources/Download.js.map
|
||||||
|
jar-resources/ElementResize.d.ts
|
||||||
|
jar-resources/ElementResize.js
|
||||||
|
jar-resources/ElementResize.js.map
|
||||||
|
jar-resources/Flow.d.ts
|
||||||
|
jar-resources/Flow.js
|
||||||
|
jar-resources/Flow.js.map
|
||||||
|
jar-resources/FlowBootstrap.d.ts
|
||||||
|
jar-resources/FlowBootstrap.js
|
||||||
|
jar-resources/FlowClient.d.ts
|
||||||
|
jar-resources/FlowClient.js
|
||||||
|
jar-resources/FlowShortcut.js
|
||||||
|
jar-resources/Fullscreen.d.ts
|
||||||
|
jar-resources/Fullscreen.js
|
||||||
|
jar-resources/Fullscreen.js.map
|
||||||
|
jar-resources/Geolocation.d.ts
|
||||||
|
jar-resources/Geolocation.js
|
||||||
|
jar-resources/Geolocation.js.map
|
||||||
|
jar-resources/PageVisibility.d.ts
|
||||||
|
jar-resources/PageVisibility.js
|
||||||
|
jar-resources/PageVisibility.js.map
|
||||||
|
jar-resources/ReactRouterOutletElement.tsx
|
||||||
|
jar-resources/ScreenOrientation.d.ts
|
||||||
|
jar-resources/ScreenOrientation.js
|
||||||
|
jar-resources/ScreenOrientation.js.map
|
||||||
|
jar-resources/WakeLock.d.ts
|
||||||
|
jar-resources/WakeLock.js
|
||||||
|
jar-resources/WakeLock.js.map
|
||||||
|
jar-resources/WebShare.d.ts
|
||||||
|
jar-resources/WebShare.js
|
||||||
|
jar-resources/WebShare.js.map
|
||||||
|
jar-resources/comboBoxConnector.js
|
||||||
|
jar-resources/contextMenuConnector.js
|
||||||
|
jar-resources/contextMenuTargetConnector.js
|
||||||
|
jar-resources/copilot-version.js
|
||||||
|
jar-resources/copilot.d.ts
|
||||||
|
jar-resources/copilot.js
|
||||||
|
jar-resources/copilot/base-panel-Fr0D1ZcU.js
|
||||||
|
jar-resources/copilot/chunk-DiqZc92J.js
|
||||||
|
jar-resources/copilot/consts-CSALuSsm.js
|
||||||
|
jar-resources/copilot/copilot-development-setup-user-guide-Db31eO1T.js
|
||||||
|
jar-resources/copilot/copilot-development-setup-user-guide-utils-DzEVQbWO.js
|
||||||
|
jar-resources/copilot/copilot-devtools-CYwy4U79.js
|
||||||
|
jar-resources/copilot/copilot-error-handler-9OpssAH1.js
|
||||||
|
jar-resources/copilot/copilot-features-plugin-DwQSwtbQ.js
|
||||||
|
jar-resources/copilot/copilot-feedback-plugin-JMYrBCmQ.js
|
||||||
|
jar-resources/copilot/copilot-focus-trap-CaZw1c70.js
|
||||||
|
jar-resources/copilot/copilot-global-vars-later-CWkvR40X.js
|
||||||
|
jar-resources/copilot/copilot-impersonator-plugin-iN25IekB.js
|
||||||
|
jar-resources/copilot/copilot-info-plugin-9l6uSELy.js
|
||||||
|
jar-resources/copilot/copilot-init-step2-tqpZOWcn.js
|
||||||
|
jar-resources/copilot/copilot-log-plugin-CmwIHcBw.js
|
||||||
|
jar-resources/copilot/copilot-message-box-CVAh5PSs.js
|
||||||
|
jar-resources/copilot/copilot-modes-wJyMqHUb.js
|
||||||
|
jar-resources/copilot/copilot-notification-CCNJdNg4.js
|
||||||
|
jar-resources/copilot/copilot-notification-UcomqPI8.js
|
||||||
|
jar-resources/copilot/copilot-server-communicator-impl-B7YDzJpM.js
|
||||||
|
jar-resources/copilot/copilot-settings-panel-qUN2f6RH.js
|
||||||
|
jar-resources/copilot/copilot-shortcuts-BzZuUtjW.js
|
||||||
|
jar-resources/copilot/copilot-stored-machine-state-D6qB_Peh.js
|
||||||
|
jar-resources/copilot/copilot-tree-impl-DxBvMTRa.js
|
||||||
|
jar-resources/copilot/copilot-ui-state-Dc6l_5DA.js
|
||||||
|
jar-resources/copilot/copilot-userinfo-C0s6T_kB.js
|
||||||
|
jar-resources/copilot/copilot-vaadin-versions-CkxDkDmp.js
|
||||||
|
jar-resources/copilot/copy-to-clipboard-4Y12mBRr.js
|
||||||
|
jar-resources/copilot/directive-DWLihZIi.js
|
||||||
|
jar-resources/copilot/directive-helpers-BTt8P8-5.js
|
||||||
|
jar-resources/copilot/dom-utils-Cuv93-tQ.js
|
||||||
|
jar-resources/copilot/early-project-state-LGwavSyI.js
|
||||||
|
jar-resources/copilot/figma-public/figma-api.d.ts
|
||||||
|
jar-resources/copilot/icons-CwakCZgK.js
|
||||||
|
jar-resources/copilot/lit-renderer-fa_B9boC.js
|
||||||
|
jar-resources/copilot/section-panel-ui-state-hOj_RfX_.js
|
||||||
|
jar-resources/copilot/shared/copilot-plugin-support.d.ts
|
||||||
|
jar-resources/copilot/shared/flow-utils.d.ts
|
||||||
|
jar-resources/copilot/stats-CRkPKCLQ.js
|
||||||
|
jar-resources/copilot/track-active-mode-event-DkX0nsC6.js
|
||||||
|
jar-resources/copilot/typescript-BkEBjsia.js
|
||||||
|
jar-resources/datepickerConnector.js
|
||||||
|
jar-resources/disableOnClickFunctions.js
|
||||||
|
jar-resources/dndConnector.js
|
||||||
|
jar-resources/flow-component-directive.js
|
||||||
|
jar-resources/flow-component-renderer.js
|
||||||
|
jar-resources/gridConnector.ts
|
||||||
|
jar-resources/index.d.ts
|
||||||
|
jar-resources/index.js
|
||||||
|
jar-resources/index.js.map
|
||||||
|
jar-resources/lit-renderer.ts
|
||||||
|
jar-resources/menubarConnector.js
|
||||||
|
jar-resources/messageListConnector.js
|
||||||
|
jar-resources/selectConnector.js
|
||||||
|
jar-resources/theme-util.js
|
||||||
|
jar-resources/tooltip.ts
|
||||||
|
jar-resources/treeGridConnector.ts
|
||||||
|
jar-resources/vaadin-big-decimal-field.js
|
||||||
|
jar-resources/vaadin-dev-tools/License.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/connection.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/hotswap-scroll.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/live-reload-connection.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/pre-trial-splash-screen.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/vaadin-dev-tools.d.ts
|
||||||
|
jar-resources/vaadin-dev-tools/vaadin-dev-tools.js
|
||||||
|
jar-resources/vaadin-dev-tools/vaadin-dev-tools.js.map
|
||||||
|
jar-resources/vaadin-dev-tools/websocket-connection.d.ts
|
||||||
|
jar-resources/vaadin-grid-flow-selection-column.js
|
||||||
|
jar-resources/vaadin-popover/popover.ts
|
||||||
|
jar-resources/vaadin-time-picker/helpers.js
|
||||||
|
jar-resources/vaadin-time-picker/timepickerConnector.js
|
||||||
|
jar-resources/vaadin-upload-manager-connector.ts
|
||||||
|
jar-resources/virtualListConnector.js
|
||||||
|
jsx-dev-transform/index.ts
|
||||||
|
jsx-dev-transform/jsx-dev-runtime.ts
|
||||||
|
jsx-dev-transform/jsx-runtime.ts
|
||||||
|
layouts.json
|
||||||
|
routes.tsx
|
||||||
|
vaadin-featureflags.js
|
||||||
|
vaadin-react.tsx
|
||||||
|
vaadin.ts
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* This file is auto-generated by Vaadin.
|
||||||
|
* If you want to customize the entry point, you can copy this file or create
|
||||||
|
* your own `index.tsx` in your frontend directory.
|
||||||
|
* By default, the `index.tsx` file should be in `./frontend/` folder.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* - You need to restart the dev-server after adding the new `index.tsx` file.
|
||||||
|
* After that, all modifications to `index.tsx` are recompiled automatically.
|
||||||
|
* - `index.js` is also supported if you don't want to use TypeScript.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { RouterProvider } from 'react-router';
|
||||||
|
import { router } from 'Frontend/generated/routes.js';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outlet = document.getElementById('outlet')!;
|
||||||
|
let root = (outlet as any)._root ?? createRoot(outlet);
|
||||||
|
(outlet as any)._root = root;
|
||||||
|
root.render(createElement(App));
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Reads the first item from the system clipboard and returns its text/plain
|
||||||
|
* and text/html representations. Either field is {@code null} if the
|
||||||
|
* corresponding MIME type is not present.
|
||||||
|
*
|
||||||
|
* The caller is expected to be inside a transient user gesture and to have
|
||||||
|
* been granted the {@code clipboard-read} permission; otherwise
|
||||||
|
* {@code navigator.clipboard.read} rejects and this function propagates the
|
||||||
|
* rejection.
|
||||||
|
*/
|
||||||
|
async function readClipboardPayload() {
|
||||||
|
const items = await navigator.clipboard.read();
|
||||||
|
if (!items.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const item = items[0];
|
||||||
|
const get = async (type) => item.types.includes(type) ? (await item.getType(type)).text() : null;
|
||||||
|
return {
|
||||||
|
text: await get('text/plain'),
|
||||||
|
html: await get('text/html')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Re-encodes the given {@code <img>} as {@code image/png} via a canvas
|
||||||
|
* round-trip. The source can be any rasterisable format the browser already
|
||||||
|
* decodes ({@code image/png}, {@code image/jpeg}, {@code image/svg+xml}, ...);
|
||||||
|
* the output is always a {@code Promise<Blob>} of {@code image/png}, the only
|
||||||
|
* image MIME type every browser's asynchronous Clipboard API accepts on write.
|
||||||
|
*
|
||||||
|
* Cross-origin images need {@code crossorigin="anonymous"} on the {@code <img>}
|
||||||
|
* plus matching CORS headers, otherwise the canvas is tainted and
|
||||||
|
* {@code toBlob} throws.
|
||||||
|
*/
|
||||||
|
function imageToPngBlob(img) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const draw = () => {
|
||||||
|
try {
|
||||||
|
const width = img.naturalWidth || img.width;
|
||||||
|
const height = img.naturalHeight || img.height;
|
||||||
|
if (!width || !height) {
|
||||||
|
reject(new Error('image has no intrinsic size'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('2D canvas context not available'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
canvas.toBlob((png) => (png ? resolve(png) : reject(new Error('canvas.toBlob returned null'))), 'image/png');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (img.complete) {
|
||||||
|
// `complete` is also true for an image that already failed to load or has
|
||||||
|
// an empty src; those have naturalWidth === 0 and their load/error events
|
||||||
|
// have already fired and will never fire again, so we must settle here
|
||||||
|
// rather than wait for an event that never comes.
|
||||||
|
if (img.naturalWidth > 0) {
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(new Error('image failed to load or has empty src'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
img.addEventListener('load', draw, { once: true });
|
||||||
|
img.addEventListener('error', () => reject(new Error('image load failed')), { once: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Writes any combination of text/plain, text/html and image/png to the system
|
||||||
|
* clipboard as a single ClipboardItem. Any argument may be {@code null} to omit
|
||||||
|
* that MIME type; at least one is expected to be non-null (the caller enforces
|
||||||
|
* this). The image argument is the source {@code <img>}; it is re-encoded as
|
||||||
|
* {@code image/png} via {@link imageToPngBlob} and the resulting
|
||||||
|
* {@code Promise<Blob>} is fed directly to {@code ClipboardItem} so the
|
||||||
|
* {@code navigator.clipboard.write} call stays synchronous inside the user
|
||||||
|
* gesture (Safari otherwise loses activation on the first await).
|
||||||
|
*
|
||||||
|
* The caller is expected to be inside a transient user gesture; otherwise
|
||||||
|
* {@code navigator.clipboard.write} rejects and this function propagates the
|
||||||
|
* rejection.
|
||||||
|
*
|
||||||
|
* Resolves with the {@code text/plain} value if present, otherwise with the
|
||||||
|
* {@code text/html} value, otherwise with {@code null} (image-only case).
|
||||||
|
*/
|
||||||
|
async function writeClipboardPayload(text, html, image) {
|
||||||
|
const entries = {};
|
||||||
|
if (text !== null) {
|
||||||
|
entries['text/plain'] = text;
|
||||||
|
}
|
||||||
|
if (html !== null) {
|
||||||
|
entries['text/html'] = html;
|
||||||
|
}
|
||||||
|
if (image !== null) {
|
||||||
|
entries['image/png'] = imageToPngBlob(image);
|
||||||
|
}
|
||||||
|
await navigator.clipboard.write([new ClipboardItem(entries)]);
|
||||||
|
return text !== null ? text : html;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Posts each file from a {@code paste} event's {@code clipboardData.files} as
|
||||||
|
* its own XHR to the URL stored as the named attribute on {@code element}. The
|
||||||
|
* wire format matches vaadin-upload: raw body, percent-encoded {@code X-Filename}
|
||||||
|
* header, MIME type in {@code Content-Type}.
|
||||||
|
*
|
||||||
|
* Each upload is processed in its own HTTP request, so the UI changes the
|
||||||
|
* server-side UploadHandler makes through {@code UI.access} are applied to the
|
||||||
|
* state tree but not sent to the client by the upload response itself. Once
|
||||||
|
* every upload of the paste has settled this helper dispatches a
|
||||||
|
* {@code vaadin-paste-upload-finished} event back on {@code element}; a
|
||||||
|
* server-side listener for that event triggers a normal Flow round trip that
|
||||||
|
* flushes those pending UI changes — so the API works without {@code @Push},
|
||||||
|
* exactly like a regular upload completing through the Upload component.
|
||||||
|
*
|
||||||
|
* Editable targets ({@code <input>}, {@code <textarea>}, {@code contentEditable})
|
||||||
|
* are not given any special treatment here: browsers do not paste files into
|
||||||
|
* those elements, so a paste containing a file in a focused text field is
|
||||||
|
* still a "the user tried to drop a file on the page" event from the
|
||||||
|
* application's point of view.
|
||||||
|
*/
|
||||||
|
// Monotonic counter incremented once per paste gesture so server-side
|
||||||
|
// handlers can correlate the parallel fetch POSTs that belong to the same
|
||||||
|
// paste, and order pastes against each other. Scoped to the browser tab —
|
||||||
|
// a different tab gets its own counter, but no server-side state crosses
|
||||||
|
// tabs in this flow.
|
||||||
|
let pasteSequence = 0;
|
||||||
|
function uploadPastedFiles(event, element, urlAttribute) {
|
||||||
|
const files = event.clipboardData?.files;
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = element.getAttribute(urlAttribute);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pasteSequence += 1;
|
||||||
|
const pasteId = String(pasteSequence);
|
||||||
|
// Surface the file count too: the batch server handler needs it to know
|
||||||
|
// when the paste has been fully delivered (one fetch per file means the
|
||||||
|
// server only observes arrivals, not the total).
|
||||||
|
const fileCount = String(files.length);
|
||||||
|
const uploads = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const headers = {
|
||||||
|
'X-Filename': encodeURIComponent(file.name),
|
||||||
|
'X-Paste-Id': pasteId,
|
||||||
|
'X-Paste-File-Count': fileCount
|
||||||
|
};
|
||||||
|
if (file.type) {
|
||||||
|
headers['Content-Type'] = file.type;
|
||||||
|
}
|
||||||
|
// The per-file UploadHandler callback runs as each POST is processed;
|
||||||
|
// log network/connectivity failures the server will never see otherwise.
|
||||||
|
uploads.push(fetch(url, { method: 'POST', headers: headers, body: file }).catch((err) => {
|
||||||
|
console.error('Vaadin clipboard file upload failed', err);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// Tell the server the paste's uploads are done so it can flush the queued
|
||||||
|
// UI updates without requiring @Push. The upload response is written only
|
||||||
|
// after the handler's UI.access task has applied its changes to the state
|
||||||
|
// tree, so by the time a fetch settles those changes are guaranteed to be
|
||||||
|
// picked up by this round trip.
|
||||||
|
Promise.allSettled(uploads).then(() => {
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-paste-upload-finished'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.clipboard = {
|
||||||
|
readPayload: readClipboardPayload,
|
||||||
|
writePayload: writeClipboardPayload,
|
||||||
|
uploadPastedFiles: uploadPastedFiles
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Clipboard.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Triggers a file download from the given URL using the standard
|
||||||
|
* <a href download> click pattern.
|
||||||
|
*
|
||||||
|
* The anchor is synthesised, clicked synchronously inside the caller's
|
||||||
|
* gesture context, and removed. The browser then either navigates to the
|
||||||
|
* URL (server responds with Content-Disposition: attachment) or saves the
|
||||||
|
* resource directly when the download attribute applies.
|
||||||
|
*
|
||||||
|
* The {@code download} attribute is honoured only for same-origin URLs;
|
||||||
|
* cross-origin responses must set Content-Disposition themselves for the
|
||||||
|
* filename to take effect.
|
||||||
|
*/
|
||||||
|
function startDownload(url, filename) {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
// Always set `download` so the browser saves the response rather than
|
||||||
|
// navigating to it. Empty value lets the browser pick the filename from
|
||||||
|
// Content-Disposition or the URL pathname; a non-empty value is the
|
||||||
|
// suggested filename (honoured only same-origin). Cross-origin responses
|
||||||
|
// without Content-Disposition: attachment still navigate — that's a
|
||||||
|
// server-side concern this client helper can't override.
|
||||||
|
a.download = filename ?? '';
|
||||||
|
// Opt out of Vaadin's client-side router so the click reaches the
|
||||||
|
// browser's native download handling instead of being intercepted as an
|
||||||
|
// in-app navigation. Matches Anchor.setHref(DownloadHandler).
|
||||||
|
a.setAttribute('router-ignore', '');
|
||||||
|
// Hidden but in the document — some browsers ignore clicks on detached
|
||||||
|
// anchors.
|
||||||
|
a.style.display = 'none';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.download = {
|
||||||
|
start: startDownload
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Download.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"Download.js","sourceRoot":"","sources":["../../../../src/main/frontend/Download.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;GAYG;AACH,SAAS,aAAa,CAAC,GAAW,EAAE,QAAiB;IACnD,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;IACb,sEAAsE;IACtE,wEAAwE;IACxE,oEAAoE;IACpE,yEAAyE;IACzE,oEAAoE;IACpE,yDAAyD;IACzD,CAAC,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,kEAAkE;IAClE,wEAAwE;IACxE,8DAA8D;IAC9D,CAAC,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACpC,uEAAuE;IACvE,WAAW;IACX,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,MAAM,EAAE,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,MAAa,CAAC;AAC3B,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC;AACnB,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;AACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG;IAC1B,KAAK,EAAE,aAAa;CACrB,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Triggers a file download from the given URL using the standard\n * <a href download> click pattern.\n *\n * The anchor is synthesised, clicked synchronously inside the caller's\n * gesture context, and removed. The browser then either navigates to the\n * URL (server responds with Content-Disposition: attachment) or saves the\n * resource directly when the download attribute applies.\n *\n * The {@code download} attribute is honoured only for same-origin URLs;\n * cross-origin responses must set Content-Disposition themselves for the\n * filename to take effect.\n */\nfunction startDownload(url: string, filename?: string): void {\n const a = document.createElement('a');\n a.href = url;\n // Always set `download` so the browser saves the response rather than\n // navigating to it. Empty value lets the browser pick the filename from\n // Content-Disposition or the URL pathname; a non-empty value is the\n // suggested filename (honoured only same-origin). Cross-origin responses\n // without Content-Disposition: attachment still navigate — that's a\n // server-side concern this client helper can't override.\n a.download = filename ?? '';\n // Opt out of Vaadin's client-side router so the click reaches the\n // browser's native download handling instead of being intercepted as an\n // in-app navigation. Matches Anchor.setHref(DownloadHandler).\n a.setAttribute('router-ignore', '');\n // Hidden but in the document — some browsers ignore clicks on detached\n // anchors.\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n}\n\nconst $wnd = window as any;\n$wnd.Vaadin ??= {};\n$wnd.Vaadin.Flow ??= {};\n$wnd.Vaadin.Flow.download = {\n start: startDownload\n};\n\n// Empty export to ensure TypeScript emits this as an ES module,\n// which is required for Vite to load it via import.\nexport {};\n"]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.elementResize = {
|
||||||
|
/**
|
||||||
|
* Installs a ResizeObserver on the given element and invokes the callback
|
||||||
|
* with the rounded content-box width and height each time the element
|
||||||
|
* resizes. Returns a function that disconnects the observer.
|
||||||
|
*
|
||||||
|
* Sub-pixel decimals from contentRect are rounded to integers to avoid
|
||||||
|
* spamming equal-after-rounding updates back to the server.
|
||||||
|
*/
|
||||||
|
observe(element, callback) {
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.target.isConnected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback({
|
||||||
|
width: Math.round(entry.contentRect.width),
|
||||||
|
height: Math.round(entry.contentRect.height)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(element);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=ElementResize.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"ElementResize.js","sourceRoot":"","sources":["../../../../src/main/frontend/ElementResize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,MAAM,IAAI,GAAG,MAAa,CAAC;AAC3B,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC;AACnB,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;AACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,GAAG;IAC/B;;;;;;;OAOG;IACH,OAAO,CAAC,OAAgB,EAAE,QAA8B;QACtD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;oBAC9B,SAAS;gBACX,CAAC;gBACD,QAAQ,CAAC;oBACP,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC;oBAC1C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;iBAC7C,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACrC,CAAC;CACF,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Size data passed to the observe() callback. Field names match the Java\n * Size record so the value can be Jackson-deserialised on the server when\n * forwarded through a trigger-framework input.\n */\ninterface Size {\n width: number;\n height: number;\n}\n\nconst $wnd = window as any;\n$wnd.Vaadin ??= {};\n$wnd.Vaadin.Flow ??= {};\n$wnd.Vaadin.Flow.elementResize = {\n /**\n * Installs a ResizeObserver on the given element and invokes the callback\n * with the rounded content-box width and height each time the element\n * resizes. Returns a function that disconnects the observer.\n *\n * Sub-pixel decimals from contentRect are rounded to integers to avoid\n * spamming equal-after-rounding updates back to the server.\n */\n observe(element: Element, callback: (size: Size) => void): () => void {\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (!entry.target.isConnected) {\n continue;\n }\n callback({\n width: Math.round(entry.contentRect.width),\n height: Math.round(entry.contentRect.height)\n });\n }\n });\n observer.observe(element);\n return () => observer.disconnect();\n }\n};\n\n// Empty export to ensure TypeScript emits this as an ES module,\n// which is required for Vite to load it via import.\nexport {};\n"]}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import './Clipboard';
|
||||||
|
import './Download';
|
||||||
|
import './ElementResize';
|
||||||
|
import './Geolocation';
|
||||||
|
import './WakeLock';
|
||||||
|
export interface FlowConfig {
|
||||||
|
imports?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
interface AppConfig {
|
||||||
|
productionMode: boolean;
|
||||||
|
appId: string;
|
||||||
|
uidl: any;
|
||||||
|
}
|
||||||
|
interface AppInitResponse {
|
||||||
|
appConfig: AppConfig;
|
||||||
|
pushScript?: string;
|
||||||
|
}
|
||||||
|
interface Router {
|
||||||
|
render: (ctx: NavigationParameters, shouldUpdateHistory: boolean) => Promise<void>;
|
||||||
|
}
|
||||||
|
interface HTMLRouterContainer extends HTMLElement {
|
||||||
|
onBeforeEnter?: (ctx: NavigationParameters, cmd: PreventAndRedirectCommands, router: Router) => void | Promise<any>;
|
||||||
|
onBeforeLeave?: (ctx: NavigationParameters, cmd: PreventCommands, router: Router) => void | Promise<any>;
|
||||||
|
serverConnected?: (cancel: boolean, url?: NavigationParameters) => void;
|
||||||
|
serverPaused?: () => void;
|
||||||
|
}
|
||||||
|
interface FlowRoute {
|
||||||
|
action: (params: NavigationParameters) => Promise<HTMLRouterContainer>;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
export interface NavigationParameters {
|
||||||
|
pathname: string;
|
||||||
|
search?: string;
|
||||||
|
}
|
||||||
|
export interface PreventCommands {
|
||||||
|
prevent: () => any;
|
||||||
|
continue?: () => any;
|
||||||
|
}
|
||||||
|
export interface PreventAndRedirectCommands extends PreventCommands {
|
||||||
|
redirect: (route: string) => any;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Client API for flow UI operations.
|
||||||
|
*/
|
||||||
|
export declare class Flow {
|
||||||
|
config: FlowConfig;
|
||||||
|
response?: AppInitResponse;
|
||||||
|
pathname: string;
|
||||||
|
container: HTMLRouterContainer;
|
||||||
|
private isActive;
|
||||||
|
private baseRegex;
|
||||||
|
private appShellTitle;
|
||||||
|
private navigation;
|
||||||
|
constructor(config?: FlowConfig);
|
||||||
|
/**
|
||||||
|
* Return a `route` object for vaadin-router in an one-element array.
|
||||||
|
*
|
||||||
|
* The `FlowRoute` object `path` property handles any route,
|
||||||
|
* and the `action` returns the flow container without updating the content,
|
||||||
|
* delaying the actual Flow server call to the `onBeforeEnter` phase.
|
||||||
|
*
|
||||||
|
* This is a specific API for its use with `vaadin-router`.
|
||||||
|
*/
|
||||||
|
get serverSideRoutes(): [FlowRoute];
|
||||||
|
loadingStarted(): void;
|
||||||
|
loadingFinished(): void;
|
||||||
|
private get action();
|
||||||
|
private flowLeave;
|
||||||
|
private flowNavigate;
|
||||||
|
private getFlowRoutePath;
|
||||||
|
private getFlowRouteQuery;
|
||||||
|
private flowInit;
|
||||||
|
private loadScript;
|
||||||
|
private findNonce;
|
||||||
|
private injectAppIdScript;
|
||||||
|
private flowInitClient;
|
||||||
|
private flowInitUi;
|
||||||
|
private collectBrowserDetails;
|
||||||
|
private addConnectionIndicator;
|
||||||
|
private offlineStubAction;
|
||||||
|
private isFlowClientLoaded;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
import { ConnectionIndicator, ConnectionState } from '@vaadin/common-frontend';
|
||||||
|
import './Clipboard';
|
||||||
|
import { currentFullscreenState } from './Fullscreen';
|
||||||
|
import './Download';
|
||||||
|
import './ElementResize';
|
||||||
|
import './Geolocation';
|
||||||
|
import { currentVisibility } from './PageVisibility';
|
||||||
|
import { currentScreenOrientationAngle, currentScreenOrientationType } from './ScreenOrientation';
|
||||||
|
import './WakeLock';
|
||||||
|
import { isShareSupported } from './WebShare';
|
||||||
|
class FlowUiInitializationError extends Error {
|
||||||
|
}
|
||||||
|
// flow uses body for keeping references
|
||||||
|
const flowRoot = window.document.body;
|
||||||
|
const $wnd = window;
|
||||||
|
const ROOT_NODE_ID = 1; // See StateTree.java
|
||||||
|
function getClients() {
|
||||||
|
return Object.keys($wnd.Vaadin.Flow.clients)
|
||||||
|
.filter((key) => key !== 'TypeScript')
|
||||||
|
.map((id) => $wnd.Vaadin.Flow.clients[id]);
|
||||||
|
}
|
||||||
|
function sendEvent(eventName, data) {
|
||||||
|
getClients().forEach((client) => client.sendEventMessage(ROOT_NODE_ID, eventName, data));
|
||||||
|
}
|
||||||
|
// In the future could be replaced with RegExp.escape()
|
||||||
|
function escapeRegExp(pattern) {
|
||||||
|
return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Client API for flow UI operations.
|
||||||
|
*/
|
||||||
|
export class Flow {
|
||||||
|
config;
|
||||||
|
response = undefined;
|
||||||
|
pathname = '';
|
||||||
|
container;
|
||||||
|
// flag used to inform Testbench whether a server route is in progress
|
||||||
|
isActive = false;
|
||||||
|
baseRegex = /^\//;
|
||||||
|
appShellTitle;
|
||||||
|
navigation = '';
|
||||||
|
constructor(config) {
|
||||||
|
// Set window.name early so @PreserveOnRefresh can use it to identify the browser tab
|
||||||
|
// Only set if not already set to preserve any existing value
|
||||||
|
if (!window.name) {
|
||||||
|
window.name = `v-${Math.random()}`;
|
||||||
|
}
|
||||||
|
flowRoot.$ = flowRoot.$ || [];
|
||||||
|
this.config = config || {};
|
||||||
|
// TB checks for the existence of window.Vaadin.Flow in order
|
||||||
|
// to consider that TB needs to wait for `initFlow()`.
|
||||||
|
$wnd.Vaadin = $wnd.Vaadin || {};
|
||||||
|
$wnd.Vaadin.Flow = $wnd.Vaadin.Flow || {};
|
||||||
|
$wnd.Vaadin.Flow.clients = {
|
||||||
|
TypeScript: {
|
||||||
|
isActive: () => this.isActive
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Set browser details collection function as global for use by refresh()
|
||||||
|
$wnd.Vaadin.Flow.getBrowserDetailsParameters = this.collectBrowserDetails.bind(this);
|
||||||
|
// Regular expression used to remove the app-context
|
||||||
|
const elm = document.head.querySelector('base');
|
||||||
|
this.baseRegex = new RegExp(`^${
|
||||||
|
// IE11 does not support document.baseURI
|
||||||
|
escapeRegExp((document.baseURI || (elm && elm.href) || '/').replace(/^https?:\/\/[^/]+/i, ''))}`);
|
||||||
|
this.appShellTitle = document.title;
|
||||||
|
// Put a vaadin-connection-indicator in the dom
|
||||||
|
this.addConnectionIndicator();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return a `route` object for vaadin-router in an one-element array.
|
||||||
|
*
|
||||||
|
* The `FlowRoute` object `path` property handles any route,
|
||||||
|
* and the `action` returns the flow container without updating the content,
|
||||||
|
* delaying the actual Flow server call to the `onBeforeEnter` phase.
|
||||||
|
*
|
||||||
|
* This is a specific API for its use with `vaadin-router`.
|
||||||
|
*/
|
||||||
|
get serverSideRoutes() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: '(.*)',
|
||||||
|
action: this.action
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
loadingStarted() {
|
||||||
|
// Make Testbench know that server request is in progress
|
||||||
|
this.isActive = true;
|
||||||
|
$wnd.Vaadin.connectionState.loadingStarted();
|
||||||
|
}
|
||||||
|
loadingFinished() {
|
||||||
|
// Make Testbench know that server request has finished
|
||||||
|
this.isActive = false;
|
||||||
|
$wnd.Vaadin.connectionState.loadingFinished();
|
||||||
|
if ($wnd.Vaadin.listener) {
|
||||||
|
// Listeners registered, do not register again.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$wnd.Vaadin.listener = {};
|
||||||
|
// Listen for click on router-links -> 'link' navigation trigger
|
||||||
|
// and on <a> nodes -> 'client' navigation trigger.
|
||||||
|
// Use capture phase to detect prevented / stopped events.
|
||||||
|
document.addEventListener('click', (_e) => {
|
||||||
|
if (_e.target) {
|
||||||
|
if (_e.composedPath().some((node) => node instanceof HTMLElement && node.hasAttribute('router-link'))) {
|
||||||
|
this.navigation = 'link';
|
||||||
|
}
|
||||||
|
else if (_e.composedPath().some((node) => node.nodeName === 'A')) {
|
||||||
|
this.navigation = 'client';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get action() {
|
||||||
|
// Return a function which is bound to the flow instance, thus we can use
|
||||||
|
// the syntax `...serverSideRoutes` in vaadin-router.
|
||||||
|
return async (params) => {
|
||||||
|
// Store last action pathname so as we can check it in events
|
||||||
|
this.pathname = params.pathname;
|
||||||
|
if ($wnd.Vaadin.connectionState.online) {
|
||||||
|
try {
|
||||||
|
await this.flowInit();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof FlowUiInitializationError) {
|
||||||
|
// error initializing Flow: assume connection lost
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
return this.offlineStubAction();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert an offline stub
|
||||||
|
return this.offlineStubAction();
|
||||||
|
}
|
||||||
|
// When an action happens, navigation will be resolved `onBeforeEnter`
|
||||||
|
this.container.onBeforeEnter = (ctx, cmd) => this.flowNavigate(ctx, cmd);
|
||||||
|
// For covering the 'server -> client' use case
|
||||||
|
this.container.onBeforeLeave = (ctx, cmd) => this.flowLeave(ctx, cmd);
|
||||||
|
return this.container;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Send a remote call to `JavaScriptBootstrapUI` to check
|
||||||
|
// whether navigation has to be cancelled.
|
||||||
|
async flowLeave(ctx, cmd) {
|
||||||
|
// server -> server, viewing offline stub, or browser is offline
|
||||||
|
const { connectionState } = $wnd.Vaadin;
|
||||||
|
if (this.pathname === ctx.pathname || !this.isFlowClientLoaded() || connectionState.offline) {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
// 'server -> client'
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.loadingStarted();
|
||||||
|
// The callback to run from server side to cancel navigation
|
||||||
|
this.container.serverConnected = (cancel) => {
|
||||||
|
resolve(cmd && cancel ? cmd.prevent() : cmd?.continue?.());
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
// Call server side to check whether we can leave the view
|
||||||
|
sendEvent('ui-leave-navigation', { route: this.getFlowRoutePath(ctx), query: this.getFlowRouteQuery(ctx) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Send the remote call to `UI` to render the flow
|
||||||
|
// route specified by the context
|
||||||
|
async flowNavigate(ctx, cmd) {
|
||||||
|
if (this.response) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.loadingStarted();
|
||||||
|
// The callback to run from server side once the view is ready
|
||||||
|
this.container.serverConnected = (cancel, redirectContext) => {
|
||||||
|
if (cmd && cancel) {
|
||||||
|
resolve(cmd.prevent());
|
||||||
|
}
|
||||||
|
else if (cmd && cmd.redirect && redirectContext) {
|
||||||
|
resolve(cmd.redirect(redirectContext.pathname));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cmd?.continue?.();
|
||||||
|
this.container.style.display = '';
|
||||||
|
resolve(this.container);
|
||||||
|
}
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
this.container.serverPaused = () => {
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
// Call server side to navigate to the given route
|
||||||
|
sendEvent('ui-navigate', {
|
||||||
|
route: this.getFlowRoutePath(ctx),
|
||||||
|
query: this.getFlowRouteQuery(ctx),
|
||||||
|
appShellTitle: this.appShellTitle,
|
||||||
|
historyState: history.state,
|
||||||
|
trigger: this.navigation
|
||||||
|
});
|
||||||
|
// Default to history navigation trigger.
|
||||||
|
// Link and client cases are handled by click listener in loadingFinished().
|
||||||
|
this.navigation = 'history';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No server response => offline or erroneous connection
|
||||||
|
return Promise.resolve(this.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getFlowRoutePath(context) {
|
||||||
|
// Don't decode the pathname here - let the server handle decoding
|
||||||
|
// individual path segments. This preserves the distinction between
|
||||||
|
// literal slashes (path separators) and encoded slashes (%2F, data).
|
||||||
|
return context.pathname.replace(this.baseRegex, '');
|
||||||
|
}
|
||||||
|
getFlowRouteQuery(context) {
|
||||||
|
return (context.search && context.search.substring(1)) || '';
|
||||||
|
}
|
||||||
|
// import flow client modules and initialize UI in server side.
|
||||||
|
async flowInit() {
|
||||||
|
// Do not start flow twice
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
$wnd.Vaadin.Flow.nonce = this.findNonce();
|
||||||
|
// show flow progress indicator
|
||||||
|
this.loadingStarted();
|
||||||
|
// Initialize server side UI
|
||||||
|
this.response = await this.flowInitUi();
|
||||||
|
const { pushScript, appConfig } = this.response;
|
||||||
|
if (typeof pushScript === 'string') {
|
||||||
|
await this.loadScript(pushScript);
|
||||||
|
}
|
||||||
|
const { appId } = appConfig;
|
||||||
|
// we use a custom tag for the flow app container
|
||||||
|
// This must be created before bootstrapMod.init is called as that call
|
||||||
|
// can handle a UIDL from the server, which relies on the container being available
|
||||||
|
const tag = `flow-container-${appId.toLowerCase()}`;
|
||||||
|
const serverCreatedContainer = document.querySelector(tag);
|
||||||
|
if (serverCreatedContainer) {
|
||||||
|
this.container = serverCreatedContainer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.container = document.createElement(tag);
|
||||||
|
this.container.id = appId;
|
||||||
|
}
|
||||||
|
flowRoot.$[appId] = this.container;
|
||||||
|
// Load bootstrap script with server side parameters
|
||||||
|
const bootstrapMod = await import('./FlowBootstrap');
|
||||||
|
bootstrapMod.init(this.response);
|
||||||
|
// Load custom modules defined by user
|
||||||
|
if (typeof this.config.imports === 'function') {
|
||||||
|
this.injectAppIdScript(appId);
|
||||||
|
await this.config.imports();
|
||||||
|
}
|
||||||
|
// Load flow-client module
|
||||||
|
const clientMod = await import('./FlowClient');
|
||||||
|
await this.flowInitClient(clientMod);
|
||||||
|
// hide flow progress indicator
|
||||||
|
this.loadingFinished();
|
||||||
|
}
|
||||||
|
// It might be that components created from server expect that their content has been rendered.
|
||||||
|
// Appending eagerly the container we avoid these kind of errors.
|
||||||
|
// Note that the client router will move this container to the outlet if the navigation succeed
|
||||||
|
if (this.container && !this.container.isConnected) {
|
||||||
|
this.container.style.display = 'none';
|
||||||
|
document.body.appendChild(this.container);
|
||||||
|
}
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
async loadScript(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.onload = () => resolve();
|
||||||
|
script.onerror = reject;
|
||||||
|
script.src = url;
|
||||||
|
const { nonce } = $wnd.Vaadin.Flow;
|
||||||
|
if (nonce !== undefined) {
|
||||||
|
script.setAttribute('nonce', nonce);
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
findNonce() {
|
||||||
|
let nonce;
|
||||||
|
const scriptTags = document.head.getElementsByTagName('script');
|
||||||
|
for (const scriptTag of scriptTags) {
|
||||||
|
if (scriptTag.nonce) {
|
||||||
|
nonce = scriptTag.nonce;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
injectAppIdScript(appId) {
|
||||||
|
const appIdWithoutHashCode = appId.substring(0, appId.lastIndexOf('-'));
|
||||||
|
const scriptAppId = document.createElement('script');
|
||||||
|
scriptAppId.type = 'module';
|
||||||
|
scriptAppId.setAttribute('data-app-id', appIdWithoutHashCode);
|
||||||
|
const { nonce } = $wnd.Vaadin.Flow;
|
||||||
|
if (nonce !== undefined) {
|
||||||
|
scriptAppId.setAttribute('nonce', nonce);
|
||||||
|
}
|
||||||
|
document.body.append(scriptAppId);
|
||||||
|
}
|
||||||
|
// After the flow-client javascript module has been loaded, this initializes flow UI
|
||||||
|
// in the browser.
|
||||||
|
async flowInitClient(clientMod) {
|
||||||
|
clientMod.init();
|
||||||
|
// client init is async, we need to loop until initialized
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
// client `isActive() == true` while initializing or processing
|
||||||
|
const initializing = getClients().reduce((prev, client) => prev || client.isActive(), false);
|
||||||
|
if (!initializing) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Returns the `appConfig` object
|
||||||
|
async flowInitUi() {
|
||||||
|
// appConfig was sent in the index.html request
|
||||||
|
const initial = $wnd.Vaadin && $wnd.Vaadin.TypeScript && $wnd.Vaadin.TypeScript.initial;
|
||||||
|
if (initial) {
|
||||||
|
$wnd.Vaadin.TypeScript.initial = undefined;
|
||||||
|
return Promise.resolve(initial);
|
||||||
|
}
|
||||||
|
const browserDetails = await this.collectBrowserDetails();
|
||||||
|
// send a request to the `JavaScriptBootstrapHandler`
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
const httpRequest = xhr;
|
||||||
|
// Browser details are appended as individual query parameters rather
|
||||||
|
// than as a single JSON-encoded value. A JSON payload in the URL
|
||||||
|
// produces many percent-encoded escape sequences (%7B, %22, %3A, ...)
|
||||||
|
// that some firewalls/WAFs (e.g. Sophos) flag and block, which would
|
||||||
|
// fail the bootstrap on the very first page load. Plain key=value pairs
|
||||||
|
// avoid that pattern entirely.
|
||||||
|
const browserDetailsParams = browserDetails
|
||||||
|
? Object.entries(browserDetails)
|
||||||
|
.map(([key, value]) => `&${key}=${encodeURIComponent(value)}`)
|
||||||
|
.join('')
|
||||||
|
: '';
|
||||||
|
const requestPath = `?v-r=init&location=${encodeURIComponent(this.getFlowRoutePath(location))}&query=${encodeURIComponent(this.getFlowRouteQuery(location))}${browserDetailsParams}`;
|
||||||
|
httpRequest.open('GET', requestPath);
|
||||||
|
httpRequest.onerror = () => reject(new FlowUiInitializationError(`Invalid server response when initializing Flow UI.
|
||||||
|
${httpRequest.status}
|
||||||
|
${httpRequest.responseText}`));
|
||||||
|
httpRequest.onload = () => {
|
||||||
|
const contentType = httpRequest.getResponseHeader('content-type');
|
||||||
|
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||||
|
resolve(JSON.parse(httpRequest.responseText));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpRequest.onerror();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
httpRequest.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Collects browser details parameters
|
||||||
|
async collectBrowserDetails() {
|
||||||
|
const params = {};
|
||||||
|
/* Screen height and width */
|
||||||
|
params['v-sh'] = $wnd.screen.height;
|
||||||
|
params['v-sw'] = $wnd.screen.width;
|
||||||
|
/* Browser window dimensions */
|
||||||
|
params['v-wh'] = $wnd.innerHeight;
|
||||||
|
params['v-ww'] = $wnd.innerWidth;
|
||||||
|
/* Body element dimensions */
|
||||||
|
params['v-bh'] = $wnd.document.body.clientHeight;
|
||||||
|
params['v-bw'] = $wnd.document.body.clientWidth;
|
||||||
|
/* Current time */
|
||||||
|
const date = new Date();
|
||||||
|
params['v-curdate'] = date.getTime();
|
||||||
|
/* Current timezone offset (including DST shift) */
|
||||||
|
const tzo1 = date.getTimezoneOffset();
|
||||||
|
/* Compare the current tz offset with the first offset from the end
|
||||||
|
of the year that differs --- if less that, we are in DST, otherwise
|
||||||
|
we are in normal time */
|
||||||
|
let dstDiff = 0;
|
||||||
|
let rawTzo = tzo1;
|
||||||
|
for (let m = 12; m > 0; m -= 1) {
|
||||||
|
date.setUTCMonth(m);
|
||||||
|
const tzo2 = date.getTimezoneOffset();
|
||||||
|
if (tzo1 !== tzo2) {
|
||||||
|
dstDiff = tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1;
|
||||||
|
rawTzo = tzo1 > tzo2 ? tzo1 : tzo2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Time zone offset */
|
||||||
|
params['v-tzo'] = tzo1;
|
||||||
|
/* DST difference */
|
||||||
|
params['v-dstd'] = dstDiff;
|
||||||
|
/* Time zone offset without DST */
|
||||||
|
params['v-rtzo'] = rawTzo;
|
||||||
|
/* DST in effect? */
|
||||||
|
params['v-dston'] = tzo1 !== rawTzo;
|
||||||
|
/* Time zone id (if available) */
|
||||||
|
try {
|
||||||
|
params['v-tzid'] = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
params['v-tzid'] = '';
|
||||||
|
}
|
||||||
|
/* Window name */
|
||||||
|
if ($wnd.name) {
|
||||||
|
params['v-wn'] = $wnd.name;
|
||||||
|
}
|
||||||
|
/* Detect touch device support */
|
||||||
|
let supportsTouch = false;
|
||||||
|
try {
|
||||||
|
$wnd.document.createEvent('TouchEvent');
|
||||||
|
supportsTouch = true;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
/* Chrome and IE10 touch detection */
|
||||||
|
supportsTouch = 'ontouchstart' in $wnd || typeof $wnd.navigator.msMaxTouchPoints !== 'undefined';
|
||||||
|
}
|
||||||
|
params['v-td'] = supportsTouch;
|
||||||
|
/* Device Pixel Ratio */
|
||||||
|
params['v-pr'] = $wnd.devicePixelRatio;
|
||||||
|
if ($wnd.navigator.platform) {
|
||||||
|
params['v-np'] = $wnd.navigator.platform;
|
||||||
|
}
|
||||||
|
/* Color scheme from CSS color-scheme property */
|
||||||
|
const colorScheme = getComputedStyle(document.documentElement).colorScheme.trim();
|
||||||
|
// "normal" is the default value and means no color scheme is set
|
||||||
|
params['v-cs'] = colorScheme && colorScheme !== 'normal' ? colorScheme : '';
|
||||||
|
/* Page visibility — initial state of document.hidden / document.hasFocus() */
|
||||||
|
params['v-pv'] = currentVisibility();
|
||||||
|
/* Fullscreen state — initial state of document.fullscreenEnabled / .fullscreenElement */
|
||||||
|
params['v-fs'] = currentFullscreenState();
|
||||||
|
/* Screen orientation — initial state of screen.orientation, empty
|
||||||
|
when the Screen Orientation API is unavailable. */
|
||||||
|
params['v-so'] = currentScreenOrientationType();
|
||||||
|
params['v-soa'] = currentScreenOrientationAngle();
|
||||||
|
/* Theme name - detect which theme is in use */
|
||||||
|
const computedStyle = getComputedStyle(document.documentElement);
|
||||||
|
let themeName = '';
|
||||||
|
if (computedStyle.getPropertyValue('--vaadin-lumo-theme').trim()) {
|
||||||
|
themeName = 'lumo';
|
||||||
|
}
|
||||||
|
else if (computedStyle.getPropertyValue('--vaadin-aura-theme').trim()) {
|
||||||
|
themeName = 'aura';
|
||||||
|
}
|
||||||
|
params['v-tn'] = themeName;
|
||||||
|
/* Geolocation availability — guarded because tests may reset
|
||||||
|
window.Vaadin between runs, removing the namespace that
|
||||||
|
Geolocation.ts installs at import time. */
|
||||||
|
const geolocation = $wnd.Vaadin.Flow?.geolocation;
|
||||||
|
if (geolocation) {
|
||||||
|
params['v-ga'] = await geolocation.queryAvailability();
|
||||||
|
}
|
||||||
|
/* Wake-lock availability — same guard rationale as geolocation. */
|
||||||
|
const wakeLock = $wnd.Vaadin.Flow?.wakeLock;
|
||||||
|
if (wakeLock) {
|
||||||
|
params['v-wla'] = wakeLock.queryAvailability();
|
||||||
|
}
|
||||||
|
/* Web Share API support */
|
||||||
|
params['v-ws'] = isShareSupported();
|
||||||
|
/* Stringify each value (they are parsed on the server side) */
|
||||||
|
const stringParams = {};
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = params[key];
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
stringParams[key] = value.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return stringParams;
|
||||||
|
}
|
||||||
|
// Create shared connection state store and connection indicator
|
||||||
|
addConnectionIndicator() {
|
||||||
|
// add connection indicator to DOM
|
||||||
|
ConnectionIndicator.create();
|
||||||
|
// Listen to browser online/offline events and update the loading indicator accordingly.
|
||||||
|
// Note: if flow-client is loaded, it instead handles the state transitions.
|
||||||
|
$wnd.addEventListener('online', () => {
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
// Send an HTTP HEAD request for sw.js to verify server reachability.
|
||||||
|
// We do not expect sw.js to be cached, so the request goes to the
|
||||||
|
// server rather than being served from local cache.
|
||||||
|
// Require network-level failure to revert the state to CONNECTION_LOST
|
||||||
|
// (HTTP error code is ok since it still verifies server's presence).
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.RECONNECTING;
|
||||||
|
const http = new XMLHttpRequest();
|
||||||
|
http.open('HEAD', 'sw.js');
|
||||||
|
http.onload = () => {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTED;
|
||||||
|
};
|
||||||
|
http.onerror = () => {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
};
|
||||||
|
// Postpone request to reduce potential net::ERR_INTERNET_DISCONNECTED
|
||||||
|
// errors that sometimes occurs even if browser says it is online
|
||||||
|
setTimeout(() => http.send(), 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$wnd.addEventListener('offline', () => {
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async offlineStubAction() {
|
||||||
|
const offlineStub = document.createElement('iframe');
|
||||||
|
const offlineStubPath = './offline-stub.html';
|
||||||
|
offlineStub.setAttribute('src', offlineStubPath);
|
||||||
|
offlineStub.setAttribute('style', 'width: 100%; height: 100%; border: 0');
|
||||||
|
this.response = undefined;
|
||||||
|
let onlineListener;
|
||||||
|
const removeOfflineStubAndOnlineListener = () => {
|
||||||
|
if (onlineListener !== undefined) {
|
||||||
|
$wnd.Vaadin.connectionState.removeStateChangeListener(onlineListener);
|
||||||
|
onlineListener = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
offlineStub.onBeforeEnter = (ctx, _cmds, router) => {
|
||||||
|
onlineListener = () => {
|
||||||
|
if ($wnd.Vaadin.connectionState.online) {
|
||||||
|
removeOfflineStubAndOnlineListener();
|
||||||
|
router.render(ctx, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$wnd.Vaadin.connectionState.addStateChangeListener(onlineListener);
|
||||||
|
};
|
||||||
|
offlineStub.onBeforeLeave = (_ctx, _cmds, _router) => {
|
||||||
|
removeOfflineStubAndOnlineListener();
|
||||||
|
};
|
||||||
|
return offlineStub;
|
||||||
|
}
|
||||||
|
isFlowClientLoaded() {
|
||||||
|
return this.response !== undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=Flow.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export const init: (appInitResponse: any) => void;
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/* This is a copy of the regular `BootstrapHandler.js` in the flow-server
|
||||||
|
module, but with the following modifications:
|
||||||
|
- The main function is exported as an ES module for lazy initialization.
|
||||||
|
- Application configuration is passed as a parameter instead of using
|
||||||
|
replacement placeholders as in the regular bootstrapping.
|
||||||
|
- It reuses `Vaadin.Flow.clients` if exists.
|
||||||
|
- Fixed lint errors.
|
||||||
|
*/
|
||||||
|
const init = function (appInitResponse) {
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
var apps = {};
|
||||||
|
var widgetsets = {};
|
||||||
|
|
||||||
|
var log;
|
||||||
|
if (typeof window.console === undefined || !window.location.search.match(/[&?]debug(&|$)/)) {
|
||||||
|
/* If no console.log present, just use a no-op */
|
||||||
|
log = function () {};
|
||||||
|
} else if (typeof window.console.log === 'function') {
|
||||||
|
/* If it's a function, use it with apply */
|
||||||
|
log = function () {
|
||||||
|
window.console.log.apply(window.console, arguments);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
/* In IE, its a native function for which apply is not defined, but it works
|
||||||
|
without a proper 'this' reference */
|
||||||
|
log = window.console.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isInitializedInDom = function (appId) {
|
||||||
|
var appDiv = document.getElementById(appId);
|
||||||
|
if (!appDiv) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < appDiv.childElementCount; i++) {
|
||||||
|
var className = appDiv.childNodes[i].className;
|
||||||
|
/* If the app div contains a child with the class
|
||||||
|
'v-app-loading' we have only received the HTML
|
||||||
|
but not yet started the widget set
|
||||||
|
(UIConnector removes the v-app-loading div). */
|
||||||
|
if (className && className.indexOf('v-app-loading') != -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Needed for Testbench compatibility, but prevents any Vaadin 7 app from
|
||||||
|
* bootstrapping unless the legacy vaadinBootstrap.js file is loaded before
|
||||||
|
* this script.
|
||||||
|
*/
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a CSS animation on an element by adding a class, then
|
||||||
|
* removes the class when the animation ends.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.flashClass = function (element, className) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
void element.offsetWidth;
|
||||||
|
element.classList.add(className);
|
||||||
|
function onAnimationEnd(e) {
|
||||||
|
if (e.target === element) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
element.removeEventListener('animationend', onAnimationEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.addEventListener('animationend', onAnimationEnd);
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
var style = getComputedStyle(element);
|
||||||
|
var animName = style.animationName;
|
||||||
|
if (!animName || animName === 'none') {
|
||||||
|
element.classList.remove(className);
|
||||||
|
element.removeEventListener('animationend', onAnimationEnd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed for wrapping custom javascript functionality in the components (i.e. connectors)
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.tryCatchWrapper = function (originalFunction, component) {
|
||||||
|
return function () {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const result = originalFunction.apply(this, arguments);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`There seems to be an error in ${component}:
|
||||||
|
${error.message}
|
||||||
|
Please submit an issue to https://github.com/vaadin/flow-components/issues/new/choose`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.Vaadin.Flow.initApplication) {
|
||||||
|
window.Vaadin.Flow.clients = window.Vaadin.Flow.clients || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a Flow application with the given ID and configuration,
|
||||||
|
* and triggers the widgetset callback to start the client engine.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.initApplication = function (appId, config) {
|
||||||
|
var testbenchId = appId.replace(/-\d+$/, '');
|
||||||
|
|
||||||
|
if (apps[appId]) {
|
||||||
|
if (
|
||||||
|
window.Vaadin &&
|
||||||
|
window.Vaadin.Flow &&
|
||||||
|
window.Vaadin.Flow.clients &&
|
||||||
|
window.Vaadin.Flow.clients[testbenchId] &&
|
||||||
|
window.Vaadin.Flow.clients[testbenchId].initializing
|
||||||
|
) {
|
||||||
|
throw new Error('Application ' + appId + ' is already being initialized');
|
||||||
|
}
|
||||||
|
if (isInitializedInDom(appId)) {
|
||||||
|
if (appInitResponse.appConfig.productionMode) {
|
||||||
|
throw new Error('Application ' + appId + ' already initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old contents for Flow
|
||||||
|
var appDiv = document.getElementById(appId);
|
||||||
|
for (var i = 0; i < appDiv.childElementCount; i++) {
|
||||||
|
appDiv.childNodes[i].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For devMode reset app config and restart widgetset as client
|
||||||
|
// is up and running after hrm update.
|
||||||
|
const getConfig = function (name) {
|
||||||
|
return config[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Export public data */
|
||||||
|
const app = {
|
||||||
|
getConfig: getConfig
|
||||||
|
};
|
||||||
|
apps[appId] = app;
|
||||||
|
|
||||||
|
if (widgetsets['client'].callback) {
|
||||||
|
log('Starting from bootstrap', appId);
|
||||||
|
widgetsets['client'].callback(appId);
|
||||||
|
} else {
|
||||||
|
log('Setting pending startup', appId);
|
||||||
|
widgetsets['client'].pendingApps.push(appId);
|
||||||
|
}
|
||||||
|
return apps[appId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log('init application', appId, config);
|
||||||
|
|
||||||
|
window.Vaadin.Flow.clients[testbenchId] = {
|
||||||
|
isActive: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
initializing: true,
|
||||||
|
productionMode: mode
|
||||||
|
};
|
||||||
|
|
||||||
|
var getConfig = function (name) {
|
||||||
|
var value = config[name];
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Export public data */
|
||||||
|
var app = {
|
||||||
|
getConfig: getConfig
|
||||||
|
};
|
||||||
|
apps[appId] = app;
|
||||||
|
|
||||||
|
var widgetset = 'client';
|
||||||
|
widgetsets[widgetset] = {
|
||||||
|
pendingApps: []
|
||||||
|
};
|
||||||
|
if (widgetsets[widgetset].callback) {
|
||||||
|
log('Starting from bootstrap', appId);
|
||||||
|
widgetsets[widgetset].callback(appId);
|
||||||
|
} else {
|
||||||
|
log('Setting pending startup', appId);
|
||||||
|
widgetsets[widgetset].pendingApps.push(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
/** Returns an array of all registered application IDs */
|
||||||
|
window.Vaadin.Flow.getAppIds = function () {
|
||||||
|
var ids = [];
|
||||||
|
for (var id in apps) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(apps, id)) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
/** Returns the application object for the given ID */
|
||||||
|
window.Vaadin.Flow.getApp = function (appId) {
|
||||||
|
return apps[appId];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Registers a widgetset callback and starts any applications
|
||||||
|
* that are waiting for it.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.registerWidgetset = function (widgetset, callback) {
|
||||||
|
log('Widgetset registered', widgetset);
|
||||||
|
var ws = widgetsets[widgetset];
|
||||||
|
if (ws && ws.pendingApps) {
|
||||||
|
ws.callback = callback;
|
||||||
|
for (var i = 0; i < ws.pendingApps.length; i++) {
|
||||||
|
var appId = ws.pendingApps[i];
|
||||||
|
log('Starting from register widgetset', appId);
|
||||||
|
callback(appId);
|
||||||
|
}
|
||||||
|
ws.pendingApps = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Flow bootstrap loaded');
|
||||||
|
if (appInitResponse.appConfig.productionMode && typeof window.__gwtStatsEvent != 'function') {
|
||||||
|
window.Vaadin.Flow.gwtStatsEvents = [];
|
||||||
|
window.__gwtStatsEvent = function (event) {
|
||||||
|
window.Vaadin.Flow.gwtStatsEvents.push(event);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var config = appInitResponse.appConfig;
|
||||||
|
var mode = appInitResponse.appConfig.productionMode;
|
||||||
|
window.Vaadin.Flow.initApplication(config.appId, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { init };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const init: () => void;
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Client-side helpers for keyboard shortcuts. Loaded on demand by
|
||||||
|
* ShortcutRegistration (see initShortcutClient) the same way FlowWebPush.js is
|
||||||
|
* loaded by WebPush. Provides the popover/modal origin guards (#24974) and the
|
||||||
|
* keydown delegate used when a shortcut listens on a browser-only element.
|
||||||
|
*/
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
window.Vaadin.Flow.shortcut = window.Vaadin.Flow.shortcut || {
|
||||||
|
// Nearest open popover/modal ancestor of the given node in the flattened
|
||||||
|
// (composed) tree, so slotted light-DOM content resolves to the overlay in a
|
||||||
|
// component's shadow root.
|
||||||
|
_scopeOf: function (node) {
|
||||||
|
while (node) {
|
||||||
|
if (node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = node.assignedSlot || node.parentNode || node.host || null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Nearest open popover/modal ancestor of the event target.
|
||||||
|
_eventScope: function (event) {
|
||||||
|
const path = event.composedPath();
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const node = path[i];
|
||||||
|
if (node && node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delegate path: suppress when an open popover/modal sits between the event
|
||||||
|
// target and the boundary element the listener is attached to. Returns true
|
||||||
|
// when the shortcut is allowed to fire. Fails open on error.
|
||||||
|
eventWithinBoundary: function (event, boundary) {
|
||||||
|
try {
|
||||||
|
const path = event.composedPath();
|
||||||
|
const boundaryIndex = path.indexOf(boundary);
|
||||||
|
if (boundaryIndex < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < boundaryIndex; i++) {
|
||||||
|
const node = path[i];
|
||||||
|
if (node && node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Normal path: fire only when the event and the shortcut owner (located via
|
||||||
|
// the given attribute selector) share the same popover/modal scope. Returns
|
||||||
|
// true when the shortcut is allowed to fire. Fails open on error.
|
||||||
|
//
|
||||||
|
// A relayed clone (see registerKeydownDelegate) carries the real origin scope
|
||||||
|
// in _vaadinShortcutOriginScope, because its own composedPath points at the
|
||||||
|
// listenOn element and no longer reflects where the keydown happened.
|
||||||
|
eventInOwnerScope: function (event, ownerSelector) {
|
||||||
|
try {
|
||||||
|
const owner = document.querySelector(ownerSelector);
|
||||||
|
if (!owner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const eventScope =
|
||||||
|
'_vaadinShortcutOriginScope' in event
|
||||||
|
? event._vaadinShortcutOriginScope
|
||||||
|
: window.Vaadin.Flow.shortcut._eventScope(event);
|
||||||
|
return eventScope === window.Vaadin.Flow.shortcut._scopeOf(owner);
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Relays keydown events from a browser-only element (found by the JS locator)
|
||||||
|
// to the listenOn component. When the given matcher accepts the event a clone
|
||||||
|
// is re-dispatched to listenOn so the server-side shortcut listener fires.
|
||||||
|
// (Previously the inline ELEMENT_LOCATOR_JS in ShortcutRegistration.)
|
||||||
|
registerKeydownDelegate: function (listenOn, delegate, matches, resetFocus, allowDefault) {
|
||||||
|
if (!delegate) {
|
||||||
|
throw 'Shortcut listenOn element not found with the given JS locator';
|
||||||
|
}
|
||||||
|
delegate.addEventListener('keydown', function (event) {
|
||||||
|
if (matches(event, delegate)) {
|
||||||
|
if (resetFocus) {
|
||||||
|
window.Vaadin.Flow.resetFocus();
|
||||||
|
}
|
||||||
|
const clone = new event.constructor(event.type, event);
|
||||||
|
// Remember where the keydown actually originated: the clone is
|
||||||
|
// re-targeted at listenOn, so its composedPath can no longer tell a
|
||||||
|
// downstream owner-scope guard that the event came from this overlay.
|
||||||
|
clone._vaadinShortcutOriginScope = window.Vaadin.Flow.shortcut._eventScope(event);
|
||||||
|
listenOn.dispatchEvent(clone);
|
||||||
|
if (!allowDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
type VaadinFullscreenState = 'UNSUPPORTED' | 'NOT_FULLSCREEN' | 'FULLSCREEN';
|
||||||
|
/**
|
||||||
|
* Returns the current fullscreen state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function currentFullscreenState(): VaadinFullscreenState;
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns the current fullscreen state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function currentFullscreenState() {
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
return document.fullscreenElement ? 'FULLSCREEN' : 'NOT_FULLSCREEN';
|
||||||
|
}
|
||||||
|
// Dispatch on document.body so the server-side Page facade (listening on
|
||||||
|
// the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(state) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-fullscreen-change', { detail: state }));
|
||||||
|
}
|
||||||
|
// Tracks the most recent component-fullscreen setup so the wrapper can be
|
||||||
|
// torn down when fullscreen exits (programmatically or via Escape) or when
|
||||||
|
// a new fullscreen request supersedes it.
|
||||||
|
let activeComponentReset;
|
||||||
|
function resetComponentIfActive() {
|
||||||
|
if (activeComponentReset) {
|
||||||
|
const fn = activeComponentReset;
|
||||||
|
activeComponentReset = undefined;
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('fullscreenchange', () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
resetComponentIfActive();
|
||||||
|
}
|
||||||
|
dispatch(currentFullscreenState());
|
||||||
|
});
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.fullscreen = {
|
||||||
|
/**
|
||||||
|
* Requests fullscreen for the entire page (document.documentElement).
|
||||||
|
* Resolves once the browser has entered fullscreen; rejects with the
|
||||||
|
* browser's error if the request is refused (no user activation,
|
||||||
|
* permissions policy, etc.) or with a custom error if fullscreen is not
|
||||||
|
* supported.
|
||||||
|
*/
|
||||||
|
async requestPageFullscreen() {
|
||||||
|
resetComponentIfActive();
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
throw new Error('Fullscreen is not supported');
|
||||||
|
}
|
||||||
|
await document.documentElement.requestFullscreen();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Requests fullscreen for a specific component by moving it into the
|
||||||
|
* given wrapper element and hiding the rest of the view. Fullscreens
|
||||||
|
* document.documentElement so that Vaadin theming and overlay
|
||||||
|
* components keep working. The component is restored to its original
|
||||||
|
* position on exit (programmatic, Escape, or a superseding request).
|
||||||
|
* If the browser rejects the request, the DOM is rolled back before the
|
||||||
|
* promise rejects with the browser's error.
|
||||||
|
*/
|
||||||
|
async requestComponentFullscreen(element, wrapper) {
|
||||||
|
resetComponentIfActive();
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
throw new Error('Fullscreen is not supported');
|
||||||
|
}
|
||||||
|
const originalParent = element.parentNode;
|
||||||
|
if (!originalParent) {
|
||||||
|
throw new Error('Component is not attached to the DOM');
|
||||||
|
}
|
||||||
|
// The view root is the wrapper's current element child (the route
|
||||||
|
// content). Capture it before touching the DOM, because the steps below
|
||||||
|
// insert a placeholder comment and move the element into the wrapper —
|
||||||
|
// after that, the wrapper's first node may be the placeholder rather than
|
||||||
|
// the view root. Use firstElementChild so comment/text nodes are skipped.
|
||||||
|
const viewRoot = wrapper.firstElementChild;
|
||||||
|
const placeholder = document.createComment('vaadin-fullscreen-placeholder');
|
||||||
|
originalParent.insertBefore(placeholder, element);
|
||||||
|
wrapper.appendChild(element);
|
||||||
|
// When the fullscreened component *is* the view root there is nothing
|
||||||
|
// else to hide; hiding it would blank the fullscreen. Otherwise hide the
|
||||||
|
// view root so only the fullscreened component shows.
|
||||||
|
const hidden = viewRoot === element ? null : viewRoot;
|
||||||
|
const previousDisplay = hidden?.style.display ?? '';
|
||||||
|
if (hidden) {
|
||||||
|
hidden.style.display = 'none';
|
||||||
|
}
|
||||||
|
activeComponentReset = () => {
|
||||||
|
placeholder.parentNode?.insertBefore(element, placeholder);
|
||||||
|
placeholder.remove();
|
||||||
|
if (hidden) {
|
||||||
|
hidden.style.display = previousDisplay;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await document.documentElement.requestFullscreen();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// Browser rejected the request — undo the DOM changes so the page
|
||||||
|
// does not end up looking fullscreened without actually being so.
|
||||||
|
resetComponentIfActive();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Exits fullscreen mode if the page is currently in fullscreen.
|
||||||
|
*/
|
||||||
|
exitFullscreen() {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=Fullscreen.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
function copyCoords(c) {
|
||||||
|
return {
|
||||||
|
latitude: c.latitude,
|
||||||
|
longitude: c.longitude,
|
||||||
|
accuracy: c.accuracy,
|
||||||
|
altitude: c.altitude,
|
||||||
|
altitudeAccuracy: c.altitudeAccuracy,
|
||||||
|
heading: c.heading,
|
||||||
|
speed: c.speed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const watches = new Map();
|
||||||
|
// The cached availability for the current page. Populated on first
|
||||||
|
// queryAvailability() call, refreshed from each get()/watch() outcome, and
|
||||||
|
// kept current by a permissionchange listener (where supported).
|
||||||
|
let cachedAvailability = null;
|
||||||
|
let permissionChangeListenerInstalled = false;
|
||||||
|
function publishAvailability(next) {
|
||||||
|
if (cachedAvailability === next) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cachedAvailability = next;
|
||||||
|
// Dispatch on document.body so the server-side Geolocation facade (listening
|
||||||
|
// on the UI element, which is body) can update its cached value.
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-geolocation-availability-change', {
|
||||||
|
detail: { availability: next }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// Applies a single get()/watch() outcome to the cached availability and
|
||||||
|
// returns the value to report in the response. Never overwrites
|
||||||
|
// UNSUPPORTED, which is session-stable. TIMEOUT and POSITION_UNAVAILABLE
|
||||||
|
// don't reveal the permission state, so the previous cached value is
|
||||||
|
// returned unchanged.
|
||||||
|
function getAndCacheAvailabilityFromResult(position, error) {
|
||||||
|
if (cachedAvailability !== 'UNSUPPORTED') {
|
||||||
|
if (position) {
|
||||||
|
publishAvailability('GRANTED');
|
||||||
|
}
|
||||||
|
else if (error?.code === 1) {
|
||||||
|
publishAvailability('DENIED');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedAvailability ?? 'UNKNOWN';
|
||||||
|
}
|
||||||
|
async function resolveAvailability() {
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
// Chromium exposes document.featurePolicy; Firefox and Safari do not
|
||||||
|
// expose any feature-policy introspection API, so the check is only
|
||||||
|
// possible on Chromium. When absent, assume geolocation is allowed.
|
||||||
|
const doc = document;
|
||||||
|
if (doc.featurePolicy && typeof doc.featurePolicy.allowsFeature === 'function') {
|
||||||
|
try {
|
||||||
|
if (!doc.featurePolicy.allowsFeature('geolocation')) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore and assume allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const status = await navigator.permissions.query({ name: 'geolocation' });
|
||||||
|
if (!permissionChangeListenerInstalled) {
|
||||||
|
permissionChangeListenerInstalled = true;
|
||||||
|
status.addEventListener('change', () => {
|
||||||
|
publishAvailability(stateToAvailability(status.state));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return stateToAvailability(status.state);
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Safari rejects the 'geolocation' permission name with a TypeError
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function stateToAvailability(state) {
|
||||||
|
switch (state) {
|
||||||
|
case 'granted':
|
||||||
|
return 'GRANTED';
|
||||||
|
case 'denied':
|
||||||
|
return 'DENIED';
|
||||||
|
case 'prompt':
|
||||||
|
return 'PROMPT';
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.geolocation = {
|
||||||
|
get(options) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
navigator.geolocation.getCurrentPosition((p) => {
|
||||||
|
const position = { coords: copyCoords(p.coords), timestamp: p.timestamp };
|
||||||
|
resolve({ position, availability: getAndCacheAvailabilityFromResult(position, undefined) });
|
||||||
|
}, (e) => {
|
||||||
|
const error = { code: e.code, message: e.message };
|
||||||
|
resolve({ error, availability: getAndCacheAvailabilityFromResult(undefined, error) });
|
||||||
|
}, options || undefined);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch(element, options, watchKey) {
|
||||||
|
if (watches.has(watchKey)) {
|
||||||
|
navigator.geolocation.clearWatch(watches.get(watchKey));
|
||||||
|
}
|
||||||
|
watches.set(watchKey, navigator.geolocation.watchPosition((p) => {
|
||||||
|
const position = { coords: copyCoords(p.coords), timestamp: p.timestamp };
|
||||||
|
getAndCacheAvailabilityFromResult(position, undefined);
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-geolocation-position', {
|
||||||
|
detail: position
|
||||||
|
}));
|
||||||
|
}, (e) => {
|
||||||
|
const error = { code: e.code, message: e.message };
|
||||||
|
getAndCacheAvailabilityFromResult(undefined, error);
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-geolocation-error', {
|
||||||
|
detail: error
|
||||||
|
}));
|
||||||
|
}, options || undefined));
|
||||||
|
},
|
||||||
|
clearWatch(watchKey) {
|
||||||
|
if (watches.has(watchKey)) {
|
||||||
|
navigator.geolocation.clearWatch(watches.get(watchKey));
|
||||||
|
watches.delete(watchKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async queryAvailability() {
|
||||||
|
const value = await resolveAvailability();
|
||||||
|
// publish without dispatching a change event — there is no previous
|
||||||
|
// cached value to compare against when cachedAvailability is null and
|
||||||
|
// the bootstrap consumer just wants the initial answer.
|
||||||
|
cachedAvailability = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Geolocation.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
|||||||
|
type VaadinPageVisibility = 'VISIBLE' | 'VISIBLE_NOT_FOCUSED' | 'HIDDEN';
|
||||||
|
/**
|
||||||
|
* Returns the current visibility state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function currentVisibility(): VaadinPageVisibility;
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// Firefox defers the visibilitychange event while the window is blurred, so
|
||||||
|
// a blur handler needs to wait long enough for that delivery to land before
|
||||||
|
// concluding the state is really "visible but not focused".
|
||||||
|
const FIREFOX_BLUR_SETTLE_MS = 500;
|
||||||
|
const DEFAULT_BLUR_SETTLE_MS = 10;
|
||||||
|
/**
|
||||||
|
* Returns the current visibility state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function currentVisibility() {
|
||||||
|
if (document.hidden) {
|
||||||
|
return 'HIDDEN';
|
||||||
|
}
|
||||||
|
return document.hasFocus() ? 'VISIBLE' : 'VISIBLE_NOT_FOCUSED';
|
||||||
|
}
|
||||||
|
function isFirefox() {
|
||||||
|
// Firefox is the only supported browser that reorders visibilitychange
|
||||||
|
// relative to blur; UA sniffing is acceptable here because the alternative
|
||||||
|
// is waiting the longer interval on every browser.
|
||||||
|
return navigator.userAgent.indexOf('Firefox') > -1;
|
||||||
|
}
|
||||||
|
let blurTimer;
|
||||||
|
// Dispatch on document.body so the server-side Page facade (listening on
|
||||||
|
// the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(state) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-page-visibility-change', { detail: state }));
|
||||||
|
}
|
||||||
|
function clearBlurTimer() {
|
||||||
|
if (blurTimer !== undefined) {
|
||||||
|
clearTimeout(blurTimer);
|
||||||
|
blurTimer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
dispatch(document.hidden ? 'HIDDEN' : 'VISIBLE');
|
||||||
|
});
|
||||||
|
window.addEventListener('blur', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
const delay = isFirefox() ? FIREFOX_BLUR_SETTLE_MS : DEFAULT_BLUR_SETTLE_MS;
|
||||||
|
blurTimer = setTimeout(() => {
|
||||||
|
blurTimer = undefined;
|
||||||
|
if (!document.hidden) {
|
||||||
|
dispatch('VISIBLE_NOT_FOCUSED');
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
if (!document.hidden) {
|
||||||
|
dispatch('VISIBLE');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=PageVisibility.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"PageVisibility.js","sourceRoot":"","sources":["../../../../src/main/frontend/PageVisibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,4EAA4E;AAC5E,4EAA4E;AAC5E,4DAA4D;AAC5D,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC;AACjE,CAAC;AAED,SAAS,SAAS;IAChB,uEAAuE;IACvE,2EAA2E;IAC3E,mDAAmD;IACnD,OAAO,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,SAAoD,CAAC;AAEzD,yEAAyE;AACzE,wDAAwD;AACxD,SAAS,QAAQ,CAAC,KAA2B;IAC3C,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,SAAS,GAAG,SAAS,CAAC;IACxB,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;IACjD,cAAc,EAAE,CAAC;IACjB,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;IACnC,cAAc,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAC5E,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;QAC1B,SAAS,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,QAAQ,CAAC,qBAAqB,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,EAAE,KAAK,CAAC,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;IACpC,cAAc,EAAE,CAAC;IACjB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\ntype VaadinPageVisibility = 'VISIBLE' | 'VISIBLE_NOT_FOCUSED' | 'HIDDEN';\n\n// Firefox defers the visibilitychange event while the window is blurred, so\n// a blur handler needs to wait long enough for that delivery to land before\n// concluding the state is really \"visible but not focused\".\nconst FIREFOX_BLUR_SETTLE_MS = 500;\nconst DEFAULT_BLUR_SETTLE_MS = 10;\n\n/**\n * Returns the current visibility state synchronously. Used by the bootstrap\n * path to seed the server-side signal without waiting for a DOM event.\n */\nexport function currentVisibility(): VaadinPageVisibility {\n if (document.hidden) {\n return 'HIDDEN';\n }\n return document.hasFocus() ? 'VISIBLE' : 'VISIBLE_NOT_FOCUSED';\n}\n\nfunction isFirefox(): boolean {\n // Firefox is the only supported browser that reorders visibilitychange\n // relative to blur; UA sniffing is acceptable here because the alternative\n // is waiting the longer interval on every browser.\n return navigator.userAgent.indexOf('Firefox') > -1;\n}\n\nlet blurTimer: ReturnType<typeof setTimeout> | undefined;\n\n// Dispatch on document.body so the server-side Page facade (listening on\n// the UI element, which is body) can update its signal.\nfunction dispatch(state: VaadinPageVisibility): void {\n document.body.dispatchEvent(new CustomEvent('vaadin-page-visibility-change', { detail: state }));\n}\n\nfunction clearBlurTimer(): void {\n if (blurTimer !== undefined) {\n clearTimeout(blurTimer);\n blurTimer = undefined;\n }\n}\n\ndocument.addEventListener('visibilitychange', () => {\n clearBlurTimer();\n dispatch(document.hidden ? 'HIDDEN' : 'VISIBLE');\n});\n\nwindow.addEventListener('blur', () => {\n clearBlurTimer();\n const delay = isFirefox() ? FIREFOX_BLUR_SETTLE_MS : DEFAULT_BLUR_SETTLE_MS;\n blurTimer = setTimeout(() => {\n blurTimer = undefined;\n if (!document.hidden) {\n dispatch('VISIBLE_NOT_FOCUSED');\n }\n }, delay);\n});\n\nwindow.addEventListener('focus', () => {\n clearBlurTimer();\n if (!document.hidden) {\n dispatch('VISIBLE');\n }\n});\n"]}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Outlet } from 'react-router';
|
||||||
|
import { ReactAdapterElement } from "Frontend/generated/flow/ReactAdapter.js";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class ReactRouterOutletElement extends ReactAdapterElement {
|
||||||
|
public async connectedCallback() {
|
||||||
|
await super.connectedCallback();
|
||||||
|
this.style.display = 'contents';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): React.ReactElement | null {
|
||||||
|
return <Outlet />;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('react-router-outlet', ReactRouterOutletElement);
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Returns the current screen orientation type synchronously, or
|
||||||
|
* {@code 'unsupported'} if the Screen Orientation API is unavailable. Used by
|
||||||
|
* the bootstrap path to seed the server-side signal without waiting for a DOM
|
||||||
|
* event.
|
||||||
|
*/
|
||||||
|
export declare function currentScreenOrientationType(): string;
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation angle synchronously, or 0 if the
|
||||||
|
* Screen Orientation API is unavailable.
|
||||||
|
*/
|
||||||
|
export declare function currentScreenOrientationAngle(): number;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation type synchronously, or
|
||||||
|
* {@code 'unsupported'} if the Screen Orientation API is unavailable. Used by
|
||||||
|
* the bootstrap path to seed the server-side signal without waiting for a DOM
|
||||||
|
* event.
|
||||||
|
*/
|
||||||
|
export function currentScreenOrientationType() {
|
||||||
|
return screen.orientation?.type ?? 'unsupported';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation angle synchronously, or 0 if the
|
||||||
|
* Screen Orientation API is unavailable.
|
||||||
|
*/
|
||||||
|
export function currentScreenOrientationAngle() {
|
||||||
|
return screen.orientation?.angle ?? 0;
|
||||||
|
}
|
||||||
|
// Dispatch on document.body so the server-side ScreenOrientation facade
|
||||||
|
// (listening on the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(detail) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-screen-orientation-change', { detail }));
|
||||||
|
}
|
||||||
|
if (screen.orientation) {
|
||||||
|
screen.orientation.addEventListener('change', () => {
|
||||||
|
dispatch({
|
||||||
|
type: screen.orientation.type,
|
||||||
|
angle: screen.orientation.angle
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
function lockErrorCode(domExceptionName) {
|
||||||
|
switch (domExceptionName) {
|
||||||
|
case 'NotSupportedError':
|
||||||
|
return 'NOT_SUPPORTED';
|
||||||
|
case 'SecurityError':
|
||||||
|
return 'SECURITY';
|
||||||
|
case 'AbortError':
|
||||||
|
return 'ABORT';
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$wnd.Vaadin.Flow.screenOrientation = {
|
||||||
|
// Always resolves so the server-side .then(success, error) chain only
|
||||||
|
// receives the "error" branch on a bridge failure (lost connection, etc.).
|
||||||
|
// Rejected DOMExceptions are folded into the resolved result so the server
|
||||||
|
// can decode them as a record without forfeiting the JS-bridge error arm.
|
||||||
|
lock(type) {
|
||||||
|
if (!screen.orientation || typeof screen.orientation.lock !== 'function') {
|
||||||
|
return Promise.resolve({
|
||||||
|
success: false,
|
||||||
|
code: 'NOT_SUPPORTED',
|
||||||
|
message: 'Screen Orientation API is not supported in this browser.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return screen.orientation
|
||||||
|
.lock(type)
|
||||||
|
.then(() => ({ success: true }))
|
||||||
|
.catch((e) => {
|
||||||
|
const code = lockErrorCode(e.name);
|
||||||
|
const message = e.message ?? '';
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
code,
|
||||||
|
// The DOMException name is dropped once mapped to a typed code;
|
||||||
|
// keep it in the message for diagnostics when no code matches.
|
||||||
|
message: code === 'UNKNOWN' && e.name ? `${e.name}: ${message}` : message
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unlock() {
|
||||||
|
screen.orientation?.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=ScreenOrientation.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// Whether the server-side has asked us to hold the lock. The browser releases
|
||||||
|
// the lock whenever the tab is hidden; this flag is what lets the
|
||||||
|
// visibilitychange handler re-acquire silently when the tab returns.
|
||||||
|
let wanted = false;
|
||||||
|
let sentinel = null;
|
||||||
|
let visibilityListenerInstalled = false;
|
||||||
|
function dispatch(element, state) {
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-wake-lock-change', { detail: state }));
|
||||||
|
}
|
||||||
|
async function acquire(element) {
|
||||||
|
if (sentinel) {
|
||||||
|
return { state: 'granted' };
|
||||||
|
}
|
||||||
|
if (!window.isSecureContext || !('wakeLock' in navigator)) {
|
||||||
|
return {
|
||||||
|
state: 'error',
|
||||||
|
errorCode: 'UNSUPPORTED',
|
||||||
|
message: window.isSecureContext
|
||||||
|
? 'Screen Wake Lock API not implemented in this browser'
|
||||||
|
: 'Screen Wake Lock API requires a secure context (HTTPS or localhost)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const next = await navigator.wakeLock.request('screen');
|
||||||
|
// The user (or the browser) may have released the lock or the tab may have
|
||||||
|
// been hidden again while the request was in flight.
|
||||||
|
if (!wanted || document.visibilityState !== 'visible') {
|
||||||
|
try {
|
||||||
|
await next.release();
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore; releasing an already-released sentinel throws on some
|
||||||
|
// browsers and there is nothing meaningful to do here.
|
||||||
|
}
|
||||||
|
return { state: 'deferred' };
|
||||||
|
}
|
||||||
|
sentinel = next;
|
||||||
|
next.addEventListener('release', () => {
|
||||||
|
sentinel = null;
|
||||||
|
dispatch(element, 'RELEASED');
|
||||||
|
});
|
||||||
|
dispatch(element, 'ACTIVE');
|
||||||
|
return { state: 'granted' };
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const name = e?.name;
|
||||||
|
const errorCode = name === 'NotAllowedError' ? 'NOT_ALLOWED' : 'UNKNOWN';
|
||||||
|
return {
|
||||||
|
state: 'error',
|
||||||
|
errorCode,
|
||||||
|
message: e?.message ? String(e.message) : String(e)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function installVisibilityListener(element) {
|
||||||
|
if (visibilityListenerInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visibilityListenerInstalled = true;
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (wanted && !sentinel && document.visibilityState === 'visible') {
|
||||||
|
acquire(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.wakeLock = {
|
||||||
|
request(element) {
|
||||||
|
wanted = true;
|
||||||
|
installVisibilityListener(element);
|
||||||
|
if (document.visibilityState !== 'visible') {
|
||||||
|
// The browser will not grant a lock while the page is hidden; the
|
||||||
|
// visibilitychange listener will pick it up on the next 'visible'.
|
||||||
|
return Promise.resolve({ state: 'deferred' });
|
||||||
|
}
|
||||||
|
return acquire(element);
|
||||||
|
},
|
||||||
|
async release(element) {
|
||||||
|
wanted = false;
|
||||||
|
if (!sentinel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const current = sentinel;
|
||||||
|
sentinel = null;
|
||||||
|
try {
|
||||||
|
await current.release();
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore; the 'release' event listener installed in acquire() also
|
||||||
|
// dispatches RELEASED, so the state still reaches the server even when
|
||||||
|
// the explicit release() call rejects.
|
||||||
|
}
|
||||||
|
dispatch(element, 'RELEASED');
|
||||||
|
},
|
||||||
|
queryAvailability() {
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
return 'wakeLock' in navigator ? 'SUPPORTED' : 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=WakeLock.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Returns whether the current browser exposes the Web Share API
|
||||||
|
* (`navigator.share`). Used by the bootstrap path to seed the server-side
|
||||||
|
* support signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function isShareSupported(): boolean;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns whether the current browser exposes the Web Share API
|
||||||
|
* (`navigator.share`). Used by the bootstrap path to seed the server-side
|
||||||
|
* support signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function isShareSupported() {
|
||||||
|
return typeof navigator.share === 'function';
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=WebShare.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"WebShare.js","sourceRoot":"","sources":["../../../../src/main/frontend/WebShare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,CAAC;AAC/C,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Returns whether the current browser exposes the Web Share API\n * (`navigator.share`). Used by the bootstrap path to seed the server-side\n * support signal without waiting for a DOM event.\n */\nexport function isShareSupported(): boolean {\n return typeof navigator.share === 'function';\n}\n"]}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
||||||
|
import { timeOut } from '@vaadin/component-base/src/async.js';
|
||||||
|
import { ComboBoxPlaceholder } from '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
|
||||||
|
window.Vaadin.Flow.comboBoxConnector = {};
|
||||||
|
window.Vaadin.Flow.comboBoxConnector.initLazy = (comboBox) => {
|
||||||
|
// Check whether the connector was already initialized for the ComboBox
|
||||||
|
if (comboBox.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.$connector = {};
|
||||||
|
|
||||||
|
let cache = {};
|
||||||
|
const placeHolder = new window.Vaadin.ComboBoxPlaceholder();
|
||||||
|
|
||||||
|
let lastTypedFilter = '';
|
||||||
|
let lastRequestedRange = [-1, -1];
|
||||||
|
let lastRequestedFilter = '';
|
||||||
|
let needsDataCommunicatorReset = false;
|
||||||
|
|
||||||
|
const dataProvider = function (params, callback) {
|
||||||
|
if (params.pageSize != comboBox.pageSize) {
|
||||||
|
throw 'Invalid pageSize';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comboBox._clientSideFilter) {
|
||||||
|
if (cache[0]) {
|
||||||
|
performClientSideFilter(cache[0], params.filter, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First fetch: ignore the typed filter so we get the full dataset
|
||||||
|
params = { ...params, filter: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTypedFilter !== params.filter) {
|
||||||
|
cache = {};
|
||||||
|
lastTypedFilter = params.filter;
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
|
||||||
|
comboBox._filterDebouncer = Debouncer.debounce(
|
||||||
|
comboBox._filterDebouncer,
|
||||||
|
timeOut.after(comboBox._filterTimeout ?? 500),
|
||||||
|
() => {
|
||||||
|
// Filter cycled back to what server last received — force re-emit.
|
||||||
|
if (params.filter === lastRequestedFilter) {
|
||||||
|
needsDataCommunicatorReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.clearCache();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comboBox._filterDebouncer?.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If buffer-prefetch already cached this page, commit it without a server
|
||||||
|
// round-trip; otherwise ask the server.
|
||||||
|
if (cache[params.page]) {
|
||||||
|
callback(cache[params.page], comboBox.size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.$connector.requestPage(params.page, params.filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.getViewportRange = function () {
|
||||||
|
const indices = Array.from(comboBox._scroller?.children ?? [])
|
||||||
|
.map((child) => child.index)
|
||||||
|
.filter((index) => Number.isFinite(index))
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
if (indices.length === 0) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
return [indices[0], indices[indices.length - 1]];
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.requestPage = function (page, filter) {
|
||||||
|
let viewportRange = comboBox.$connector.getViewportRange();
|
||||||
|
const buffer = viewportRange[1] - viewportRange[0];
|
||||||
|
const sizeLimit = Number.isFinite(comboBox.size) ? comboBox.size : Number.POSITIVE_INFINITY;
|
||||||
|
viewportRange[0] = Math.max(viewportRange[0] - buffer, 0);
|
||||||
|
viewportRange[1] = Math.min(viewportRange[1] + buffer, sizeLimit - 1);
|
||||||
|
|
||||||
|
let viewportPageRange = [
|
||||||
|
Math.floor(viewportRange[0] / comboBox.pageSize),
|
||||||
|
Math.floor(viewportRange[1] / comboBox.pageSize)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Collapse to the requested page when it's outside the current viewport,
|
||||||
|
// so confirm() can resolve callbacks left behind by fast scrolling.
|
||||||
|
if (page < viewportPageRange[0] || page > viewportPageRange[1]) {
|
||||||
|
viewportPageRange = [page, page];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastRequestedRange[0] != viewportPageRange[0] || lastRequestedRange[1] != viewportPageRange[1]) {
|
||||||
|
const startIndex = viewportPageRange[0] * comboBox.pageSize;
|
||||||
|
const endIndex = (viewportPageRange[1] + 1) * comboBox.pageSize;
|
||||||
|
comboBox.$server.setViewportRange(startIndex, endIndex - startIndex, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsDataCommunicatorReset) {
|
||||||
|
comboBox.$server.resetDataCommunicator();
|
||||||
|
needsDataCommunicatorReset = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRequestedRange = viewportPageRange;
|
||||||
|
lastRequestedFilter = filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.clear = (start, length) => {
|
||||||
|
const { pageSize } = comboBox;
|
||||||
|
const firstPage = Math.floor(start / pageSize);
|
||||||
|
const lastPage = firstPage + Math.ceil(length / pageSize);
|
||||||
|
|
||||||
|
for (let page = firstPage; page < lastPage; page++) {
|
||||||
|
delete cache[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = firstPage * pageSize; index < lastPage * pageSize; index++) {
|
||||||
|
if (comboBox.filteredItems[index]) {
|
||||||
|
comboBox.filteredItems[index] = placeHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.filter = (item, filter) => {
|
||||||
|
filter = filter ? filter.toString().toLowerCase() : '';
|
||||||
|
return comboBox._getItemLabel(item, comboBox.itemLabelPath).toString().toLowerCase().indexOf(filter) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.set = (index, items, filter) => {
|
||||||
|
if (filter !== lastTypedFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index % comboBox.pageSize != 0) {
|
||||||
|
throw 'Got new data to index ' + index + ' which is not aligned with the page size of ' + comboBox.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pendingRequests } = comboBox.__dataProviderController.rootCache;
|
||||||
|
if (index === 0 && items.length === 0 && pendingRequests[0]) {
|
||||||
|
// Makes sure that the dataProvider callback is called even when server
|
||||||
|
// returns empty data set (no items match the filter).
|
||||||
|
cache[0] = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPageToSet = index / comboBox.pageSize;
|
||||||
|
const updatedPageCount = Math.ceil(items.length / comboBox.pageSize);
|
||||||
|
|
||||||
|
for (let i = 0; i < updatedPageCount; i++) {
|
||||||
|
let page = firstPageToSet + i;
|
||||||
|
let slice = items.slice(i * comboBox.pageSize, (i + 1) * comboBox.pageSize);
|
||||||
|
|
||||||
|
cache[page] = slice;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.updateData = (items) => {
|
||||||
|
const itemsMap = new Map(items.map((item) => [item.key, item]));
|
||||||
|
|
||||||
|
comboBox.filteredItems = comboBox.filteredItems.map((item) => {
|
||||||
|
return itemsMap.get(item.key) || item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.updateSize = function (newSize) {
|
||||||
|
if (!comboBox._clientSideFilter) {
|
||||||
|
// FIXME: It may be that this size set is unnecessary, since when
|
||||||
|
// providing data to combobox via callback we may use data's size.
|
||||||
|
// However, if this size reflect the whole data size, including
|
||||||
|
// data not fetched yet into client side, and combobox expect it
|
||||||
|
// to be set as such, the at least, we don't need it in case the
|
||||||
|
// filter is clientSide only, since it'll increase the height of
|
||||||
|
// the popup at only at first user filter to this size, while the
|
||||||
|
// filtered items count are less.
|
||||||
|
comboBox.size = newSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.reset = function () {
|
||||||
|
comboBox._filterDebouncer?.cancel();
|
||||||
|
comboBox._filterDebouncer = null;
|
||||||
|
cache = {};
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
lastTypedFilter = '';
|
||||||
|
comboBox.clearCache();
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.confirm = function (id, filter) {
|
||||||
|
if (filter !== lastTypedFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done applying changes from this batch, resolve pending
|
||||||
|
// callbacks
|
||||||
|
const { pendingRequests } = comboBox.__dataProviderController.rootCache;
|
||||||
|
Object.entries(pendingRequests).forEach(([page, callback]) => {
|
||||||
|
const items = cache[page];
|
||||||
|
|
||||||
|
if (comboBox._clientSideFilter && items) {
|
||||||
|
performClientSideFilter(items, comboBox.filter, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(items ?? [], comboBox.size);
|
||||||
|
delete cache[page];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let server know we're done
|
||||||
|
comboBox.$server.confirmUpdate(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform filter on client side (here) using the items from specified page
|
||||||
|
// and submitting the filtered items to specified callback.
|
||||||
|
// The filter used is the one from combobox, not the lastFilter stored since
|
||||||
|
// that may not reflect user's input.
|
||||||
|
const performClientSideFilter = function (page, filter, callback) {
|
||||||
|
let filteredItems = page;
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
filteredItems = page.filter((item) => comboBox.$connector.filter(item, filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(filteredItems, filteredItems.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent setting the custom value as the 'value'-prop automatically
|
||||||
|
comboBox.addEventListener('custom-value-set', (e) => e.preventDefault());
|
||||||
|
|
||||||
|
comboBox.itemClassNameGenerator = function (item) {
|
||||||
|
return item.className || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign last, after all `$connector` functions are defined.
|
||||||
|
comboBox.dataProvider = dataProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Vaadin.ComboBoxPlaceholder = ComboBoxPlaceholder;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
function getContainer(appId, nodeId) {
|
||||||
|
try {
|
||||||
|
return window.Vaadin.Flow.clients[appId].getByNodeId(nodeId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Could not get node %s from app %s', nodeId, appId);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connector for a context menu element.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} contextMenu
|
||||||
|
* @param {string} appId
|
||||||
|
*/
|
||||||
|
function initLazy(contextMenu, appId) {
|
||||||
|
if (contextMenu.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.$connector = {
|
||||||
|
/**
|
||||||
|
* Generates and assigns the items to the context menu.
|
||||||
|
*
|
||||||
|
* @param {number} nodeId
|
||||||
|
*/
|
||||||
|
generateItems(nodeId) {
|
||||||
|
const items = generateItemsTree(appId, nodeId);
|
||||||
|
|
||||||
|
contextMenu.items = items;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an items tree compatible with the context-menu web component
|
||||||
|
* by traversing the given Flow DOM tree of context menu item nodes
|
||||||
|
* whose root node is identified by the `nodeId` argument.
|
||||||
|
*
|
||||||
|
* The app id is required to access the store of Flow DOM nodes.
|
||||||
|
*
|
||||||
|
* @param {string} appId
|
||||||
|
* @param {number} nodeId
|
||||||
|
*/
|
||||||
|
function generateItemsTree(appId, nodeId) {
|
||||||
|
const container = getContainer(appId, nodeId);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(container.children).map((child) => {
|
||||||
|
const item = {
|
||||||
|
component: child,
|
||||||
|
checked: child._checked,
|
||||||
|
keepOpen: child._keepOpen,
|
||||||
|
className: child.className,
|
||||||
|
theme: child.__theme,
|
||||||
|
tooltip: child.tooltip,
|
||||||
|
tooltipPosition: child.tooltipPosition
|
||||||
|
};
|
||||||
|
// Do not hardcode tag name to allow `vaadin-menu-bar-item`
|
||||||
|
if (child._hasVaadinItemMixin && child._containerNodeId) {
|
||||||
|
item.children = generateItemsTree(appId, child._containerNodeId);
|
||||||
|
}
|
||||||
|
child._item = item;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the checked state for a context menu item.
|
||||||
|
*
|
||||||
|
* This method is supposed to be called when the context menu item is closed,
|
||||||
|
* so there is no need for triggering a re-render eagarly.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {boolean} checked
|
||||||
|
*/
|
||||||
|
function setChecked(component, checked) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.checked = checked;
|
||||||
|
|
||||||
|
// Set the attribute in the connector to show the checkmark
|
||||||
|
// without having to re-render the whole menu while opened.
|
||||||
|
if (component._item.keepOpen) {
|
||||||
|
component.toggleAttribute('menu-item-checked', checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the keep open state for a context menu item.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {boolean} keepOpen
|
||||||
|
*/
|
||||||
|
function setKeepOpen(component, keepOpen) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.keepOpen = keepOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the theme for a context menu item.
|
||||||
|
*
|
||||||
|
* This method is supposed to be called when the context menu item is closed,
|
||||||
|
* so there is no need for triggering a re-render eagarly.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {string | undefined | null} theme
|
||||||
|
*/
|
||||||
|
function setTheme(component, theme) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.theme = theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.contextMenuConnector = {
|
||||||
|
initLazy,
|
||||||
|
generateItemsTree,
|
||||||
|
setChecked,
|
||||||
|
setKeepOpen,
|
||||||
|
setTheme
|
||||||
|
};
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import * as Gestures from '@vaadin/component-base/src/gestures.js';
|
||||||
|
|
||||||
|
function init(target) {
|
||||||
|
if (target.$contextMenuTargetConnector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.$contextMenuTargetConnector = {
|
||||||
|
openOnHandler(e) {
|
||||||
|
// used by Grid to prevent context menu on selection column click
|
||||||
|
if (target.preventContextMenu && target.preventContextMenu(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// The menu is opened later, after a server round-trip, when the event has
|
||||||
|
// finished dispatching and `composedPath()` returns an empty array. Capture
|
||||||
|
// the composed path now so the menu can resolve the target inside a shadow
|
||||||
|
// root (e.g. a grid cell) instead of the retargeted host.
|
||||||
|
e.__composedPath = e.composedPath();
|
||||||
|
this.$contextMenuTargetConnector.openEvent = e;
|
||||||
|
let detail = {};
|
||||||
|
if (target.getContextMenuBeforeOpenDetail) {
|
||||||
|
detail = target.getContextMenuBeforeOpenDetail(e);
|
||||||
|
}
|
||||||
|
target.dispatchEvent(
|
||||||
|
new CustomEvent('vaadin-context-menu-before-open', {
|
||||||
|
detail: detail
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOpenOn(eventType) {
|
||||||
|
this.removeListener();
|
||||||
|
this.openOnEventType = eventType;
|
||||||
|
|
||||||
|
customElements.whenDefined('vaadin-context-menu').then(() => {
|
||||||
|
if (Gestures.gestures[eventType]) {
|
||||||
|
Gestures.addListener(target, eventType, this.openOnHandler);
|
||||||
|
} else {
|
||||||
|
target.addEventListener(eventType, this.openOnHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeListener() {
|
||||||
|
if (this.openOnEventType) {
|
||||||
|
if (Gestures.gestures[this.openOnEventType]) {
|
||||||
|
Gestures.removeListener(target, this.openOnEventType, this.openOnHandler);
|
||||||
|
} else {
|
||||||
|
target.removeEventListener(this.openOnEventType, this.openOnHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openMenu(contextMenu) {
|
||||||
|
contextMenu.open(this.openEvent);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeConnector() {
|
||||||
|
this.removeListener();
|
||||||
|
target.$contextMenuTargetConnector = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.contextMenuTargetConnector = { init };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// Full cdn version: 25.2.5-undefined
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { registerImporter, createChildrenDefinitions } from './copilot/figma-public/figma-api';
|
||||||
|
export type { FigmaNode, ImportMetadata } from './copilot/figma-public/figma-api';
|
||||||
|
export type { ComponentDefinition, ComponentDefinitionProperties } from './copilot/shared/flow-utils';
|
||||||
File diff suppressed because one or more lines are too long
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { c as t, n, t as r } from "./dom-utils-Cuv93-tQ.js";
|
||||||
|
import { i, n as a, r as o, t as s } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { a as c, i as l } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { i as u, n as d } from "./copilot-modes-wJyMqHUb.js";
|
||||||
|
//#region frontend/copilot/shared/section-panels/base-panel.ts
|
||||||
|
var f, p = e((() => {
|
||||||
|
i(), c(), s(), t(), u(), f = class extends o {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this.eventBusRemovers = [], this.messageHandlers = {}, this.handleESC = (e) => {
|
||||||
|
let t = a.getPanelByTag(this.tagName);
|
||||||
|
d().appInteractable && t && !t.individual || e.key === "Escape" && r(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getPreferredWidth() {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
getPreferredHeight() {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
getPreferredMaxWidth() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
onEventBus(e, t) {
|
||||||
|
this.eventBusRemovers.push(l.on(e, t));
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.addESCListener();
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback(), this.eventBusRemovers.forEach((e) => e()), this.removeESCListener();
|
||||||
|
}
|
||||||
|
addESCListener() {
|
||||||
|
document.addEventListener("keydown", this.handleESC);
|
||||||
|
}
|
||||||
|
removeESCListener() {
|
||||||
|
document.removeEventListener("keydown", this.handleESC);
|
||||||
|
}
|
||||||
|
onCommand(e, t) {
|
||||||
|
this.messageHandlers[e] = t;
|
||||||
|
}
|
||||||
|
handleMessage(e) {
|
||||||
|
return this.messageHandlers[e.command] ? (this.messageHandlers[e.command].call(this, e), !0) : !1;
|
||||||
|
}
|
||||||
|
repositionInPopover(e) {
|
||||||
|
let t = Math.max(this.scrollHeight, this.offsetHeight);
|
||||||
|
if (t === 0) return;
|
||||||
|
let n = e.getAttribute("for");
|
||||||
|
if (!n) return;
|
||||||
|
let r = e.parentElement?.querySelector(`#${n}`);
|
||||||
|
if (!r) return;
|
||||||
|
let i = r.getBoundingClientRect(), a = i.top - 16, o = window.innerHeight - 16 - i.bottom, s = (e.position ?? e.getAttribute("position") ?? "bottom").startsWith("top"), c = s ? a : o;
|
||||||
|
c >= t || (s ? o : a) <= c || (e.position = s ? "bottom" : "top", e._overlayElement?._updatePosition?.());
|
||||||
|
}
|
||||||
|
requestLayoutUpdate() {
|
||||||
|
let e = this.localName;
|
||||||
|
if (a.positionUpdatedManually(e)) return Promise.resolve();
|
||||||
|
let t = a.getPanelByTag(e);
|
||||||
|
return t ? new Promise((r) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let i = n(this, "vaadin-dialog");
|
||||||
|
if (!i) {
|
||||||
|
let e = n(this, "vaadin-popover");
|
||||||
|
e && this.repositionInPopover(e), r();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let o = this.parentElement?.getBoundingClientRect();
|
||||||
|
if (!o || o.width === 0 && o.height === 0) {
|
||||||
|
r();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let s = i._overlayElement, c = s?.shadowRoot?.querySelector("[part=\"overlay\"]"), l = s?.shadowRoot?.querySelector("[part=\"content\"]"), u = s?.shadowRoot?.querySelector("[part=\"footer\"]");
|
||||||
|
if (!c || !l) {
|
||||||
|
r();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let d = Math.max(this.scrollWidth, this.offsetWidth, o.width), f = Math.max(this.scrollHeight, this.offsetHeight, o.height), p = c.getBoundingClientRect(), m = l.getBoundingClientRect(), h = Math.max(0, p.width - m.width), g = Math.max(0, p.height - m.height), _ = u?.getBoundingClientRect(), v = !!_ && _.width > 0 && _.height > 0, y = v ? Math.max(0, p.left - _.left) + Math.max(0, _.right - p.right) : 0, b = v ? Math.max(0, p.top - _.top) + Math.max(0, _.bottom - p.bottom) : 0, x = this.getPreferredMaxWidth(), S = Math.floor(window.innerHeight * 2 / 3), C = Math.max(0, this.getPreferredWidth()), w = Math.max(0, this.getPreferredHeight()), T = Math.min(x, Math.max(120, window.innerWidth - 32)), E = Math.min(S, Math.max(120, window.innerHeight - 32)), D = Math.max(120, Math.min(T, Math.max(C, Math.ceil(d + h + y)))), O = Math.max(120, Math.min(E, Math.max(w, Math.ceil(f + g + b)))), k = `${D}px`, A = `${O}px`;
|
||||||
|
i.setAttribute("width", k), i.setAttribute("height", A), i.width = k, i.height = A;
|
||||||
|
let j = t.position, M = Number.parseFloat(i.getAttribute("top") ?? ""), N = Number.parseFloat(i.getAttribute("left") ?? ""), P = j?.top ?? (Number.isNaN(M) ? 0 : M), F = j?.left ?? (Number.isNaN(N) ? 0 : N), I = document.querySelector("copilot-main")?.shadowRoot?.querySelector(`copilot-toolbar #${e}-toolbar-btn`), L = P, R = F;
|
||||||
|
if (I) {
|
||||||
|
let e = I.getBoundingClientRect(), t = e.top - 16 - 16, n = window.innerHeight - 16 - e.bottom - 16;
|
||||||
|
L = t >= O || t >= n ? e.top - O - 16 : e.bottom + 16, R = e.left + e.width / 2 - D / 2;
|
||||||
|
}
|
||||||
|
L = Math.max(16, Math.min(L, window.innerHeight - 16 - O)), R = Math.max(16, Math.min(R, window.innerWidth - 16 - D)), a.updatePanel(e, { position: {
|
||||||
|
top: L,
|
||||||
|
left: R,
|
||||||
|
width: D,
|
||||||
|
height: O
|
||||||
|
} }, !1), r();
|
||||||
|
});
|
||||||
|
}) : Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
export { p as n, f as t };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//#region \0rolldown/runtime.js
|
||||||
|
var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescriptor, r = Object.getOwnPropertyNames, i = Object.getPrototypeOf, a = Object.prototype.hasOwnProperty, o = (e, t) => () => (e && (t = e(e = 0)), t), s = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports), c = (e, i, o, s) => {
|
||||||
|
if (i && typeof i == "object" || typeof i == "function") for (var c = r(i), l = 0, u = c.length, d; l < u; l++) d = c[l], !a.call(e, d) && d !== o && t(e, d, {
|
||||||
|
get: ((e) => i[e]).bind(null, d),
|
||||||
|
enumerable: !(s = n(i, d)) || s.enumerable
|
||||||
|
});
|
||||||
|
return e;
|
||||||
|
}, l = (n, r, a) => (a = n == null ? {} : e(i(n)), c(r || !n || !n.__esModule ? t(a, "default", {
|
||||||
|
value: n,
|
||||||
|
enumerable: !0
|
||||||
|
}) : a, n)), u = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
|
||||||
|
if (typeof require < "u") return require.apply(this, arguments);
|
||||||
|
throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
export { l as i, o as n, u as r, s as t };
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
//#region frontend/copilot/shared/consts.ts
|
||||||
|
var t, n, r, i, a, o, s, c, l, u = e((() => {
|
||||||
|
t = "copilot-", n = "25.2.5", r = "undefined", i = r === "undefined" ? "" : r, a = "attention-required", o = "https://plugins.jetbrains.com/plugin/23758-vaadin", s = "https://marketplace.visualstudio.com/items?itemName=vaadin.vaadin-vscode", c = "https://marketplace.eclipse.org/content/vaadin-tools", l = {
|
||||||
|
sectionId: "custom-components",
|
||||||
|
sectionName: "Custom Components"
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
export { o as a, s as c, c as i, u as l, i as n, t as o, l as r, n as s, a as t };
|
||||||
+402
@@ -0,0 +1,402 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { L as t, R as n, at as r, dt as i, n as a, o, r as s, t as c, u as l } from "./icons-CwakCZgK.js";
|
||||||
|
import { a as u, c as d, i as f, l as p, o as m } from "./consts-CSALuSsm.js";
|
||||||
|
import { a as h, d as g, i as _, l as v, n as y, o as b, r as x, s as S, t as C } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { a as w, i as T, n as E, r as D } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { i as O, n as k } from "./copilot-modes-wJyMqHUb.js";
|
||||||
|
import { i as A, o as j } from "./copilot-error-handler-9OpssAH1.js";
|
||||||
|
import { n as M, t as N } from "./early-project-state-LGwavSyI.js";
|
||||||
|
import { i as P, o as F, s as I, t as L } from "./copilot-development-setup-user-guide-utils-DzEVQbWO.js";
|
||||||
|
import { n as R, t as z } from "./base-panel-Fr0D1ZcU.js";
|
||||||
|
//#region frontend/copilot/copilot-development-setup-user-guide.ts
|
||||||
|
function B(e, t) {
|
||||||
|
if (!t) return !0;
|
||||||
|
let [n, r, i] = t.split(".").map((e) => Number.parseInt(e)), [a, o, s] = e.split(".").map((e) => Number.parseInt(e));
|
||||||
|
if (n < a) return !0;
|
||||||
|
if (n === a) {
|
||||||
|
if (r < o) return !0;
|
||||||
|
if (r === o) return i < s;
|
||||||
|
}
|
||||||
|
return !1;
|
||||||
|
}
|
||||||
|
var V, H, U, W, G, K;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
a(), S(), C(), s(), r(), p(), D(), I(), w(), j(), t(), M(), O(), R(), _(), b(), H = "https://github.com/JetBrains/JetBrainsRuntime/releases", U = "Download complete", W = (V = class extends z {
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
super(), this.javaPluginSectionOpened = !1, this.hotswapSectionOpened = !1, this.hotswapTab = "hotswapagent", this.downloadStatusMessages = [], this.downloadProgress = 0, this.onDownloadStatusUpdate = this.downloadStatusUpdate.bind(this), this.handleESC = (e) => {
|
||||||
|
k().appInteractable || e.key === "Escape" && y.openPanel(K.tag);
|
||||||
|
}, this.reaction(() => [N.jdkInfo, E.idePluginState], () => {
|
||||||
|
E.idePluginState && (!E.idePluginState.ide || !E.idePluginState.active ? this.javaPluginSectionOpened = !0 : (!new Set(["vscode", "intellij"]).has(E.idePluginState.ide) || !E.idePluginState.active) && (this.javaPluginSectionOpened = !1)), N.jdkInfo && P() !== "success" && (this.hotswapSectionOpened = !0);
|
||||||
|
}, { fireImmediately: !0 });
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents"), T.on("set-up-vs-code-hotswap-status", this.onDownloadStatusUpdate);
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback(), T.off("set-up-vs-code-hotswap-status", this.onDownloadStatusUpdate);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
let e = {
|
||||||
|
intellij: E.idePluginState?.ide === "intellij",
|
||||||
|
vscode: E.idePluginState?.ide === "vscode",
|
||||||
|
eclipse: E.idePluginState?.ide === "eclipse",
|
||||||
|
idePluginInstalled: !!E.idePluginState?.active
|
||||||
|
};
|
||||||
|
return l`
|
||||||
|
${this.renderPluginSection(e)}
|
||||||
|
<hr class="border-b border-e-0 border-s-0 border-t-0 mx-4 my-0" />
|
||||||
|
${this.renderHotswapSection(e)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderPluginSection(e) {
|
||||||
|
let t = "";
|
||||||
|
e.intellij ? t = "IntelliJ" : e.vscode ? t = "VS Code" : e.eclipse && (t = "Eclipse");
|
||||||
|
let n, r;
|
||||||
|
e.vscode || e.intellij ? e.idePluginInstalled ? (n = `Plugin for ${t} installed`, r = this.renderPluginInstalledContent()) : (n = `Plugin for ${t} not installed`, r = this.renderPluginIsNotInstalledContent(e)) : e.eclipse ? (n = e.idePluginInstalled ? "Eclipse plugin installed" : "Eclipse plugin not installed", r = e.idePluginInstalled ? this.renderPluginInstalledContent() : this.renderEclipsePluginContent()) : (n = "No IDE found", r = this.renderNoIdePluginContent());
|
||||||
|
let a = e.idePluginInstalled ? c.checkCircle : c.warning;
|
||||||
|
return l`
|
||||||
|
<vaadin-details
|
||||||
|
theme="reverse"
|
||||||
|
.opened=${this.javaPluginSectionOpened}
|
||||||
|
@opened-changed=${(e) => {
|
||||||
|
i(() => {
|
||||||
|
this.javaPluginSectionOpened = e.detail.value;
|
||||||
|
}), this.requestLayoutUpdate();
|
||||||
|
}}>
|
||||||
|
<vaadin-details-summary class="px-4 py-3.5" slot="summary">
|
||||||
|
<div class="flex gap-1.5">
|
||||||
|
<vaadin-icon
|
||||||
|
class="${e.idePluginInstalled ? "text-teal-11" : "text-ruby-11"}"
|
||||||
|
.svg=${a}></vaadin-icon>
|
||||||
|
<span>${n}</span>
|
||||||
|
</div>
|
||||||
|
</vaadin-details-summary>
|
||||||
|
<div>${r}</div>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderNoIdePluginContent() {
|
||||||
|
return l`
|
||||||
|
<div class="flex flex-col gap-2 pb-4 px-4">
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
For the best development experience, use
|
||||||
|
<a class="gap-1 inline-flex items-center" href="https://code.visualstudio.com"
|
||||||
|
><vaadin-icon class="icon-sm" .svg=${c.vsCode}></vaadin-icon>Visual Studio Code</a
|
||||||
|
>
|
||||||
|
or
|
||||||
|
<a class="gap-1 inline-flex items-center" href="https://www.jetbrains.com/idea"
|
||||||
|
><vaadin-icon class="icon-sm" .svg=${c.intelliJ}></vaadin-icon>IntelliJ IDEA</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderEclipsePluginContent() {
|
||||||
|
return l`
|
||||||
|
<div class="flex flex-col gap-2 items-start pb-4 px-4">
|
||||||
|
<p class="m-0 text-secondary">Install the Vaadin Eclipse Plugin to ensure a smooth development workflow</p>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
Installing the plugin is not required, but strongly recommended. Some Vaadin Copilot functionality, such as
|
||||||
|
undo, will not function optimally without the plugin.
|
||||||
|
</p>
|
||||||
|
<vaadin-button
|
||||||
|
class="mt-2"
|
||||||
|
@click="${() => {
|
||||||
|
window.open(f, "_blank");
|
||||||
|
}}"
|
||||||
|
>Install from Eclipse Marketplace
|
||||||
|
<vaadin-icon slot="suffix" .svg="${c.arrowOutward}"></vaadin-icon>
|
||||||
|
</vaadin-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderPluginInstalledContent() {
|
||||||
|
return l`
|
||||||
|
<p class="m-0 pb-4 px-4 text-secondary">You have a running plugin. Enjoy your awesome development workflow!</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderPluginIsNotInstalledContent(e) {
|
||||||
|
let t = null, n = "Install from Marketplace";
|
||||||
|
return e.intellij ? (t = u, n = "Install from JetBrains Marketplace") : e.vscode ? (t = d, n = "Install from VSCode Marketplace") : e.eclipse && (t = f, n = "Install from Eclipse Marketplace"), l`
|
||||||
|
<div class="flex flex-col gap-2 items-start pb-4 px-4">
|
||||||
|
<p class="m-0 text-secondary">Install the Vaadin IDE Plugin to ensure a smooth development workflow</p>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
Installing the plugin is not required, but strongly recommended. Some Vaadin Copilot functionality, such as
|
||||||
|
undo, will not function optimally without the plugin.
|
||||||
|
</p>
|
||||||
|
${t ? l` <vaadin-button
|
||||||
|
class="mt-2"
|
||||||
|
@click="${() => {
|
||||||
|
window.open(t, "_blank");
|
||||||
|
}}"
|
||||||
|
>${n}
|
||||||
|
<vaadin-icon slot="suffix" .svg="${c.arrowOutward}"></vaadin-icon>
|
||||||
|
</vaadin-button>` : o}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
getActiveTabContent(e, t) {
|
||||||
|
return this.hotswapTab === "jrebel" ? t.jrebel ? this.renderJRebelInstalledContent() : this.renderJRebelNotInstalledContent() : e.intellij ? this.renderIntelliJHotswapHint() : e.vscode ? this.renderVSCodeHotswapHint() : this.renderHotswapAgentNotInstalledContent(e);
|
||||||
|
}
|
||||||
|
renderHotswapSection(e) {
|
||||||
|
let { jdkInfo: t } = N;
|
||||||
|
if (!t) return o;
|
||||||
|
let n = P(), r = F(), a, s;
|
||||||
|
n === "success" ? (a = c.checkCircle, s = "Java Hotswap is enabled") : n === "warning" ? (a = c.warning, s = "Java Hotswap is not enabled") : n === "error" && (a = c.warning, s = "Java Hotswap is partially enabled");
|
||||||
|
let u = this.getActiveTabContent(e, t), d = r === "jrebel" ? this.renderJRebelInstalledContent() : this.renderHotswapAgentInstalledContent(), f = this.hotswapTab === "hotswapagent" ? 0 : 1;
|
||||||
|
return l` <vaadin-details
|
||||||
|
theme="reverse"
|
||||||
|
.opened=${this.hotswapSectionOpened}
|
||||||
|
@opened-changed=${(e) => {
|
||||||
|
i(() => {
|
||||||
|
this.hotswapSectionOpened = e.detail.value;
|
||||||
|
}), this.requestLayoutUpdate();
|
||||||
|
}}>
|
||||||
|
<vaadin-details-summary class="px-4 py-3.5" slot="summary">
|
||||||
|
<div class="flex gap-1.5">
|
||||||
|
<vaadin-icon
|
||||||
|
class="${n === "success" ? "text-teal-11" : "text-ruby-11"}"
|
||||||
|
.svg=${a}></vaadin-icon>
|
||||||
|
<span>${s}</span>
|
||||||
|
</div>
|
||||||
|
</vaadin-details-summary>
|
||||||
|
<div>
|
||||||
|
${r === "none" ? l`
|
||||||
|
<vaadin-tabs
|
||||||
|
.selected=${f}
|
||||||
|
@selected-changed=${(e) => {
|
||||||
|
this.hotswapTab = e.detail.value === 0 ? "hotswapagent" : "jrebel";
|
||||||
|
}}>
|
||||||
|
<vaadin-tab>Hotswap Agent</vaadin-tab>
|
||||||
|
<vaadin-tab>JRebel</vaadin-tab>
|
||||||
|
</vaadin-tabs>
|
||||||
|
${u}
|
||||||
|
` : l`${d}`}
|
||||||
|
</div>
|
||||||
|
</vaadin-details>`;
|
||||||
|
}
|
||||||
|
renderJRebelNotInstalledContent() {
|
||||||
|
return l`
|
||||||
|
<div class="flex flex-col gap-2 p-4">
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
<a class="inline-flex items-center" href="https://www.jrebel.com"
|
||||||
|
>JRebel <vaadin-icon class="icon-sm" .svg=${c.arrowOutward}></vaadin-icon
|
||||||
|
></a>
|
||||||
|
is a commercial hotswap solution. Vaadin detects the JRebel Agent and automatically reloads the application in
|
||||||
|
the browser after the Java changes have been hotpatched.
|
||||||
|
</p>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
Go to
|
||||||
|
<a
|
||||||
|
class="inline-flex items-center"
|
||||||
|
href="https://www.jrebel.com/products/jrebel/learn"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
https://www.jrebel.com/products/jrebel/learn
|
||||||
|
<vaadin-icon class="icon-sm" .svg=${c.arrowOutward}></vaadin-icon
|
||||||
|
></a>
|
||||||
|
to get started.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderHotswapAgentNotInstalledContent(e) {
|
||||||
|
return l` <div class="p-2">${[
|
||||||
|
this.renderJavaRunningInDebugModeSection(),
|
||||||
|
this.renderHotswapAgentJdkSection(e),
|
||||||
|
this.renderInstallHotswapAgentJdkSection(e),
|
||||||
|
this.renderHotswapAgentVersionSection(),
|
||||||
|
this.renderHotswapAgentMissingArgParam(e)
|
||||||
|
]}</div> `;
|
||||||
|
}
|
||||||
|
renderIntelliJHotswapHint() {
|
||||||
|
return l` <div class="flex flex-col gap-2 p-4">
|
||||||
|
<h3 class="font-semibold my-0 text-sm">Use 'Debug using Hotswap Agent' launch configuration</h3>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
Vaadin IntelliJ plugin offers launch mode that does not require any manual configuration!
|
||||||
|
</p>
|
||||||
|
<p class="m-0 text-secondary">
|
||||||
|
In order to run recommended launch configuration, you should click three dots right next to Debug button and
|
||||||
|
select
|
||||||
|
<code class="bg-gray-3 dark:bg-gray-7 font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>Debug using Hotswap Agent</code
|
||||||
|
>
|
||||||
|
option.
|
||||||
|
</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
renderVSCodeHotswapHint() {
|
||||||
|
return l` <div>
|
||||||
|
<h3 class="font-semibold my-0 text-sm">Use 'Debug (hotswap)'</h3>
|
||||||
|
With Vaadin Visual Studio Code extension you can run Hotswap Agent without any manual configuration required!
|
||||||
|
<p class="m-0">
|
||||||
|
Click
|
||||||
|
<code class="bg-gray-3 dark:bg-gray-7 font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>Debug (hotswap)</code
|
||||||
|
>
|
||||||
|
within your main class to debug application using Hotswap Agent.
|
||||||
|
</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
renderJavaRunningInDebugModeSection() {
|
||||||
|
return l`
|
||||||
|
<vaadin-details theme="reverse" .opened="${!N.jdkInfo?.runningInJavaDebugMode}">
|
||||||
|
<vaadin-details-summary class="p-2" slot="summary">Run Java in debug mode</vaadin-details-summary>
|
||||||
|
<p class="m-0 pb-2 px-2 text-secondary">Start the application in debug mode in the IDE.</p>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderHotswapAgentMissingArgParam(e) {
|
||||||
|
return l`
|
||||||
|
<vaadin-details theme="reverse" .opened="${!(N.jdkInfo?.runningWitHotswap && N.jdkInfo?.runningWithExtendClassDef)}">
|
||||||
|
<vaadin-details-summary class="p-2" slot="summary">Enable HotswapAgent</vaadin-details-summary>
|
||||||
|
<div class="flex flex-col gap-2 pb-2 px-2 text-secondary">
|
||||||
|
<ul class="m-0 ps-4">
|
||||||
|
${e.intellij ? l`<li>Launch as mentioned in the previous step</li>` : o}
|
||||||
|
${e.intellij ? l`<li>
|
||||||
|
To manually configure IntelliJ, add the
|
||||||
|
<code
|
||||||
|
class="bg-gray-3 dark:bg-gray-7 break-all font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>-XX:HotswapAgent=fatjar -XX:+AllowEnhancedClassRedefinition -XX:+UpdateClasses</code
|
||||||
|
>
|
||||||
|
JVM arguments when launching the application.
|
||||||
|
</li>` : l`<li>
|
||||||
|
Add the
|
||||||
|
<code
|
||||||
|
class="bg-gray-3 dark:bg-gray-7 break-all font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>-XX:HotswapAgent=fatjar -XX:+AllowEnhancedClassRedefinition -XX:+UpdateClasses</code
|
||||||
|
>
|
||||||
|
JVM arguments when launching the application.
|
||||||
|
</li>`}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderHotswapAgentJdkSection(e) {
|
||||||
|
let t = N.jdkInfo?.extendedClassDefCapable, n = this.downloadStatusMessages?.[this.downloadStatusMessages.length - 1] === U, r = this.downloadProgress > 0 ? l`<vaadin-progress-bar .value="${this.downloadProgress}" min="0" max="1"></vaadin-progress-bar>` : o, i = n ? l`<h3 class="font-semibold my-0 text-sm">
|
||||||
|
Go to VS Code and launch the 'Debug using Hotswap Agent' configuration
|
||||||
|
</h3>` : o;
|
||||||
|
return l`
|
||||||
|
<vaadin-details theme="reverse" .opened="${!t}">
|
||||||
|
<vaadin-details-summary class="p-2" slot="summary">Run using JetBrains Runtime JDK</vaadin-details-summary>
|
||||||
|
<div class="flex flex-col gap-2 pb-2 px-2 text-secondary">
|
||||||
|
<p class="m-0">JetBrains Runtime provides much better hotswapping compared to other JDKs.</p>
|
||||||
|
<ul class="m-0 ps-4">
|
||||||
|
${e.intellij && B("1.3.0", E.idePluginState?.version) ? l` <li>Upgrade to the latest IntelliJ plugin</li>` : o}
|
||||||
|
${e.intellij ? l` <li>Launch the application in IntelliJ using "Debug using Hotswap Agent"</li>` : o}
|
||||||
|
${e.vscode ? l` <li>
|
||||||
|
<a href @click="${(e) => this.downloadJetbrainsRuntime(e)}"
|
||||||
|
>Let Copilot download and set up JetBrains Runtime for VS Code</a
|
||||||
|
>
|
||||||
|
${r}
|
||||||
|
<ul>
|
||||||
|
${this.downloadStatusMessages.map((e) => l`<li>${e}</li>`)} ${i}
|
||||||
|
</ul>
|
||||||
|
</li>` : o}
|
||||||
|
<li>
|
||||||
|
${e.intellij || e.vscode ? l`If there is a problem, you can manually
|
||||||
|
<a target="_blank" href="${H}">download JetBrains Runtime JDK</a> and set up your
|
||||||
|
debug configuration to use it.` : l`<a target="_blank" href="${H}">Download JetBrains Runtime JDK</a> and set up
|
||||||
|
your debug configuration to use it.`}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderInstallHotswapAgentJdkSection(e) {
|
||||||
|
let t = N.jdkInfo?.hotswapAgentFound, n = N.jdkInfo?.extendedClassDefCapable;
|
||||||
|
return l`
|
||||||
|
<vaadin-details theme="reverse" .opened="${!t}">
|
||||||
|
<vaadin-details-summary class="p-2" slot="summary"> Install HotswapAgent </vaadin-details-summary>
|
||||||
|
<div class="flex flex-col gap-2 pb-2 px-2 text-secondary">
|
||||||
|
<p class="m-0">
|
||||||
|
Hotswap Agent provides application level support for hot reloading, such as reinitalizing Vaadin @Route or
|
||||||
|
@BrowserCallable classes when they are updated.
|
||||||
|
</p>
|
||||||
|
<ul class="m-0 ps-4">
|
||||||
|
${e.intellij ? l`<li>Launch as mentioned in the previous step</li>` : o}
|
||||||
|
${!e.intellij && !n ? l`<li>First install JetBrains Runtime as mentioned in the step above.</li>` : o}
|
||||||
|
${e.intellij ? l`<li>
|
||||||
|
To manually configure IntelliJ, download HotswapAgent and install the jar file as
|
||||||
|
<code class="bg-gray-3 dark:bg-gray-7 font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>[JAVA_HOME]/lib/hotswap/hotswap-agent.jar</code
|
||||||
|
>
|
||||||
|
in the JetBrains Runtime JDK. Note that the file must be renamed to exactly match this path.
|
||||||
|
</li>` : l`<li>
|
||||||
|
Download HotswapAgent and install the jar file as
|
||||||
|
<code class="bg-gray-3 dark:bg-gray-7 font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>[JAVA_HOME]/lib/hotswap/hotswap-agent.jar</code
|
||||||
|
>
|
||||||
|
in the JetBrains Runtime JDK. Note that the file must be renamed to exactly match this path.
|
||||||
|
</li>`}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderHotswapAgentVersionSection() {
|
||||||
|
if (!N.jdkInfo?.hotswapAgentFound) return o;
|
||||||
|
let e = N.jdkInfo?.hotswapVersionOk, t = N.jdkInfo?.hotswapVersion, n = N.jdkInfo?.hotswapAgentLocation;
|
||||||
|
return l`
|
||||||
|
<vaadin-details theme="reverse" .opened="${!e}">
|
||||||
|
<vaadin-details-summary class="p-2" slot="summary">Hotswap version requires update</vaadin-details-summary>
|
||||||
|
<div>
|
||||||
|
HotswapAgent version ${t} is in use
|
||||||
|
<a target="_blank" href="https://github.com/HotswapProjects/HotswapAgent/releases"
|
||||||
|
>Download the latest HotswapAgent</a
|
||||||
|
>
|
||||||
|
and place it in
|
||||||
|
<code class="bg-gray-3 dark:bg-gray-7 font-mono inline-flex px-1.5 py-px rounded-md text-body text-xs"
|
||||||
|
>${n}</code
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</vaadin-details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderJRebelInstalledContent() {
|
||||||
|
return l` <p class="m-0 pb-2 px-2">JRebel is in use. Enjoy your awesome development workflow!</p> `;
|
||||||
|
}
|
||||||
|
renderHotswapAgentInstalledContent() {
|
||||||
|
return l`
|
||||||
|
<p class="m-0 pb-4 px-4 text-secondary">Hotswap agent is in use. Enjoy your awesome development workflow!</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
async downloadJetbrainsRuntime(e) {
|
||||||
|
return e.target.disabled = !0, e.preventDefault(), this.downloadStatusMessages = [], n(`${m}set-up-vs-code-hotswap`, {}, (e) => {
|
||||||
|
e.data.error ? (A("Error downloading JetBrains runtime", e.data.error), this.downloadStatusMessages = [...this.downloadStatusMessages, "Download failed"]) : this.downloadStatusMessages = [...this.downloadStatusMessages, U];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadStatusUpdate(e) {
|
||||||
|
let t = e.detail.progress;
|
||||||
|
t ? this.downloadProgress = t : this.downloadStatusMessages = [...this.downloadStatusMessages, e.detail.message];
|
||||||
|
}
|
||||||
|
}, V.NAME = "copilot-development-setup-user-guide", V), h([v()], W.prototype, "javaPluginSectionOpened", void 0), h([v()], W.prototype, "hotswapSectionOpened", void 0), h([v()], W.prototype, "hotswapTab", void 0), h([v()], W.prototype, "downloadStatusMessages", void 0), h([v()], W.prototype, "downloadProgress", void 0), W = h([g(W.NAME)], W), G = class extends x {
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents");
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return l`<vaadin-button
|
||||||
|
id="close"
|
||||||
|
@click="${() => y.closePanel(K.tag)}"
|
||||||
|
>Close
|
||||||
|
</vaadin-button>`;
|
||||||
|
}
|
||||||
|
}, G = h([g("copilot-development-setup-footer-actions")], G), K = {
|
||||||
|
header: "Development Workflow",
|
||||||
|
tag: L,
|
||||||
|
footerActionsTag: "copilot-development-setup-footer-actions",
|
||||||
|
individual: !0
|
||||||
|
}, globalThis.Vaadin.copilot.plugins.push({ init(e) {
|
||||||
|
e.addPanel(K);
|
||||||
|
} }), y.addPanel(K);
|
||||||
|
}))();
|
||||||
|
export { G as CopilotDevelopmentSetupFooterActions, W as CopilotDevelopmentSetupUserGuide, K as copilotDevelopmentSetupPanelConfig };
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { _ as t, g as n } from "./icons-CwakCZgK.js";
|
||||||
|
import { l as r, o as i } from "./consts-CSALuSsm.js";
|
||||||
|
import { n as a, t as o } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { n as s, r as c } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { r as l, t as u } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { n as d, t as f } from "./early-project-state-LGwavSyI.js";
|
||||||
|
//#region frontend/copilot/shared/copilot-development-setup-user-guide-utils.ts
|
||||||
|
function p() {
|
||||||
|
l("use-dev-workflow-guide"), a.openPanel(y);
|
||||||
|
}
|
||||||
|
function m() {
|
||||||
|
let e = f.jdkInfo;
|
||||||
|
return e ? e.jrebel ? "success" : e.hotswapAgentFound ? !e.hotswapVersionOk || !e.runningWithExtendClassDef || !e.runningWitHotswap || !e.runningInJavaDebugMode ? "error" : "success" : "warning" : null;
|
||||||
|
}
|
||||||
|
function h() {
|
||||||
|
let e = f.jdkInfo;
|
||||||
|
return !e || m() !== "success" ? "none" : e.jrebel ? "jrebel" : e.runningWitHotswap ? "hotswap" : "none";
|
||||||
|
}
|
||||||
|
function g() {
|
||||||
|
return s.idePluginState !== void 0 && !s.idePluginState.active ? "warning" : "success";
|
||||||
|
}
|
||||||
|
function _() {
|
||||||
|
if (!f.jdkInfo) return { status: "success" };
|
||||||
|
let e = m(), t = g();
|
||||||
|
return e === "warning" ? t === "warning" ? {
|
||||||
|
status: "warning",
|
||||||
|
message: "IDE Plugin, Hotswap"
|
||||||
|
} : {
|
||||||
|
status: "warning",
|
||||||
|
message: "Hotswap is not enabled"
|
||||||
|
} : t === "warning" ? {
|
||||||
|
status: "warning",
|
||||||
|
message: "IDE Plugin is not active"
|
||||||
|
} : e === "error" ? {
|
||||||
|
status: "error",
|
||||||
|
message: "Hotswap is partially enabled"
|
||||||
|
} : { status: "success" };
|
||||||
|
}
|
||||||
|
function v() {
|
||||||
|
t(`${i}get-dev-setup-info`, {}), window.Vaadin.copilot.eventbus.on("copilot-get-dev-setup-info-response", (e) => {
|
||||||
|
if (e.detail.content) {
|
||||||
|
let t = JSON.parse(e.detail.content);
|
||||||
|
s.setIdePluginState(t.ideInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var y, b = e((() => {
|
||||||
|
c(), n(), r(), o(), u(), d(), y = "copilot-development-setup-user-guide";
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
export { g as a, p as c, m as i, v as n, h as o, _ as r, b as s, y as t };
|
||||||
+345
@@ -0,0 +1,345 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { $ as t, C as n, Q as r, et as i, n as a, o, r as s, t as c, u as l, y as u } from "./icons-CwakCZgK.js";
|
||||||
|
import { a as d, d as f, i as p, l as m, n as h, o as g, r as _, s as v, t as y } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { n as b, r as x } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { r as S, t as C } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { n as w, t as T } from "./copilot-stored-machine-state-D6qB_Peh.js";
|
||||||
|
import { n as E, r as D } from "./copilot-notification-CCNJdNg4.js";
|
||||||
|
import { n as O, t as k } from "./early-project-state-LGwavSyI.js";
|
||||||
|
import { a as A, i as j, s as M, t as N } from "./copilot-development-setup-user-guide-utils-DzEVQbWO.js";
|
||||||
|
//#region frontend/copilot/copilot-devtools/copilot-devtools.ts
|
||||||
|
var P, F, I, L, R;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
v(), s(), p(), x(), a(), M(), i(), y(), C(), O(), T(), E(), n(), g(), P = "bg-[linear-gradient(to_right,var(--amber-3),var(--amber-5),var(--amber-3),var(--amber-6))] dark:bg-[linear-gradient(to_right,var(--amber-5),var(--amber-7),var(--amber-5),var(--amber-8))]", F = "bg-[linear-gradient(to_right,var(--blue-3),var(--blue-5),var(--blue-3),var(--blue-6))] dark:bg-[linear-gradient(to_right,var(--blue-4),var(--blue-6),var(--blue-4),var(--blue-7))]", I = "bg-[linear-gradient(to_right,var(--ruby-3),var(--ruby-5),var(--ruby-3),var(--ruby-6))] dark:bg-[linear-gradient(to_right,var(--ruby-4),var(--ruby-6),var(--ruby-4),var(--ruby-7))]", L = "bg-[linear-gradient(to_right,var(--teal-3),var(--teal-5),var(--teal-3),var(--teal-6))] dark:bg-[linear-gradient(to_right,var(--teal-4),var(--teal-6),var(--teal-4),var(--teal-7))]", R = class extends _ {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this._helpExpanded = !1;
|
||||||
|
}
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("flex", "flex-col");
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return l`
|
||||||
|
<header class="flex items-center pe-2 ps-4 py-2">
|
||||||
|
<h2 class="font-bold gap-1 me-auto my-0 text-xs uppercase">Vaadin Copilot</h2>
|
||||||
|
<vaadin-button
|
||||||
|
aria-label="Close"
|
||||||
|
theme="icon tertiary"
|
||||||
|
@click=${() => {
|
||||||
|
this.closePopover();
|
||||||
|
}}>
|
||||||
|
<vaadin-icon .svg="${c.close}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Close"></vaadin-tooltip>
|
||||||
|
</vaadin-button>
|
||||||
|
</header>
|
||||||
|
<div class="flex flex-col gap-4 pb-4 px-4">
|
||||||
|
${this.renderCopilotServerWarning()} ${this.renderUserButton()} ${this.renderDevelopmentWorkflow()}
|
||||||
|
${this.renderWelcomeToVersion()}
|
||||||
|
<div class="bg-gray-3 dark:bg-gray-6 flex flex-col rounded-md">
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.handleAppInfoClick}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.info}"></vaadin-icon>
|
||||||
|
App Info
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button @click="${this.handleAppLogClick}" class="border-0 h-auto justify-start py-2" theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.terminal}"></vaadin-icon>
|
||||||
|
App Log
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.handleFeaturesClick}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.listAlt}"></vaadin-icon>
|
||||||
|
Features
|
||||||
|
</vaadin-button>
|
||||||
|
${k.springSecurityEnabled ? l`
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.handleImpersonateAppUserClick}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.accountCircle}"></vaadin-icon>
|
||||||
|
Impersonate App User
|
||||||
|
</vaadin-button>
|
||||||
|
` : o}
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-3 dark:bg-gray-6 flex flex-col rounded-md">
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.handleFeedbackClick}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.feedback}"></vaadin-icon>
|
||||||
|
Feedback
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.toggleHelpAndSupport}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.help}"></vaadin-icon>
|
||||||
|
Help & Support
|
||||||
|
<vaadin-icon
|
||||||
|
slot="suffix"
|
||||||
|
.svg="${this._helpExpanded ? c.keyboardArrowUp : c.keyboardArrowDown}"></vaadin-icon>
|
||||||
|
</vaadin-button>
|
||||||
|
${this._helpExpanded ? this.renderHelpLinks() : o}
|
||||||
|
<vaadin-button
|
||||||
|
@click="${this.handleSettingsClick}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.settings}"></vaadin-icon>
|
||||||
|
Settings
|
||||||
|
</vaadin-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderUserButton() {
|
||||||
|
let e = b.userInfo?.validLicense, t = e ? P : F, n = e ? "text-amber-12 dark:text-amber-11" : "text-blue-12 dark:text-blue-11", r = this.getUserName() !== "Log in";
|
||||||
|
return l`
|
||||||
|
<vaadin-button
|
||||||
|
@click=${this.handleUserLoginClick}
|
||||||
|
class="animate-gradient ${t} border-0 h-auto justify-start py-2 text-start ${r ? "gap-3 px-3" : "items-start"}">
|
||||||
|
${r ? this.renderUserImage() : l`<vaadin-icon
|
||||||
|
class="text-blue-12 dark:text-blue-11"
|
||||||
|
slot="prefix"
|
||||||
|
.svg="${c.login}"></vaadin-icon>`}
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span>${this.getUserName()}</span>
|
||||||
|
<span class="${n} text-xs">${this.getLicenseType()}</span>
|
||||||
|
</span>
|
||||||
|
</vaadin-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderCopilotServerWarning() {
|
||||||
|
return b.userInfo?.copilotServerReached === !1 ? l`
|
||||||
|
<vaadin-button
|
||||||
|
@click=${this.showCopilotServerTroubleshooting}
|
||||||
|
class="animate-gradient ${I} border-0 h-auto items-start justify-start py-2 text-start"
|
||||||
|
data-test-id="copilot-server-unreachable">
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Click here for troubleshooting"></vaadin-tooltip>
|
||||||
|
<vaadin-icon class="text-ruby-12 dark:text-ruby-11" slot="prefix" .svg="${c.warning}"></vaadin-icon>
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span>Copilot server is unreachable</span>
|
||||||
|
<span class="text-ruby-12 dark:text-ruby-11 text-xs">Check your proxy or firewall settings.</span>
|
||||||
|
</span>
|
||||||
|
</vaadin-button>
|
||||||
|
` : o;
|
||||||
|
}
|
||||||
|
showCopilotServerTroubleshooting() {
|
||||||
|
D({
|
||||||
|
type: t.WARNING,
|
||||||
|
message: "Copilot server is unreachable",
|
||||||
|
details: u(l`
|
||||||
|
<p class="m-0">Copilot could not connect to the Copilot server.</p>
|
||||||
|
<p class="mb-0 mt-2">To troubleshoot:</p>
|
||||||
|
<ol class="mb-0 mt-1 ps-4">
|
||||||
|
<li>Verify that this machine can reach the Copilot server and complete its TLS handshake:</li>
|
||||||
|
<li class="list-none mt-1">
|
||||||
|
<code
|
||||||
|
class="bg-gray-3 dark:bg-gray-6 box-border inline-block pe-8 ps-3 py-1.75 relative rounded-md text-xs w-full"
|
||||||
|
><copilot-copy></copilot-copy>curl -Iv https://copilot.vaadin.com</code
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>Check that your firewall or network policy allows access to <code>copilot.vaadin.com</code>.</li>
|
||||||
|
<li>If you use a proxy, verify its settings and that it trusts the server's SSL certificate.</li>
|
||||||
|
</ol>
|
||||||
|
`),
|
||||||
|
delay: 3e4
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renderWelcomeToVersion() {
|
||||||
|
let e = b.projectVersionReleaseNoteInfo;
|
||||||
|
return e === null || w.getMostRecentReleaseNoteDismissed() || !e.mostRecentVersion || !e.url ? o : l`
|
||||||
|
<div class="flex relative">
|
||||||
|
<vaadin-button
|
||||||
|
id="release-note-btn"
|
||||||
|
data-test-id="release-note-btn"
|
||||||
|
class="border-0 h-auto items-start justify-start px-3 py-2 text-start w-full"
|
||||||
|
@click="${(t) => {
|
||||||
|
window.open(e.url, "_blank");
|
||||||
|
}}">
|
||||||
|
<vaadin-icon class="text-blue-11" slot="prefix" .svg="${c.info}"></vaadin-icon>
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span>Welcome to Vaadin ${e.vaadinVersion}</span>
|
||||||
|
<span class="text-blue-11 text-xs">Click for release notes</span>
|
||||||
|
</span>
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button
|
||||||
|
class="absolute end-0 top-0"
|
||||||
|
id="dismiss-release-note-item"
|
||||||
|
theme="icon tertiary"
|
||||||
|
@click="${(e) => {
|
||||||
|
e.stopPropagation(), w.setMostRecentReleaseNoteDismissed(!0);
|
||||||
|
}}"
|
||||||
|
><vaadin-icon .svg="${c.close}"></vaadin-icon
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Dismiss"></vaadin-tooltip>
|
||||||
|
</vaadin-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderUserImage() {
|
||||||
|
return b.userInfo?.portraitUrl ? l`<img
|
||||||
|
alt="${this.getUserName()}"
|
||||||
|
class="rounded-full size-8 object-cover"
|
||||||
|
slot="prefix"
|
||||||
|
src="https://vaadin.com${b.userInfo.portraitUrl}" />` : o;
|
||||||
|
}
|
||||||
|
renderDevelopmentWorkflow() {
|
||||||
|
let e = j(), t = A(), n = this.getDevelopmentWorkflowConfig(e, t), r = n?.bgClass ?? "", i = n?.colorClass ?? "", a = this.resolveIcon(n), o = n?.rotateIcon ? `rotate-180 ${i}` : i, s = this.resolveTitle(n), c = n?.displayMessage ?? "";
|
||||||
|
return l`
|
||||||
|
<vaadin-button
|
||||||
|
data-test-id="development-workflow-btn"
|
||||||
|
@click="${this.handleDevelopmentWorkflowClick}"
|
||||||
|
class="animation-delay-4000 animate-gradient ${r} border-0 h-auto items-start justify-start py-2 text-start">
|
||||||
|
<vaadin-icon class="${o}" slot="prefix" .svg="${a}"></vaadin-icon>
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span>${s}</span>
|
||||||
|
<span class="text-xs ${i}">${c}</span>
|
||||||
|
</span>
|
||||||
|
</vaadin-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
getDevelopmentWorkflowConfig(e, t) {
|
||||||
|
let n = {
|
||||||
|
bgClass: L,
|
||||||
|
colorClass: "text-teal-11"
|
||||||
|
};
|
||||||
|
if (e === "warning" && t === "warning") return {
|
||||||
|
...n,
|
||||||
|
icon: c.wbIncandescent,
|
||||||
|
rotateIcon: !0,
|
||||||
|
title: "IDE plugin & Hotswap recommended",
|
||||||
|
combinedTitle: !0,
|
||||||
|
displayMessage: "Enable both for optimal development workflow"
|
||||||
|
};
|
||||||
|
if (e === "warning") return {
|
||||||
|
...n,
|
||||||
|
icon: c.wbIncandescent,
|
||||||
|
rotateIcon: !0,
|
||||||
|
title: "Hotswap recommended",
|
||||||
|
displayMessage: "Applies changes without restarting"
|
||||||
|
};
|
||||||
|
if (t === "warning") return {
|
||||||
|
...n,
|
||||||
|
icon: c.code,
|
||||||
|
getIcon: !0,
|
||||||
|
title: "IDE plugin recommended",
|
||||||
|
getTitle: !0,
|
||||||
|
displayMessage: "Simplifies Hotswap setup & config"
|
||||||
|
};
|
||||||
|
if (e === "error") return {
|
||||||
|
bgClass: I,
|
||||||
|
colorClass: "text-ruby-11",
|
||||||
|
icon: c.error,
|
||||||
|
title: "Hotswap partially enabled",
|
||||||
|
displayMessage: "View details"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
resolveIcon(e) {
|
||||||
|
return e ? e.getIcon ? this.getIdeIcon() : e.icon : c.bolt;
|
||||||
|
}
|
||||||
|
resolveTitle(e) {
|
||||||
|
return e ? e.combinedTitle ? this.getCombinedTitle() : e.getTitle ? this.getIdePluginName() : e.title : "Development Workflow";
|
||||||
|
}
|
||||||
|
getUserName() {
|
||||||
|
return [b.userInfo?.firstName, b.userInfo?.lastName].filter(Boolean).join(" ") || "Log in";
|
||||||
|
}
|
||||||
|
getLicenseType() {
|
||||||
|
return b.userInfo?.validLicense ? "" : "Unlock all Copilot features, including AI";
|
||||||
|
}
|
||||||
|
getIdeIcon() {
|
||||||
|
switch (b.idePluginState?.ide) {
|
||||||
|
case "intellij": return c.intelliJ;
|
||||||
|
case "vscode": return c.vsCode;
|
||||||
|
case "eclipse": return c.eclipse;
|
||||||
|
default: return c.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIdePluginName() {
|
||||||
|
switch (b.idePluginState?.ide) {
|
||||||
|
case "intellij": return "Vaadin plugin for IntelliJ";
|
||||||
|
case "vscode": return "Vaadin extension for VS Code";
|
||||||
|
case "eclipse": return "Vaadin plugin for Eclipse";
|
||||||
|
default: return "IDE plugin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCombinedTitle() {
|
||||||
|
switch (b.idePluginState?.ide) {
|
||||||
|
case "intellij": return "IntelliJ plugin & Hotswap recommended";
|
||||||
|
case "vscode": return "VS Code extension & Hotswap recommended";
|
||||||
|
case "eclipse": return "Eclipse plugin & Hotswap recommended";
|
||||||
|
default: return "IDE plugin & Hotswap recommended";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closePopover() {
|
||||||
|
let e = this.closest("vaadin-popover");
|
||||||
|
e && (e.opened = !1);
|
||||||
|
}
|
||||||
|
handleUserLoginClick() {
|
||||||
|
if (b.userInfo?.validLicense) {
|
||||||
|
window.open("https://vaadin.com/myaccount", "_blank", "noopener");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
b.setLoginCheckActive(!0);
|
||||||
|
}
|
||||||
|
handleDevelopmentWorkflowClick() {
|
||||||
|
S("use-dev-workflow-guide"), h.openPanel(N), this.closePopover();
|
||||||
|
}
|
||||||
|
handleAppInfoClick() {
|
||||||
|
h.openPanel(r.INFO), this.closePopover();
|
||||||
|
}
|
||||||
|
handleAppLogClick() {
|
||||||
|
h.openPanel(r.LOG), this.closePopover();
|
||||||
|
}
|
||||||
|
handleFeaturesClick() {
|
||||||
|
h.openPanel(r.FEATURES), this.closePopover();
|
||||||
|
}
|
||||||
|
handleImpersonateAppUserClick() {
|
||||||
|
h.openPanel(r.IMPERSONATOR), this.closePopover();
|
||||||
|
}
|
||||||
|
handleSettingsClick() {
|
||||||
|
h.openPanel(r.SETTINGS), this.closePopover();
|
||||||
|
}
|
||||||
|
handleFeedbackClick() {
|
||||||
|
h.openPanel(r.FEEDBACK), this.closePopover();
|
||||||
|
}
|
||||||
|
toggleHelpAndSupport() {
|
||||||
|
this._helpExpanded = !this._helpExpanded;
|
||||||
|
}
|
||||||
|
renderHelpLinks() {
|
||||||
|
return l`
|
||||||
|
<div class="flex flex-col ps-4">
|
||||||
|
${[
|
||||||
|
{
|
||||||
|
label: "Forum",
|
||||||
|
icon: "forum",
|
||||||
|
url: "https://vaadin.com/forum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Docs",
|
||||||
|
icon: "article",
|
||||||
|
url: "https://vaadin.com/docs/latest/tools/copilot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "GitHub Issues",
|
||||||
|
icon: "github",
|
||||||
|
url: "https://github.com/vaadin/copilot/issues"
|
||||||
|
}
|
||||||
|
].map(({ label: e, icon: t, url: n }) => l`
|
||||||
|
<vaadin-button
|
||||||
|
@click="${() => window.open(n, "_blank", "noopener")}"
|
||||||
|
class="border-0 h-auto justify-start py-2"
|
||||||
|
theme="tertiary">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c[t]}"></vaadin-icon>
|
||||||
|
${e}
|
||||||
|
</vaadin-button>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}, d([m()], R.prototype, "_helpExpanded", void 0), R = d([f("copilot-devtools")], R);
|
||||||
|
}))();
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { $ as t, C as n, L as r, R as i, T as a, at as o, et as s, l as c, lt as l, n as u, o as d, r as f, s as ee, t as p, u as m, y as h } from "./icons-CwakCZgK.js";
|
||||||
|
import { l as te, o as g } from "./consts-CSALuSsm.js";
|
||||||
|
import { a as _, i as v, n as y, r as b } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { t as ne } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { i as x, n as S, r as C, t as w } from "./directive-DWLihZIi.js";
|
||||||
|
import { n as T, t as E } from "./copilot-stored-machine-state-D6qB_Peh.js";
|
||||||
|
import { n as D } from "./copilot-notification-CCNJdNg4.js";
|
||||||
|
import { n as O } from "./early-project-state-LGwavSyI.js";
|
||||||
|
//#region node_modules/lit-html/directives/unsafe-html.js
|
||||||
|
var k, A, j = e((() => {
|
||||||
|
c(), C(), k = class extends S {
|
||||||
|
constructor(e) {
|
||||||
|
if (super(e), this.it = d, e.type !== x.CHILD) throw Error(this.constructor.directiveName + "() can only be used in child bindings");
|
||||||
|
}
|
||||||
|
render(e) {
|
||||||
|
if (e === d || e == null) return this._t = void 0, this.it = e;
|
||||||
|
if (e === ee) return e;
|
||||||
|
if (typeof e != "string") throw Error(this.constructor.directiveName + "() called with a non-string value");
|
||||||
|
if (e === this.it) return this._t;
|
||||||
|
this.it = e;
|
||||||
|
let t = [e];
|
||||||
|
return t.raw = t, this._t = {
|
||||||
|
_$litType$: this.constructor.resultType,
|
||||||
|
strings: t,
|
||||||
|
values: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, k.directiveName = "unsafeHTML", k.resultType = 1, A = w(k);
|
||||||
|
})), M = e((() => {
|
||||||
|
j();
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
//#region frontend/copilot/shared/copilot-userinfo-util.ts
|
||||||
|
function re() {
|
||||||
|
let e = y.userInfo;
|
||||||
|
return !e || e.copilotProjectCannotLeaveLocalhost ? !1 : T.isSendErrorReportsAllowed();
|
||||||
|
}
|
||||||
|
var ie = e((() => {
|
||||||
|
ne(), b(), $(), E(), n(), D(), s();
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
//#region frontend/copilot/shared/hotswap-utils.ts
|
||||||
|
function N() {
|
||||||
|
return y.idePluginState?.supportedActions?.find((e) => e === "restartApplication");
|
||||||
|
}
|
||||||
|
function P() {
|
||||||
|
i(`${g}plugin-restart-application`, {}, () => {}).catch((e) => {
|
||||||
|
z("Error restarting server", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var F = e((() => {
|
||||||
|
r(), te(), $(), _(), D(), s(), b(), O();
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
//#region frontend/copilot/shared/copilot-error-handler.ts
|
||||||
|
function I(e) {
|
||||||
|
if (e === void 0) return !1;
|
||||||
|
let t = Object.keys(e);
|
||||||
|
return t.length === 1 && t.includes("message") || t.length >= 3 && t.includes("message") && t.includes("exceptionMessage") && t.includes("exceptionStacktrace");
|
||||||
|
}
|
||||||
|
function L() {
|
||||||
|
let e = "A server restart is required";
|
||||||
|
return N() ? h(m`${e}${R()}`) : h(m`${e}`);
|
||||||
|
}
|
||||||
|
function R() {
|
||||||
|
return N() ? m`<vaadin-button
|
||||||
|
class="mt-2"
|
||||||
|
theme="primary"
|
||||||
|
@click=${(e) => {
|
||||||
|
let t = e.target;
|
||||||
|
t.disabled = !0, t.innerText = "Restarting...", P();
|
||||||
|
}}>
|
||||||
|
Restart Now
|
||||||
|
</vaadin-button>` : d;
|
||||||
|
}
|
||||||
|
function z(e, n) {
|
||||||
|
let r = I(n) ? n.exceptionMessage ?? n.message : n, i = {
|
||||||
|
type: t.ERROR,
|
||||||
|
message: "Copilot internal error",
|
||||||
|
details: e + (r ? `\n${r}` : "")
|
||||||
|
};
|
||||||
|
I(n) && n.suggestRestart && N() && (i.details = h(m`${e}<br />${r} ${R()}`), i.delay = 3e4), a(i);
|
||||||
|
let o;
|
||||||
|
o = n instanceof Error ? n.stack : I(n) ? n?.exceptionStacktrace?.join("\n") : n?.toString(), v.emit("system-info-with-callback", {
|
||||||
|
callback: (t) => v.send("copilot-error", {
|
||||||
|
message: `Copilot internal error: ${e}`,
|
||||||
|
details: o,
|
||||||
|
versions: t
|
||||||
|
}),
|
||||||
|
notify: !1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function B(e) {
|
||||||
|
return e?.stack?.includes("cdn.vaadin.com/copilot") || e?.stack?.includes("/copilot/copilot/") || e?.stack?.includes("/copilot/copilot-private/");
|
||||||
|
}
|
||||||
|
function V() {
|
||||||
|
let e = window.onerror;
|
||||||
|
window.onerror = (t, n, r, i, a) => {
|
||||||
|
if (B(a)) {
|
||||||
|
z(t.toString(), a);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e && e(t, n, r, i, a);
|
||||||
|
}, l((e) => {
|
||||||
|
B(e) && z("", e);
|
||||||
|
});
|
||||||
|
let t = window.Vaadin.ConsoleErrors;
|
||||||
|
if (Array.isArray(t)) for (let e of t) Array.isArray(e) ? Q.push(...e) : Q.push(e);
|
||||||
|
U((e) => Q.push(e));
|
||||||
|
}
|
||||||
|
function H(e, t, n, r, i, a) {
|
||||||
|
let o = { ...e }, s = window.Vaadin.copilot.tree, c = window.Vaadin.copilot.customComponentHandler;
|
||||||
|
o.nodes.forEach((e) => {
|
||||||
|
e.node = s.allNodesFlat.find((t) => {
|
||||||
|
if (!t.isFlowComponent) return !1;
|
||||||
|
let n = t.node;
|
||||||
|
return n.uiId === e.uiId && n.nodeId === e.nodeId;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let l = [];
|
||||||
|
n && l.push(`Error Message -> ${n}`), r && l.push(`Error Details -> ${r}`), l.push(`Active Level -> ${c.getActiveDrillDownContext() ? c.getActiveDrillDownContext()?.nameAndIdentifier : "No active level"}`), o.nodes.length > 0 && (l.push("\nRelevant Nodes:"), o.nodes.forEach((e) => {
|
||||||
|
l.push(`${e.relevance} -> ${e.node?.nameAndIdentifier ?? "Node not found"}`);
|
||||||
|
})), o.relevantPairs.length > 0 && (l.push("\nAdditional Info:"), o.relevantPairs.forEach((e) => {
|
||||||
|
l.push(`${e.relevance} -> ${e.value}`);
|
||||||
|
})), a && (l.push("Versions"), l.push(a));
|
||||||
|
let u = {
|
||||||
|
name: "Info",
|
||||||
|
content: l.join("\n")
|
||||||
|
};
|
||||||
|
o.items.unshift(u), i && o.items.push({
|
||||||
|
name: "Stacktrace",
|
||||||
|
content: i
|
||||||
|
}), v.emit("system-info-with-callback", {
|
||||||
|
callback: (e) => {
|
||||||
|
o.items.push({
|
||||||
|
name: "Versions",
|
||||||
|
content: e
|
||||||
|
}), t(o);
|
||||||
|
},
|
||||||
|
notify: !1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function U(e) {
|
||||||
|
let n = window.Vaadin.ConsoleErrors;
|
||||||
|
window.Vaadin.ConsoleErrors = { push: (r) => {
|
||||||
|
r[0] === null || r[0] === void 0 || (r[0].type !== void 0 && r[0].message !== void 0 ? e({
|
||||||
|
type: r[0].type,
|
||||||
|
message: r[0].message,
|
||||||
|
internal: !!r[0].internal,
|
||||||
|
details: r[0].details,
|
||||||
|
link: r[0].link
|
||||||
|
}) : e({
|
||||||
|
type: t.ERROR,
|
||||||
|
message: r.map((e) => W(e)).join(" "),
|
||||||
|
internal: !1
|
||||||
|
}), n.push(r));
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
function W(e) {
|
||||||
|
return e.message ? e.message.toString() : e.toString();
|
||||||
|
}
|
||||||
|
var G, K, q, J, Y, X, Z, Q, $ = e((() => {
|
||||||
|
f(), M(), o(), _(), b(), ie(), F(), n(), s(), u(), G = (e, t) => e.error ? (Z(e.error, t), !0) : !1, K = (e, n, r) => {
|
||||||
|
a({
|
||||||
|
type: t.ERROR,
|
||||||
|
message: e,
|
||||||
|
details: h(m`${q(n)} ${Y(r)}`),
|
||||||
|
delay: 3e4
|
||||||
|
});
|
||||||
|
}, q = (e) => e.length === 0 ? d : e.length < 80 ? J(e) : m`<vaadin-details class="flex flex-col peer w-full" theme="no-padding reverse">
|
||||||
|
<vaadin-details-summary class="font-medium -ms-3 self-start text-secondary text-xs" slot="summary"
|
||||||
|
>Details</vaadin-details-summary
|
||||||
|
>
|
||||||
|
${J(e)}
|
||||||
|
</vaadin-details>`, J = (e) => m`<code class="codeblock"
|
||||||
|
>${A(e)}<copilot-copy class="absolute end-0 flex top-0"></copilot-copy
|
||||||
|
></code>`, Y = (e) => e ? m`
|
||||||
|
<vaadin-button
|
||||||
|
class="peer-has-[[opened]]:mt-2"
|
||||||
|
@click="${() => {
|
||||||
|
e && v.emit("submit-exception-report-clicked", e);
|
||||||
|
}}"
|
||||||
|
id="report-issue">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${p.bugReport}"></vaadin-icon>
|
||||||
|
Report Issue</vaadin-button
|
||||||
|
>
|
||||||
|
` : d, X = (e, t, n, r, i) => {
|
||||||
|
let a = y.newVaadinVersionState?.versions?.length === 0;
|
||||||
|
i && a ? H(i, (n) => {
|
||||||
|
K(e, t, n);
|
||||||
|
}, e, t, n) : K(e, t), re() && (r?.templateData && typeof r.templateData == "string" && r.templateData.startsWith("data") && (r.templateData = "<IMAGE_DATA>"), v.emit("system-info-with-callback", {
|
||||||
|
callback: (t) => v.send("copilot-error", {
|
||||||
|
message: e,
|
||||||
|
details: String(n).replace(" ", "\n") + (r ? `\n \nRequest: \n${JSON.stringify(r)}\n` : ""),
|
||||||
|
versions: t
|
||||||
|
}),
|
||||||
|
notify: !1
|
||||||
|
})), y.clearOperationWaitsHmrUpdate();
|
||||||
|
}, Z = (e, t) => {
|
||||||
|
X(e.message, e.exceptionMessage ?? "", e.exceptionStacktrace?.join("\n") ?? "", t, e.exceptionReport);
|
||||||
|
}, Q = [];
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
export { G as a, F as c, M as d, A as f, z as i, P as l, Q as n, $ as o, L as r, V as s, U as t, N as u };
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { $ as t, et as n, n as r, o as i, r as a, t as o, u as s } from "./icons-CwakCZgK.js";
|
||||||
|
import { a as c, d as l, i as u, l as d, o as f, r as p, s as m } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { n as h, r as g } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { r as _, t as v } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { c as y, l as b, o as x, r as S, u as C } from "./copilot-error-handler-9OpssAH1.js";
|
||||||
|
import { n as w, t as T } from "./copilot-stored-machine-state-D6qB_Peh.js";
|
||||||
|
import { n as E, r as D } from "./copilot-notification-CCNJdNg4.js";
|
||||||
|
import { n as O, t as k } from "./base-panel-Fr0D1ZcU.js";
|
||||||
|
//#region frontend/copilot/shared/copilot-experimental-features.ts
|
||||||
|
var A, j, M, N, P, F, I, L, R = e((() => {
|
||||||
|
T(), g(), A = (e) => h.userInfo?.copilotExperimentalFeatureFlag === !0 && w.isExperimentalFeatureEnabled(e), j = {
|
||||||
|
id: "theme-from-image",
|
||||||
|
name: "Theme from Image",
|
||||||
|
description: "Generate a custom theme based on an image you provide.",
|
||||||
|
enabled: () => A(j),
|
||||||
|
available: () => h.appTheme === "lumo",
|
||||||
|
requiresReload: !1
|
||||||
|
}, M = {
|
||||||
|
id: "ai-docs-assistant",
|
||||||
|
name: "AI Docs Assistant",
|
||||||
|
description: "AI-powered Vaadin documentation assistant.",
|
||||||
|
enabled: () => A(M),
|
||||||
|
available: () => !0,
|
||||||
|
requiresReload: !1
|
||||||
|
}, N = {
|
||||||
|
id: "testbench-test-recorder",
|
||||||
|
name: "TestBench Test Recorder",
|
||||||
|
description: "Record user interactions to generate end-to-end Vaadin TestBench tests automatically.",
|
||||||
|
enabled: () => A(N),
|
||||||
|
available: () => !0,
|
||||||
|
requiresReload: !0
|
||||||
|
}, P = {
|
||||||
|
id: "i18n",
|
||||||
|
name: "Internationalization",
|
||||||
|
description: "Edit and manage translations for your application.",
|
||||||
|
enabled: () => A(P),
|
||||||
|
available: () => !0,
|
||||||
|
requiresReload: !0
|
||||||
|
}, F = {
|
||||||
|
id: "annotations",
|
||||||
|
name: "Annotations",
|
||||||
|
description: "Add and manage comments and annotations on your application views.",
|
||||||
|
enabled: () => A(F),
|
||||||
|
available: () => !0,
|
||||||
|
requiresReload: !0
|
||||||
|
}, I = {
|
||||||
|
id: "ui-test-generator",
|
||||||
|
name: "UI Test Generator",
|
||||||
|
description: "Generate Playwright UI Test for your application views.",
|
||||||
|
enabled: () => A(I),
|
||||||
|
available: () => !0,
|
||||||
|
requiresReload: !0
|
||||||
|
}, L = [
|
||||||
|
j,
|
||||||
|
M,
|
||||||
|
N,
|
||||||
|
P,
|
||||||
|
F,
|
||||||
|
I
|
||||||
|
];
|
||||||
|
})), z, B, V, H;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
x(), v(), a(), m(), R(), E(), n(), T(), g(), y(), r(), u(), O(), f(), z = window.Vaadin.devTools, B = class extends k {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this.toggledFeaturesThatAreRequiresServerRestart = [];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents");
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
let e = h.userInfo?.copilotExperimentalFeatureFlag;
|
||||||
|
return s`
|
||||||
|
<div class="flex flex-col gap-6 px-4 py-0.5">
|
||||||
|
<div class="border-dashed flex flex-col divide-y">
|
||||||
|
${h.featureFlags.slice().sort((e, t) => e.title.localeCompare(t.title)).map((e) => s`
|
||||||
|
<div class="flex gap-2 justify-between py-3.5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label id="${e.id}-label">${e.title}</label>
|
||||||
|
<a
|
||||||
|
class="flex gap-0.5 text-xs"
|
||||||
|
href="${e.moreInfoLink}"
|
||||||
|
id="${e.id}-desc"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>More info<vaadin-icon class="icon-sm" .svg="${o.arrowOutward}"></vaadin-icon
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<copilot-toggle-button
|
||||||
|
accessible-name-ref="${e.id}-label"
|
||||||
|
accessible-desc-ref="${e.id}-desc"
|
||||||
|
?checked=${e.enabled}
|
||||||
|
@on-change=${(t) => this.toggleFeatureFlag(t, e)}>
|
||||||
|
</copilot-toggle-button>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
${e ? s`<h3 class="font-semibold my-0 text-sm">Copilot Experimental Features</h3>
|
||||||
|
<div class="border-dashed flex flex-col divide-y">
|
||||||
|
${L.filter((e) => e.available()).slice().sort((e, t) => e.name.localeCompare(t.name)).map((e) => s`
|
||||||
|
<div class="flex gap-2 justify-between py-3.5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label id="${e.id}-label">${e.description}</label>
|
||||||
|
</div>
|
||||||
|
<copilot-toggle-button
|
||||||
|
accessible-name-ref="${e.id}-label"
|
||||||
|
?checked=${w.isExperimentalFeatureEnabled(e)}
|
||||||
|
@on-change=${(t) => this.toggleExperimentalFeatureFlag(t, e)}>
|
||||||
|
</copilot-toggle-button>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
</div>` : i}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
toggleFeatureFlag(e, n) {
|
||||||
|
let r = e.target.checked;
|
||||||
|
_("use-feature", {
|
||||||
|
source: "toggle",
|
||||||
|
enabled: r,
|
||||||
|
id: n.id
|
||||||
|
}), z.frontendConnection ? (z.frontendConnection.send("setFeature", {
|
||||||
|
featureId: n.id,
|
||||||
|
enabled: r
|
||||||
|
}), n.requiresServerRestart && h.toggleServerRequiringFeatureFlag(n), D({
|
||||||
|
type: t.INFORMATION,
|
||||||
|
message: `“${n.title}” ${r ? "enabled" : "disabled"}`,
|
||||||
|
details: n.requiresServerRestart ? S() : void 0,
|
||||||
|
dismissId: `feature${n.id}${r ? "Enabled" : "Disabled"}`
|
||||||
|
}), n.id === "copilotExperimentalFeatures" && h.userInfo && h.setUserInfo({
|
||||||
|
...h.userInfo,
|
||||||
|
copilotExperimentalFeatureFlag: r
|
||||||
|
})) : z.log("error", `Unable to toggle feature ${n.title}: No server connection available`);
|
||||||
|
}
|
||||||
|
toggleExperimentalFeatureFlag(e, t) {
|
||||||
|
let n = e.target.checked;
|
||||||
|
_("use-experimental-feature", {
|
||||||
|
source: "toggle",
|
||||||
|
enabled: n,
|
||||||
|
id: t.id
|
||||||
|
});
|
||||||
|
let r = w.isExperimentalFeatureEnabled(t);
|
||||||
|
w.setExperimentalFeatureEnabled(t, n), t.requiresReload && n && !r && window.location.reload();
|
||||||
|
}
|
||||||
|
}, c([d()], B.prototype, "toggledFeaturesThatAreRequiresServerRestart", void 0), B = c([l("copilot-features-panel")], B), V = class extends p {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this.serverRestarting = !1;
|
||||||
|
}
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (h.serverRestartRequiringToggledFeatureFlags.length === 0 || !C()) return i;
|
||||||
|
let e = this.serverRestarting ? "Restarting..." : "Click to restart server";
|
||||||
|
return s`
|
||||||
|
<vaadin-button
|
||||||
|
aria-label="Restart server"
|
||||||
|
?disabled="${this.serverRestarting}"
|
||||||
|
theme="icon tertiary"
|
||||||
|
@click=${() => {
|
||||||
|
this.serverRestarting = !0, b();
|
||||||
|
}}>
|
||||||
|
<vaadin-icon .svg="${o.refresh}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text=${e}></vaadin-tooltip>
|
||||||
|
</vaadin-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}, c([d()], V.prototype, "serverRestarting", void 0), V = c([l("copilot-features-actions")], V), H = {
|
||||||
|
header: "Features",
|
||||||
|
tag: "copilot-features-panel",
|
||||||
|
helpUrl: "https://vaadin.com/docs/latest/flow/configuration/feature-flags",
|
||||||
|
actionsTag: "copilot-features-actions",
|
||||||
|
toolbarOptions: {
|
||||||
|
allowedModesWithOrder: { common: 0 },
|
||||||
|
iconKey: "listAlt"
|
||||||
|
}
|
||||||
|
}, window.Vaadin.copilot.plugins.push({ init(e) {
|
||||||
|
e.addPanel(H);
|
||||||
|
} });
|
||||||
|
}))();
|
||||||
|
export { V as CopilotFeaturesActions, B as CopilotFeaturesPanel };
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { _ as t, at as n, dt as r, g as i, n as a, r as o, st as s, t as c, u as l } from "./icons-CwakCZgK.js";
|
||||||
|
import { l as u, o as d } from "./consts-CSALuSsm.js";
|
||||||
|
import { a as f, c as p, d as m, i as h, l as g, n as _, o as v, r as y, s as b, t as x } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { a as S, i as C, n as w, r as T } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { r as E, t as D } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { n as O, t as k } from "./base-panel-Fr0D1ZcU.js";
|
||||||
|
import { n as A, t as j } from "./copilot-message-box-CVAh5PSs.js";
|
||||||
|
//#region frontend/copilot/plugins/copilot-feedback/copilot-feedback-plugin.ts
|
||||||
|
var M, N, P, F, I, L, R, z, B;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
b(), O(), h(), o(), n(), j(), i(), u(), S(), a(), x(), D(), T(), v(), M = "https://github.com/vaadin", N = "https://github.com/vaadin/copilot/issues/new", P = "?template=feature_request.md&title=%5BFEATURE%5D", F = "A short, concise description of the bug and why you consider it a bug. Any details like exceptions and logs can be helpful as well.", I = "Please provide as many details as possible, this will help us deliver a fix as soon as possible.%0AThank you!%0A%0A%23%23%23 Description of the Bug%0A%0A{description}%0A%0A%23%23%23 Expected Behavior%0A%0AA description of what you would expect to happen. (Sometimes it is clear what the expected outcome is if something does not work, other times, it is not super clear.)%0A%0A%23%23%23 Minimal Reproducible Example%0A%0AWe would appreciate the minimum code with which we can reproduce the issue.%0A%0A%23%23%23 Versions%0A{versionsInfo}", L = s({
|
||||||
|
showForm: !0,
|
||||||
|
submitDisabled: !1
|
||||||
|
}), R = class extends k {
|
||||||
|
constructor() {
|
||||||
|
super(), this.description = "", this.types = [
|
||||||
|
{
|
||||||
|
label: "General feedback",
|
||||||
|
value: "feedback",
|
||||||
|
ghTitle: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Report a bug",
|
||||||
|
value: "bug",
|
||||||
|
ghTitle: "[BUG]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Ask a question",
|
||||||
|
value: "question",
|
||||||
|
ghTitle: "[QUESTION]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Share an idea",
|
||||||
|
value: "idea",
|
||||||
|
ghTitle: "[FEATURE]"
|
||||||
|
}
|
||||||
|
], this.type = this.types[0].value, this.topics = [
|
||||||
|
{
|
||||||
|
label: "Generic",
|
||||||
|
value: "platform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow",
|
||||||
|
value: "flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hilla",
|
||||||
|
value: "hilla"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Copilot",
|
||||||
|
value: "copilot"
|
||||||
|
}
|
||||||
|
], this.topic = this.topics[0].value;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents");
|
||||||
|
}
|
||||||
|
willUpdate(e) {
|
||||||
|
super.willUpdate(e), this.syncFooterState();
|
||||||
|
}
|
||||||
|
syncFooterState() {
|
||||||
|
let e = this.message === void 0, t = this.type === "question" && !this.email;
|
||||||
|
(L.showForm !== e || L.submitDisabled !== t) && r(() => {
|
||||||
|
L.showForm = e, L.submitDisabled = t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getPreferredHeight() {
|
||||||
|
return 620;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return l`<div class="flex flex-col gap-4 pb-4 px-4">${this.renderContent()}</div>`;
|
||||||
|
}
|
||||||
|
renderContent() {
|
||||||
|
return this.message === void 0 ? l`
|
||||||
|
${A("info", "Your feedback means a lot to us. Whether you've encountered an issue, have a question, or have ideas to improve our platform, we'd love to hear from you. Feel free to leave your email and we'll get back to you — you can also share a code snippet to help us better understand your experience.", void 0, { icon: c.favorite })}
|
||||||
|
<vaadin-radio-group
|
||||||
|
label="Type"
|
||||||
|
theme="toggle"
|
||||||
|
.value="${this.type}"
|
||||||
|
@value-changed=${(e) => {
|
||||||
|
this.type = e.detail.value;
|
||||||
|
}}>
|
||||||
|
${this.types.map((e) => l`<vaadin-radio-button .value="${e.value}" label="${e.label}"></vaadin-radio-button>`)}
|
||||||
|
</vaadin-radio-group>
|
||||||
|
<vaadin-select
|
||||||
|
label="Topic"
|
||||||
|
overlay-class="alwaysVisible"
|
||||||
|
.items=${this.topics}
|
||||||
|
.value="${this.topic}"
|
||||||
|
.hidden=${this.type !== "feedback"}
|
||||||
|
@value-changed=${(e) => {
|
||||||
|
this.topic = e.detail.value;
|
||||||
|
}}>
|
||||||
|
</vaadin-select>
|
||||||
|
<vaadin-text-area
|
||||||
|
min-rows="3"
|
||||||
|
.value="${this.description}"
|
||||||
|
@keydown=${this.keyDown}
|
||||||
|
@focus=${() => {
|
||||||
|
this.descriptionField.invalid = !1, this.descriptionField.placeholder = "";
|
||||||
|
}}
|
||||||
|
@value-changed=${(e) => {
|
||||||
|
this.description = e.detail.value;
|
||||||
|
}}
|
||||||
|
label="Your Feedback"
|
||||||
|
placeholder="What happened, what you expected, or what you'd change..."></vaadin-text-area>
|
||||||
|
<vaadin-email-field
|
||||||
|
@keydown=${this.keyDown}
|
||||||
|
@value-changed=${(e) => {
|
||||||
|
this.email = e.detail.value;
|
||||||
|
}}
|
||||||
|
.required=${this.type === "question"}
|
||||||
|
id="email"
|
||||||
|
value="${w.userInfo?.email}"
|
||||||
|
label="Email${this.type === "question" ? "" : " (optional)"}"></vaadin-email-field>
|
||||||
|
` : l`<p class="m-0">${this.message}</p>`;
|
||||||
|
}
|
||||||
|
createGithubIssue() {
|
||||||
|
C.emit("system-info-with-callback", {
|
||||||
|
callback: (e) => this.openGithub(e, this),
|
||||||
|
notify: !1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
_.closePanel("copilot-feedback-panel");
|
||||||
|
}
|
||||||
|
submit() {
|
||||||
|
if (E("feedback", {
|
||||||
|
github: !1,
|
||||||
|
type: this.type,
|
||||||
|
topic: this.topic
|
||||||
|
}), this.description.trim() === "") {
|
||||||
|
this.descriptionField.invalid = !0, this.descriptionField.placeholder = "Please tell us more before sending", this.descriptionField.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let e = {
|
||||||
|
description: this.description,
|
||||||
|
email: this.email,
|
||||||
|
type: this.type,
|
||||||
|
topic: this.topic
|
||||||
|
};
|
||||||
|
C.emit("system-info-with-callback", {
|
||||||
|
callback: (n) => t(`${d}feedback`, {
|
||||||
|
...e,
|
||||||
|
versions: n
|
||||||
|
}),
|
||||||
|
notify: !1
|
||||||
|
}), this.parentNode?.style.setProperty("--section-height", "150px"), this.message = "Thank you for sharing feedback.";
|
||||||
|
}
|
||||||
|
keyDown(e) {
|
||||||
|
(e.key === "Backspace" || e.key === "Delete") && e.stopPropagation();
|
||||||
|
}
|
||||||
|
openGithub(e, t) {
|
||||||
|
if (E("feedback", {
|
||||||
|
github: !0,
|
||||||
|
type: this.type,
|
||||||
|
topic: this.topic
|
||||||
|
}), this.type === "idea") {
|
||||||
|
window.open(`${N}${P}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.type === "feedback") {
|
||||||
|
window.open(`${M}/${this.topic}/issues/new`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let n = e ? e.replace(/\n/g, "%0A") : "Activate Copilot to include version info.", r = `${t.types.find((e) => e.value === this.type)?.ghTitle}`, i = t.description === "" ? F : t.description, a = I.replace("{description}", i).replace("{versionsInfo}", n);
|
||||||
|
window.open(`${N}?title=${r}&body=${a}`, "_blank")?.focus();
|
||||||
|
}
|
||||||
|
}, f([g()], R.prototype, "description", void 0), f([g()], R.prototype, "type", void 0), f([g()], R.prototype, "topic", void 0), f([g()], R.prototype, "email", void 0), f([g()], R.prototype, "message", void 0), f([g()], R.prototype, "types", void 0), f([g()], R.prototype, "topics", void 0), f([p("vaadin-text-area")], R.prototype, "descriptionField", void 0), R = f([m("copilot-feedback-panel")], R), z = class extends y {
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents");
|
||||||
|
}
|
||||||
|
getPanel() {
|
||||||
|
return this.closest("vaadin-dialog")?.querySelector("copilot-feedback-panel") ?? null;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return L.showForm ? l`
|
||||||
|
<vaadin-button
|
||||||
|
style="margin-inline-end: auto"
|
||||||
|
theme="tertiary"
|
||||||
|
@click=${() => this.getPanel()?.createGithubIssue()}>
|
||||||
|
<vaadin-icon slot="prefix" .svg="${c.github}"></vaadin-icon>
|
||||||
|
Create GitHub Issue
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button theme="tertiary" @click=${() => this.getPanel()?.close()}>Cancel</vaadin-button>
|
||||||
|
<vaadin-button
|
||||||
|
theme="primary"
|
||||||
|
?disabled=${L.submitDisabled}
|
||||||
|
@click=${() => this.getPanel()?.submit()}>
|
||||||
|
Submit
|
||||||
|
</vaadin-button>
|
||||||
|
` : l`<vaadin-button @click=${() => this.getPanel()?.close()}>Close</vaadin-button>`;
|
||||||
|
}
|
||||||
|
}, z = f([m("copilot-feedback-footer-actions")], z), B = {
|
||||||
|
header: "Help Us Improve!",
|
||||||
|
tag: "copilot-feedback-panel",
|
||||||
|
footerActionsTag: "copilot-feedback-footer-actions",
|
||||||
|
individual: !0
|
||||||
|
}, window.Vaadin.copilot.plugins.push({ init(e) {
|
||||||
|
e.addPanel(B);
|
||||||
|
} }), _.addPanel(B);
|
||||||
|
}))();
|
||||||
|
export { z as CopilotFeedbackFooterActions, R as CopilotFeedbackPanel };
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { c as t, d as n } from "./dom-utils-Cuv93-tQ.js";
|
||||||
|
//#region frontend/copilot/copilot-focus-trap.ts
|
||||||
|
function r() {
|
||||||
|
return document.body.querySelector("copilot-main");
|
||||||
|
}
|
||||||
|
var i, a;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
t(), i = class {
|
||||||
|
constructor() {
|
||||||
|
this.active = !1, this.activate = () => {
|
||||||
|
this.active = !0;
|
||||||
|
let e = this.getApplicationRootElement();
|
||||||
|
e && e instanceof HTMLElement && e.addEventListener("focusin", this.focusInEventListener), r()?.focus(), r()?.addEventListener("focusout", this.keepFocusInCopilot);
|
||||||
|
}, this.deactivate = () => {
|
||||||
|
this.active = !1;
|
||||||
|
let e = this.getApplicationRootElement();
|
||||||
|
e && e instanceof HTMLElement && e.removeEventListener("focusin", this.focusInEventListener), r()?.removeEventListener("focusout", this.keepFocusInCopilot);
|
||||||
|
}, this.focusInEventListener = (e) => {
|
||||||
|
this.active && (e.preventDefault(), e.stopPropagation(), n(e.target) || requestAnimationFrame(() => {
|
||||||
|
e.target.blur && e.target.blur(), r()?.focus();
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getApplicationRootElement() {
|
||||||
|
return document.body.firstElementChild;
|
||||||
|
}
|
||||||
|
keepFocusInCopilot(e) {
|
||||||
|
e.preventDefault(), e.stopPropagation(), r()?.focus();
|
||||||
|
}
|
||||||
|
}, a = new i();
|
||||||
|
}))();
|
||||||
|
export { a as copilotFocusTrap };
|
||||||
+344
@@ -0,0 +1,344 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { A as t, J as n, K as r, L as i, N as a, P as ee, R as o, _ as s, a as c, at as l, g as te, n as u, o as d, ot as f, q as p, r as m, st as h, t as g, u as _ } from "./icons-CwakCZgK.js";
|
||||||
|
import { l as v, o as y } from "./consts-CSALuSsm.js";
|
||||||
|
import { c as b, g as x } from "./dom-utils-Cuv93-tQ.js";
|
||||||
|
import { a as S, n as C, o as w, t as T } from "./copilot-tree-impl-DxBvMTRa.js";
|
||||||
|
import { a as E, i as D } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { i as O, n as k } from "./copilot-server-communicator-impl-B7YDzJpM.js";
|
||||||
|
import { a as A, i as j, t as M } from "./stats-CRkPKCLQ.js";
|
||||||
|
import { i as N, o as P } from "./copilot-error-handler-9OpssAH1.js";
|
||||||
|
//#region frontend/copilot/show-in-ide.ts
|
||||||
|
function F(e, n) {
|
||||||
|
I(e) ? (j("show-in-ide", {
|
||||||
|
attach: n ?? !1,
|
||||||
|
goToCustomComponentFile: !0
|
||||||
|
}), s(`${y}show-in-ide`, {
|
||||||
|
javaClassName: e.className,
|
||||||
|
fileName: e.absoluteFilePath
|
||||||
|
})) : ee(e) ? (j("show-in-ide", { attach: n ?? !1 }), s(`${y}show-in-ide`, {
|
||||||
|
...t(e),
|
||||||
|
attach: n ?? !1
|
||||||
|
})) : (A("show-in-ide"), s(`${y}show-in-ide`, e));
|
||||||
|
}
|
||||||
|
function I(e) {
|
||||||
|
return e === void 0 ? !1 : e.className === void 0 ? e.absoluteFilePath !== void 0 : !0;
|
||||||
|
}
|
||||||
|
function L(e) {
|
||||||
|
if (!e.isReactComponent) return;
|
||||||
|
let t = p(e.node);
|
||||||
|
if (t) return t;
|
||||||
|
let n = r(e.node);
|
||||||
|
if (n) return n;
|
||||||
|
let i = e.children.sort((e, t) => e.siblingIndex - t.siblingIndex).find((e) => e.isReactComponent && L(e) !== void 0);
|
||||||
|
if (!i) throw Error(`Could not find the source of ${e.nameAndIdentifier}`);
|
||||||
|
return p(i.node);
|
||||||
|
}
|
||||||
|
var R = e((() => {
|
||||||
|
a(), n(), v(), E(), te(), M(), D.on("show-in-ide", (e) => {
|
||||||
|
let t = e.detail.node;
|
||||||
|
if (e.detail.source) {
|
||||||
|
F(e.detail.source);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.detail.javaSource) {
|
||||||
|
F(e.detail.javaSource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!t) return;
|
||||||
|
if (t.isFlowComponent) {
|
||||||
|
F(t.node, e.detail.attach);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let n = L(t);
|
||||||
|
n && F(n);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
//#region frontend/copilot/empty-app-initializer.ts
|
||||||
|
function z(e) {
|
||||||
|
let t = document.createElement("div");
|
||||||
|
document.body.innerHTML = "", document.body.appendChild(t), c(e, t);
|
||||||
|
}
|
||||||
|
function B() {
|
||||||
|
z(_`<div class="flex flex-col gap-4 h-screen items-center justify-center">
|
||||||
|
<vaadin-icon class="animate-spin" .svg=${g.progressActivity}></vaadin-icon>
|
||||||
|
<h3 class="m-0">The files have been created</h3>
|
||||||
|
<p class="m-0">Restart the server to load the new view</p>
|
||||||
|
<p class="m-0"><small>The page will refresh automatically when the server is ready.</small></p>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
async function V() {
|
||||||
|
let e = 1e3, t = 12e4, n = Date.now(), r = async () => {
|
||||||
|
try {
|
||||||
|
return (await fetch(globalThis.location.href, { method: "HEAD" })).ok;
|
||||||
|
} catch {
|
||||||
|
return !1;
|
||||||
|
}
|
||||||
|
}, i = !1;
|
||||||
|
for (; Date.now() - n < t;) {
|
||||||
|
if (!await r()) {
|
||||||
|
i = !0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Promise((t) => {
|
||||||
|
setTimeout(t, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (; Date.now() - n < t;) {
|
||||||
|
if (await r() && i) {
|
||||||
|
sessionStorage.removeItem(G), globalThis.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise((t) => {
|
||||||
|
setTimeout(t, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function H(e) {
|
||||||
|
z(_`<div class="flex flex-col gap-4 h-screen items-center justify-center">
|
||||||
|
<vaadin-icon class="animate-spin" .svg=${g.progressActivity}></vaadin-icon>
|
||||||
|
<h3 class="m-0">Creating your ${e === "flow" ? "Flow" : "Hilla"} view...</h3>
|
||||||
|
</div>`), o("copilot-init-app", { framework: e }, async (e) => {
|
||||||
|
if (e.data.success) sessionStorage.setItem(G, "true"), B(), V();
|
||||||
|
else {
|
||||||
|
let t = e.data.reason;
|
||||||
|
N(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function U() {
|
||||||
|
z(_`<div class="m-8">
|
||||||
|
<h3>No views found</h3>
|
||||||
|
<p>To get started, you can</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
@click=${(e) => {
|
||||||
|
e.preventDefault(), H("flow");
|
||||||
|
}}
|
||||||
|
>Create a Flow view using Copilot</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Create a view manually in your IDE, see
|
||||||
|
<a target="_blank" href="https://vaadin.com/docs/latest/tutorial">the tutorial</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Learn more at <a target="_blank" href="https://vaadin.com/docs">https://vaadin.com/docs</a>.</p>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
function W() {
|
||||||
|
sessionStorage.getItem(G) ? (B(), V()) : U();
|
||||||
|
}
|
||||||
|
var G, K = e((() => {
|
||||||
|
i(), P(), u(), m(), G = "vaadin.copilot.viewCreated";
|
||||||
|
})), q, J = e((() => {
|
||||||
|
E(), q = class {
|
||||||
|
constructor(e) {
|
||||||
|
this._currentTree = e;
|
||||||
|
}
|
||||||
|
get root() {
|
||||||
|
return this.currentTree.root;
|
||||||
|
}
|
||||||
|
get allNodesFlat() {
|
||||||
|
return this.currentTree.allNodesFlat;
|
||||||
|
}
|
||||||
|
getNodeOfElement(e) {
|
||||||
|
return this.currentTree.getNodeOfElement(e);
|
||||||
|
}
|
||||||
|
getChildren(e) {
|
||||||
|
return this.currentTree.getChildren(e);
|
||||||
|
}
|
||||||
|
hasFlowComponents() {
|
||||||
|
return this.currentTree.hasFlowComponents();
|
||||||
|
}
|
||||||
|
findNodeByUuid(e) {
|
||||||
|
return this.currentTree.findNodeByUuid(e);
|
||||||
|
}
|
||||||
|
getElementByNodeUuid(e) {
|
||||||
|
return this.currentTree.getElementByNodeUuid(e);
|
||||||
|
}
|
||||||
|
findByTreePath(e) {
|
||||||
|
return this.currentTree.findByTreePath(e);
|
||||||
|
}
|
||||||
|
get currentTree() {
|
||||||
|
return this._currentTree;
|
||||||
|
}
|
||||||
|
set currentTree(e) {
|
||||||
|
let t = this._currentTree;
|
||||||
|
this._currentTree = e, D.emit("copilot-tree-created", {
|
||||||
|
prev: t,
|
||||||
|
curr: e
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get customComponentDataLoaded() {
|
||||||
|
return this._currentTree.customComponentDataLoaded;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})), Y = e((() => {
|
||||||
|
E(), D.on("navigate", (e) => {
|
||||||
|
let t = window.history.state?.idx, n = {};
|
||||||
|
t !== void 0 && (n.idx = t + 1), window.history.pushState(n, "", e.detail.path), window.dispatchEvent(new PopStateEvent("popstate"));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
//#endregion
|
||||||
|
//#region frontend/copilot/shared/copilot-storage-utils.ts
|
||||||
|
function X(e) {
|
||||||
|
let t = window.Vaadin.copilot.tree;
|
||||||
|
return e.map((e) => {
|
||||||
|
let n = null, { nodeUuid: r, treePath: i, childIndex: a } = e;
|
||||||
|
if (r) {
|
||||||
|
let e = t.findNodeByUuid(r);
|
||||||
|
e && (n = e);
|
||||||
|
}
|
||||||
|
return n ||= t.findByTreePath(i) ?? null, n && a !== void 0 && n.children.length > a ? n.children[a] : n;
|
||||||
|
}).filter((e) => e !== null);
|
||||||
|
}
|
||||||
|
var Z = e((() => {})), Q, ne = e((() => {
|
||||||
|
m(), u(), l(), w(), Z(), b(), Q = class e {
|
||||||
|
constructor() {
|
||||||
|
this.drillDownComponentStack = [], f(this, { drillDownComponentStack: h.shallow });
|
||||||
|
}
|
||||||
|
getCustomComponentIcon(e) {
|
||||||
|
let t = this.getIconTag(e);
|
||||||
|
return t === void 0 ? d : g[t];
|
||||||
|
}
|
||||||
|
getIconTag(e) {
|
||||||
|
let t = this.getCustomComponentInfo(e)?.type;
|
||||||
|
if (t === "IN_PROJECT") return "thermostatCarbon";
|
||||||
|
if (t === "EXTERNAL") return "deployedCube";
|
||||||
|
}
|
||||||
|
getCustomComponentInfo(t) {
|
||||||
|
if (t.customComponentData && e.isCustomComponentInstanceInfo(t.customComponentData)) return t.customComponentData;
|
||||||
|
}
|
||||||
|
isCustomComponent(e) {
|
||||||
|
return this.getCustomComponentInfo(e) !== void 0;
|
||||||
|
}
|
||||||
|
isVisibleAndSelectable(e) {
|
||||||
|
if (!this.getTree().customComponentDataLoaded) return !0;
|
||||||
|
let t = this.getActiveDrillDownContext();
|
||||||
|
if (!e.customComponentData) return e.isReactComponent && !e.parent && e.name === "App" && !t;
|
||||||
|
if (e.uuid === t?.uuid) return !0;
|
||||||
|
let n = this.getActiveDrillDownData(), r = e.customComponentData;
|
||||||
|
if (!n?.filePath) {
|
||||||
|
if (r) return !r.childOfCustomComponent;
|
||||||
|
} else if (e.customComponentData) return this.checkNodeIsInDrillDownContext(r, n);
|
||||||
|
else return !1;
|
||||||
|
return !0;
|
||||||
|
}
|
||||||
|
pushDrillDownContext(e) {
|
||||||
|
this.drillDownComponentStack.length > 0 && this.drillDownComponentStack[this.drillDownComponentStack.length - 1].uuid === e.uuid || (this.drillDownComponentStack.push(e), this.persistIntoStorage(), x(e));
|
||||||
|
}
|
||||||
|
isDrillDownContext(e) {
|
||||||
|
return this.getActiveDrillDownContext()?.uuid === e.uuid;
|
||||||
|
}
|
||||||
|
getActiveDrillDownContext() {
|
||||||
|
if (this.drillDownComponentStack.length !== 0) return this.resolveCurrentTreeNode(this.drillDownComponentStack[this.drillDownComponentStack.length - 1]);
|
||||||
|
}
|
||||||
|
clearDrillDownContext() {
|
||||||
|
this.drillDownComponentStack = [], this.persistIntoStorage();
|
||||||
|
}
|
||||||
|
popDrillDownContext() {
|
||||||
|
this.filterOutNonConnectedElementsFromDrillDownContextStack(), this.drillDownComponentStack.pop(), this.persistIntoStorage();
|
||||||
|
}
|
||||||
|
hasParentDrillDownContext() {
|
||||||
|
return this.drillDownComponentStack.length > 1;
|
||||||
|
}
|
||||||
|
getParentDrillDownContext() {
|
||||||
|
if (this.hasParentDrillDownContext()) return this.resolveCurrentTreeNode(this.drillDownComponentStack[this.drillDownComponentStack.length - 2]);
|
||||||
|
}
|
||||||
|
isChildInDrillContext(e) {
|
||||||
|
let t = e.customComponentData;
|
||||||
|
if (!t) return !0;
|
||||||
|
let n = this.getActiveDrillDownData();
|
||||||
|
return n ? this.checkNodeIsInDrillDownContext(t, n) : !1;
|
||||||
|
}
|
||||||
|
getActiveDrillDownData() {
|
||||||
|
let e = this.getActiveDrillDownContext();
|
||||||
|
if (e === void 0) return;
|
||||||
|
let t = this.getCustomComponentInfo(e);
|
||||||
|
if (!t?.javaClassName && !t?.reactMethodName) return;
|
||||||
|
let n = e.node;
|
||||||
|
return {
|
||||||
|
className: t.javaClassName,
|
||||||
|
methodName: t.reactMethodName,
|
||||||
|
nodeId: n.nodeId,
|
||||||
|
uiId: n.uiId,
|
||||||
|
filePath: t.customComponentFilePath ?? void 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
checkNodeIsInDrillDownContext(e, t) {
|
||||||
|
return e.createLocationMethodName && t.methodName ? e.createLocationMethodName === t.methodName && t.filePath === e.createLocationPath : t.filePath === e.createLocationPath && t.className === e.createdClassName;
|
||||||
|
}
|
||||||
|
persistIntoStorage() {
|
||||||
|
let e = this.drillDownComponentStack.map((e) => ({
|
||||||
|
treePath: e.path,
|
||||||
|
nodeUuid: e.uuid
|
||||||
|
}));
|
||||||
|
S.saveDrillDownContextReference(e);
|
||||||
|
}
|
||||||
|
restoreDrillDownFromStorage() {
|
||||||
|
let t = S.getDrillDownContextReference(), n = [];
|
||||||
|
if (t === void 0) {
|
||||||
|
let t = this.getTree().allNodesFlat.find((e) => e.customComponentData?.routeView);
|
||||||
|
t?.customComponentData && e.isCustomComponentInstanceInfo(t.customComponentData) && (n = [t]);
|
||||||
|
} else n = X(t);
|
||||||
|
n.forEach((e) => {
|
||||||
|
let t = this.drillDownComponentStack.findIndex((t) => t.uuid === e.uuid);
|
||||||
|
t !== -1 && this.drillDownComponentStack.splice(t, 1), this.drillDownComponentStack.push(e);
|
||||||
|
});
|
||||||
|
let r = this.drillDownComponentStack.filter((e) => !!this.getTree().findNodeByUuid(e.uuid));
|
||||||
|
r.length !== this.drillDownComponentStack.length && (this.drillDownComponentStack = r, this.persistIntoStorage()), this.filterOutNonConnectedElementsFromDrillDownContextStack();
|
||||||
|
let i = this.getActiveDrillDownContext();
|
||||||
|
i && x(i);
|
||||||
|
}
|
||||||
|
areInternalsVisible(e) {
|
||||||
|
if (!this.getCustomComponentInfo(e)) return !0;
|
||||||
|
let t = this.getActiveDrillDownData(), n;
|
||||||
|
return t && t.filePath && (n = t.filePath), n ? this.checkChildrenCreateLocationToDisplayInternals(e.children, n) : !1;
|
||||||
|
}
|
||||||
|
checkChildrenCreateLocationToDisplayInternals(e, t) {
|
||||||
|
for (let n of e) {
|
||||||
|
let e = n.customComponentData;
|
||||||
|
if (e && e.createLocationPath === t || this.checkChildrenCreateLocationToDisplayInternals(n.children, t)) return !0;
|
||||||
|
}
|
||||||
|
return !1;
|
||||||
|
}
|
||||||
|
getDescendantsCreatedInActiveDrillDownContextFlatten(t) {
|
||||||
|
if (t.customComponentData && e.isCustomComponentInstanceInfo(t.customComponentData)) {
|
||||||
|
let e = this.getActiveDrillDownData(), n;
|
||||||
|
if (e && e.filePath ? n = e.filePath : this.getRouteViewPath() && (n = this.getRouteViewPath()), n) return this.getChildrenInPathFlattenRecursively(t, n);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
getChildrenInPathFlattenRecursively(e, t) {
|
||||||
|
let n = e.children, r = [];
|
||||||
|
for (let e of n) {
|
||||||
|
let n = e.customComponentData;
|
||||||
|
n && n.createLocationPath === t && r.push(e), r.push(...this.getChildrenInPathFlattenRecursively(e, t));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
getTree() {
|
||||||
|
return window.Vaadin.copilot.tree;
|
||||||
|
}
|
||||||
|
getRouteViewPath() {
|
||||||
|
let e = this.getTree().allNodesFlat.find((e) => e.customComponentData?.routeView === !0);
|
||||||
|
if (e) return e.customComponentData?.createLocationPath ?? void 0;
|
||||||
|
}
|
||||||
|
resolveCurrentTreeNode(e) {
|
||||||
|
return this.getTree().findNodeByUuid(e.uuid) ?? this.getTree().findByTreePath(e.path) ?? e;
|
||||||
|
}
|
||||||
|
filterOutNonConnectedElementsFromDrillDownContextStack() {
|
||||||
|
this.drillDownComponentStack = this.drillDownComponentStack.filter((e) => e.element === void 0 ? !0 : e.element.isConnected);
|
||||||
|
}
|
||||||
|
static isCustomComponentInstanceInfo(e) {
|
||||||
|
return "type" in e && "activeLevel" in e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})), $;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
R(), k(), K(), J(), Y(), C(), ne(), window.Vaadin.copilot.comm = O, $ = new T(), window.Vaadin.copilot.tree = new q($), window.Vaadin.copilot.customComponentHandler = new Q(), window.Vaadin.copilot.initEmptyApp = H, window.Vaadin.copilot.noRoutesInProject = W;
|
||||||
|
}))();
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
import { n as e } from "./chunk-DiqZc92J.js";
|
||||||
|
import { L as t, Q as n, R as r, et as i, n as a, r as o, t as s, u as c } from "./icons-CwakCZgK.js";
|
||||||
|
import { a as l, d as u, l as d, n as f, o as p, s as m, t as h } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { n as g, t as _ } from "./copilot-stored-machine-state-D6qB_Peh.js";
|
||||||
|
import { n as v, t as y } from "./early-project-state-LGwavSyI.js";
|
||||||
|
import { n as b, t as x } from "./base-panel-Fr0D1ZcU.js";
|
||||||
|
//#region frontend/copilot/application-user-switcher.ts
|
||||||
|
function S(e) {
|
||||||
|
return r("copilot-switch-user", { username: e }, (e) => e.data.error ? {
|
||||||
|
success: !1,
|
||||||
|
errorMessage: e.data.error.message
|
||||||
|
} : { success: !0 });
|
||||||
|
}
|
||||||
|
var C = e((() => {
|
||||||
|
t();
|
||||||
|
})), w, T;
|
||||||
|
//#endregion
|
||||||
|
e((() => {
|
||||||
|
o(), m(), i(), b(), a(), C(), _(), v(), h(), p(), w = class extends x {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this.username = "", this.errorMessage = "", this.isLoading = !1, this.handleKeyDown = async (e) => {
|
||||||
|
e.key === "Enter" && this.username && !this.isLoading && await this.handleSwitchUser();
|
||||||
|
}, this.handleSwitchUser = async () => {
|
||||||
|
if (!(!this.username || this.isLoading)) {
|
||||||
|
this.isLoading = !0, this.errorMessage = "";
|
||||||
|
try {
|
||||||
|
let e = await S(this.username);
|
||||||
|
e.success ? (g.addRecentSwitchedUsername(this.username), globalThis.location.reload()) : (this.errorMessage = e.errorMessage, this.isLoading = !1);
|
||||||
|
} catch {
|
||||||
|
this.errorMessage = "An unexpected error occurred", this.isLoading = !1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this.switchToRecentUser = async (e) => {
|
||||||
|
this.username = e, await this.handleSwitchUser();
|
||||||
|
}, this.removeRecentUser = (e, t) => {
|
||||||
|
t.stopPropagation(), g.removeRecentSwitchedUsername(e), this.requestUpdate();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents"), this.reaction(() => g.getRecentSwitchedUsernames(), () => {
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (!y.springSecurityEnabled) return c`
|
||||||
|
<div class="flex flex-col items-center pb-4 px-4">
|
||||||
|
<vaadin-icon class="icon-lg mb-2" .svg="${s.accountCircle}"></vaadin-icon>
|
||||||
|
<h3 class="mb-0.5 mt-0 text-semibold text-sm">Spring Security Disabled</h3>
|
||||||
|
<p class="m-0 text-balance text-center text-secondary text-xs">
|
||||||
|
User impersonation requires Spring Security to be configured in your application
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
let e = g.getRecentSwitchedUsernames();
|
||||||
|
return c`
|
||||||
|
<div class="flex flex-col gap-4 pb-4 px-4">
|
||||||
|
<div class="flex gap-4 items-baseline">
|
||||||
|
<vaadin-text-field
|
||||||
|
class="flex-1"
|
||||||
|
label="Username"
|
||||||
|
.value="${this.username}"
|
||||||
|
.errorMessage="${this.errorMessage}"
|
||||||
|
.invalid="${this.errorMessage !== ""}"
|
||||||
|
?disabled="${this.isLoading}"
|
||||||
|
@value-changed="${(e) => {
|
||||||
|
this.username = e.detail.value, this.errorMessage = "";
|
||||||
|
}}"
|
||||||
|
@keydown="${this.handleKeyDown}">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${s.accountCircle}"></vaadin-icon>
|
||||||
|
</vaadin-text-field>
|
||||||
|
<vaadin-button
|
||||||
|
theme="primary"
|
||||||
|
?disabled="${!this.username || this.isLoading}"
|
||||||
|
@click="${this.handleSwitchUser}">
|
||||||
|
<vaadin-icon slot="prefix" .svg="${s.swapHoriz}"></vaadin-icon>
|
||||||
|
${this.isLoading ? "Switching..." : "Switch User"}
|
||||||
|
</vaadin-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${e.length > 0 ? c`
|
||||||
|
<div class="flex flex-col gap-2 mt-1">
|
||||||
|
<h3 class="m-0 text-semibold text-sm">Recent Usernames</h3>
|
||||||
|
<ul
|
||||||
|
class="bg-gray-2 dark:bg-gray-6 border border-gray-3 dark:border-gray-7 divide-y list-none m-0 p-0 rounded-md">
|
||||||
|
${e.map((e) => c`
|
||||||
|
<li class="flex gap-1 items-center pe-1 ps-3 py-1">
|
||||||
|
<span class="flex-1">${e}</span>
|
||||||
|
<vaadin-button theme="icon tertiary" @click="${() => this.switchToRecentUser(e)}">
|
||||||
|
<vaadin-icon .svg="${s.swapHoriz}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Switch to ${e}"></vaadin-tooltip>
|
||||||
|
</vaadin-button>
|
||||||
|
<vaadin-button
|
||||||
|
aria-label="Remove ${e}"
|
||||||
|
class="text-ruby-11"
|
||||||
|
theme="icon tertiary"
|
||||||
|
@click="${(t) => this.removeRecentUser(e, t)}">
|
||||||
|
<vaadin-icon .svg="${s.delete}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Remove ${e}"></vaadin-tooltip>
|
||||||
|
</vaadin-button>
|
||||||
|
</li>
|
||||||
|
`)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}, l([d()], w.prototype, "username", void 0), l([d()], w.prototype, "errorMessage", void 0), l([d()], w.prototype, "isLoading", void 0), w = l([u("copilot-impersonator")], w), T = {
|
||||||
|
header: "Impersonate User",
|
||||||
|
tag: n.IMPERSONATOR,
|
||||||
|
individual: !0,
|
||||||
|
toolbarOptions: {
|
||||||
|
allowedModesWithOrder: { common: 0 },
|
||||||
|
iconKey: "accountCircle"
|
||||||
|
}
|
||||||
|
}, globalThis.Vaadin.copilot.plugins.push({ init(e) {
|
||||||
|
e.addPanel(T);
|
||||||
|
} }), f.addPanel(T);
|
||||||
|
}))();
|
||||||
|
export { w as CopilotImpersonatorPanel };
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
import { i as e, n as t } from "./chunk-DiqZc92J.js";
|
||||||
|
import { n, o as r, r as i, t as a, u as o } from "./icons-CwakCZgK.js";
|
||||||
|
import { a as s, d as c, i as l, l as u, n as d, o as f, r as p, s as m, t as h } from "./section-panel-ui-state-hOj_RfX_.js";
|
||||||
|
import { a as g, i as _, n as v, r as y } from "./copilot-ui-state-Dc6l_5DA.js";
|
||||||
|
import { c as b, r as x, s as S } from "./copilot-development-setup-user-guide-utils-DzEVQbWO.js";
|
||||||
|
import { n as C, t as w } from "./base-panel-Fr0D1ZcU.js";
|
||||||
|
import { n as T, r as E, t as D } from "./copy-to-clipboard-4Y12mBRr.js";
|
||||||
|
//#region frontend/copilot/plugins/copilot-info/copilot-info-plugin.ts
|
||||||
|
function O(e, t) {
|
||||||
|
let n;
|
||||||
|
return n = e === !0 ? "text-teal-11" : e === "partial" ? "text-amber-11" : "text-ruby-11", o`<span class="${n}">${t}</span>`;
|
||||||
|
}
|
||||||
|
var k, A, j, M;
|
||||||
|
//#endregion
|
||||||
|
t((() => {
|
||||||
|
m(), i(), C(), y(), g(), l(), n(), k = /* @__PURE__ */ e(D(), 1), S(), h(), T(), f(), A = class extends w {
|
||||||
|
constructor(...e) {
|
||||||
|
super(...e), this.sortedEntries = [];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.classList.add("contents"), this.reaction(() => v.projectInfoEntries, () => {
|
||||||
|
if (!v.projectInfoEntries) return;
|
||||||
|
let e = [...v.projectInfoEntries, {
|
||||||
|
name: "Development Workflow",
|
||||||
|
value: ""
|
||||||
|
}];
|
||||||
|
e = e.filter((e) => e.name !== "Java Hotswap"), this.sortedEntries = e.sort((e, t) => e.name.localeCompare(t.name));
|
||||||
|
}, { fireImmediately: !0 });
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return o` <div class="flex flex-col py-2 px-4">
|
||||||
|
<dl class="border-dashed divide-y m-0">
|
||||||
|
${E(this.sortedEntries.filter((e) => e.name !== "Java Hotswap"), (e) => e.name, (e) => this.renderRow(e))}
|
||||||
|
</dl>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
renderRow(e) {
|
||||||
|
if (e.name === "Development Workflow") return this.renderDevelopmentWorkflowButton();
|
||||||
|
let t = e.name === "IDE Plugin" && e.value === !0 && v.idePluginState?.ide ? v.idePluginState.ide : e.value, n = this.getIcon(e.name, t), i = this.getIconColor(e.name), a = this.getTextColor(e);
|
||||||
|
return o`
|
||||||
|
<div class="flex gap-2 py-2">
|
||||||
|
<dt class="flex gap-2">
|
||||||
|
${n ? o`<vaadin-icon class="${i}" .svg="${n}"></vaadin-icon>` : r} ${e.name}
|
||||||
|
</dt>
|
||||||
|
<dd class="flex gap-2 m-0 ${a}">${this.renderRowValue(e)}</dd>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderRowValue(e) {
|
||||||
|
return e.name === "Vaadin Employee" && e.value === !0 ? o`
|
||||||
|
<vaadin-icon id="vaadin-employee" class="text-teal-11" .svg="${a.check}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip for="vaadin-employee" text="Yes"></vaadin-tooltip>
|
||||||
|
` : o` ${!e.booleanInfo && typeof e.value == "string" ? e.value : r}
|
||||||
|
${e.booleanInfo && typeof e.value == "boolean" ? O(e.value, e.booleanInfo.ariaLabel) : r}
|
||||||
|
${e.booleanInfo?.text ? e.booleanInfo.text : r}
|
||||||
|
${e.name === "Vaadin" ? this.renderVaadinRowMore() : r}`;
|
||||||
|
}
|
||||||
|
renderVaadinRowMore() {
|
||||||
|
let e = v.newVaadinVersionState?.versions !== void 0 && v.newVaadinVersionState.versions.length > 0;
|
||||||
|
return o`
|
||||||
|
${v.projectVersionReleaseNoteInfo && v.projectVersionReleaseNoteInfo.url ? o`<a
|
||||||
|
class="flex gap-0.5 items-center"
|
||||||
|
href="${v.projectVersionReleaseNoteInfo.url}"
|
||||||
|
id="release-notes-link"
|
||||||
|
target="_blank"
|
||||||
|
>Release notes <vaadin-icon class="icon-sm" .svg="${a.arrowOutward}"></vaadin-icon
|
||||||
|
></a>` : r}
|
||||||
|
<vaadin-button
|
||||||
|
aria-label="Edit Vaadin version"
|
||||||
|
class="-my-1.5 relative"
|
||||||
|
@click="${(e) => {
|
||||||
|
e.stopPropagation(), d.openPanel("copilot-vaadin-versions");
|
||||||
|
}}"
|
||||||
|
id="new-vaadin-version-btn"
|
||||||
|
theme="icon tertiary">
|
||||||
|
<vaadin-icon .svg="${a.editSquare}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Edit Vaadin version"></vaadin-tooltip>
|
||||||
|
${e ? o`<span aria-hidden="true" class="absolute bg-amber-11 end-0.5 rounded-full size-1 top-0.5"></span>` : ""}
|
||||||
|
</vaadin-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderDevelopmentWorkflowButton() {
|
||||||
|
let e = x(), t = "", n = a.doneAll, r = "";
|
||||||
|
return e.status === "success" ? (t = "text-teal-11", r = "IDE Plugin & Java Hotswap") : e.status === "warning" ? (t = "text-amber-11", n = a.arrowUploadReady, r = "Improve") : e.status === "error" && (t = "text-ruby-11", n = a.handyman, r = "Fix"), o`
|
||||||
|
<div class="flex gap-2 py-2">
|
||||||
|
<dt class="flex gap-2">
|
||||||
|
<vaadin-icon class="text-amber-11" .svg="${a.bolt}"></vaadin-icon>
|
||||||
|
Development Workflow
|
||||||
|
</dt>
|
||||||
|
<dd class="m-0">
|
||||||
|
<vaadin-button
|
||||||
|
class="-my-1.5 ${t}"
|
||||||
|
id="development-workflow-status-detail"
|
||||||
|
theme="tertiary"
|
||||||
|
@click=${() => {
|
||||||
|
b();
|
||||||
|
}}>
|
||||||
|
<vaadin-icon slot="prefix" .svg="${n}"></vaadin-icon>
|
||||||
|
${r}
|
||||||
|
</vaadin-button>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
getIconColor(e) {
|
||||||
|
return e.includes("Vaadin") || e === "Copilot" ? "text-vaadin-blue" : "";
|
||||||
|
}
|
||||||
|
getTextColor(e) {
|
||||||
|
if (typeof e.value == "string") {
|
||||||
|
if (e.value.startsWith("Enabled")) return "text-teal-11";
|
||||||
|
if (e.value.startsWith("Disabled")) return "text-ruby-11";
|
||||||
|
}
|
||||||
|
return "text-secondary";
|
||||||
|
}
|
||||||
|
getIcon(e, t) {
|
||||||
|
switch (e) {
|
||||||
|
case "Browser": {
|
||||||
|
let e = typeof t == "string" ? t.toLowerCase() : "";
|
||||||
|
return e.includes("chrome") && !e.includes("edg") ? a.chrome : e.includes("firefox") ? a.firefox : e.includes("safari") && !e.includes("chrome") ? a.safari : e.includes("edg") ? a.edge : a.webAsset;
|
||||||
|
}
|
||||||
|
case "Copilot": return a.vaadin;
|
||||||
|
case "Flow": return a.flow;
|
||||||
|
case "Frontend Hotswap": return a.swapHoriz;
|
||||||
|
case "Hilla": return a.hilla;
|
||||||
|
case "Java": return a.java;
|
||||||
|
case "OS": {
|
||||||
|
let e = typeof t == "string" ? t.toLowerCase() : "";
|
||||||
|
return e.includes("mac") ? a.apple : e.includes("win") ? a.windows : a.computer;
|
||||||
|
}
|
||||||
|
case "Spring": return a.spring;
|
||||||
|
case "Spring Boot": return a.springBoot;
|
||||||
|
case "Spring Data JPA": return a.springData;
|
||||||
|
case "Spring Security": return a.springSecurity;
|
||||||
|
case "Vaadin":
|
||||||
|
case "Vaadin Employee": return a.vaadin;
|
||||||
|
case "Java Hotswap": return a.swapHoriz;
|
||||||
|
case "IDE Plugin": return typeof t == "string" ? t.toLowerCase() === "intellij" ? a.intelliJ : t.toLowerCase() === "vscode" ? a.vsCode : t.toLowerCase() === "eclipse" ? a.eclipse : a.developerModeTv : a.developerModeTv;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, s([u()], A.prototype, "sortedEntries", void 0), A = s([c("copilot-info-panel")], A), j = class extends p {
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback(), this.style.display = "flex";
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return o` <vaadin-button
|
||||||
|
aria-label="Copy to clipboard"
|
||||||
|
@click=${() => {
|
||||||
|
_.emit("system-info-with-callback", {
|
||||||
|
callback: k.default,
|
||||||
|
notify: !0
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
theme="icon tertiary">
|
||||||
|
<vaadin-icon .svg="${a.fileCopy}"></vaadin-icon>
|
||||||
|
<vaadin-tooltip slot="tooltip" text="Copy to clipboard"></vaadin-tooltip>
|
||||||
|
</vaadin-button>`;
|
||||||
|
}
|
||||||
|
}, j = s([c("copilot-info-actions")], j), M = {
|
||||||
|
header: "Info",
|
||||||
|
tag: "copilot-info-panel",
|
||||||
|
actionsTag: "copilot-info-actions",
|
||||||
|
eager: !0,
|
||||||
|
toolbarOptions: {
|
||||||
|
iconKey: "info",
|
||||||
|
allowedModesWithOrder: { common: 0 }
|
||||||
|
}
|
||||||
|
}, window.Vaadin.copilot.plugins.push({ init(e) {
|
||||||
|
e.addPanel(M);
|
||||||
|
} });
|
||||||
|
}))();
|
||||||
|
export { j as Actions, A as CopilotInfoPanel };
|
||||||
+3920
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user