mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Merge branch prefs into master
The changes have been squashed as the commit history and messages were not very carefully crafted. There isn't that much value in preserving random commit messages. Also due to diverging branches, rebasing or merging it was difficult.
This commit is contained in:
parent
ce45ff9ec6
commit
3e7fbe89ac
442 changed files with 11614 additions and 6865 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -7,7 +7,8 @@ lib/
|
||||||
dev.properties
|
dev.properties
|
||||||
extensions.txt
|
extensions.txt
|
||||||
dev_storage
|
dev_storage
|
||||||
local*/
|
local/
|
||||||
|
local_*/
|
||||||
.vs
|
.vs
|
||||||
.vscode
|
.vscode
|
||||||
obj
|
obj
|
||||||
|
@ -15,3 +16,5 @@ out
|
||||||
bin
|
bin
|
||||||
.DS_Store
|
.DS_Store
|
||||||
ComponentsGenerated.wxs
|
ComponentsGenerated.wxs
|
||||||
|
!dist/javafx/**/lib
|
||||||
|
!dist/javafx/**/bin
|
||||||
|
|
|
@ -18,7 +18,7 @@ There are no real formal contribution guidelines right now, they will maybe come
|
||||||
|
|
||||||
All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/20/) and make full use of the Java Module System (JPMS).
|
All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/20/) and make full use of the Java Module System (JPMS).
|
||||||
All components are modularized, including all their dependencies.
|
All components are modularized, including all their dependencies.
|
||||||
In case a dependency is (sadly) not modularized yet, module information is manually added using [moditect](https://github.com/moditect/moditect-gradle-plugin).
|
In case a dependency is (sadly) not modularized yet, module information is manually added using [extra-java-module-info](https://github.com/gradlex-org/extra-java-module-info).
|
||||||
Further, note that as this is a pretty complicated Java project that fully utilizes modularity,
|
Further, note that as this is a pretty complicated Java project that fully utilizes modularity,
|
||||||
many IDEs still have problems building this project properly.
|
many IDEs still have problems building this project properly.
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ Alternatively, you can also use [Homebrew](https://github.com/xpipe-io/homebrew-
|
||||||
|
|
||||||
XPipe utilizes an open core model, which essentially means that the main application is open source while certain other components are not. Select parts are not open source yet, but may be added to this repository in the future.
|
XPipe utilizes an open core model, which essentially means that the main application is open source while certain other components are not. Select parts are not open source yet, but may be added to this repository in the future.
|
||||||
|
|
||||||
This mainly concerns the features only available in the professional tier and the shell handling library implementation. Furthermore, some tests and especially test environments and that run on private servers are also not included in this repository.
|
This mainly concerns the features only available in the professional tier and the shell handling library implementation. Furthermore, some CI pipelines and tests that run on private servers are also not included in this repository.
|
||||||
|
|
||||||
## More links
|
## More links
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,11 @@ plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'signing'
|
id 'signing'
|
||||||
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/java.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/java.gradle"
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/junit.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/junit.gradle"
|
||||||
|
|
||||||
System.setProperty('excludeExtensionLibrary', 'true')
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/extension_test.gradle"
|
|
||||||
|
|
||||||
version = rootProject.versionString
|
version = rootProject.versionString
|
||||||
group = 'io.xpipe'
|
group = 'io.xpipe'
|
||||||
archivesBaseName = 'xpipe-api'
|
archivesBaseName = 'xpipe-api'
|
||||||
|
@ -19,14 +15,14 @@ repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
dependencies {
|
||||||
enabled = false
|
testImplementation project(':api')
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':core')
|
api project(':core')
|
||||||
implementation project(':beacon')
|
implementation project(':beacon')
|
||||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.15.2"
|
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.16.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
@ -38,6 +34,5 @@ task dist(type: Copy) {
|
||||||
into "${project(':dist').buildDir}/dist/libraries"
|
into "${project(':dist').buildDir}/dist/libraries"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
apply from: 'publish.gradle'
|
apply from: 'publish.gradle'
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/publish-base.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/publish-base.gradle"
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.api.test;
|
package io.xpipe.api.test;
|
||||||
|
|
||||||
import io.xpipe.beacon.BeaconDaemonController;
|
import io.xpipe.beacon.test.BeaconDaemonController;
|
||||||
import io.xpipe.core.util.XPipeDaemonMode;
|
import io.xpipe.core.util.XPipeDaemonMode;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
14
api/src/test/java/io/xpipe/api/test/StartupTest.java
Normal file
14
api/src/test/java/io/xpipe/api/test/StartupTest.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package io.xpipe.api.test;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.test.BeaconDaemonController;
|
||||||
|
import io.xpipe.core.util.XPipeDaemonMode;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class StartupTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
BeaconDaemonController.start(XPipeDaemonMode.TRAY);
|
||||||
|
BeaconDaemonController.stop();
|
||||||
|
}
|
||||||
|
}
|
156
app/build.gradle
156
app/build.gradle
|
@ -1,131 +1,70 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
id 'jvm-test-suite'
|
||||||
|
id 'java-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
|
||||||
dep
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/java.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/java.gradle"
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/javafx.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/javafx.gradle"
|
||||||
apply from: "$projectDir/gradle_scripts/richtextfx.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/commons.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/prettytime.gradle"
|
|
||||||
apply from: "$projectDir/gradle_scripts/sentry.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle"
|
||||||
apply from: "$projectDir/gradle_scripts/github-api.gradle"
|
|
||||||
apply from: "$projectDir/gradle_scripts/flexmark.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/picocli.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/versioncompare.gradle"
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/markdowngenerator.gradle"
|
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
implementation.extendsFrom(dep)
|
implementation.extendsFrom(javafx)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':api')
|
api project(':core')
|
||||||
implementation project(':core')
|
api project(':beacon')
|
||||||
implementation project(':beacon')
|
|
||||||
|
|
||||||
compileOnly 'org.hamcrest:hamcrest:2.2'
|
compileOnly 'org.hamcrest:hamcrest:2.2'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.3'
|
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.10.2'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.9.3'
|
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.10.2'
|
||||||
|
|
||||||
implementation 'net.java.dev.jna:jna-jpms:5.13.0'
|
api 'com.vladsch.flexmark:flexmark:0.64.0'
|
||||||
implementation 'net.java.dev.jna:jna-platform-jpms:5.13.0'
|
api 'com.vladsch.flexmark:flexmark-util-data:0.64.0'
|
||||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.15.2"
|
api 'com.vladsch.flexmark:flexmark-util-ast:0.64.0'
|
||||||
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.15.2"
|
api 'com.vladsch.flexmark:flexmark-util-builder:0.64.0'
|
||||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.15.2"
|
api 'com.vladsch.flexmark:flexmark-util-sequence:0.64.0'
|
||||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.15.2"
|
api 'com.vladsch.flexmark:flexmark-util-misc:0.64.0'
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
|
api 'com.vladsch.flexmark:flexmark-util-dependency:0.64.0'
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
api 'com.vladsch.flexmark:flexmark-util-collection:0.64.0'
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
api 'com.vladsch.flexmark:flexmark-util-format:0.64.0'
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: "12.2.0"
|
api 'com.vladsch.flexmark:flexmark-util-html:0.64.0'
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-feather-pack', version: "12.2.0"
|
api 'com.vladsch.flexmark:flexmark-util-visitor:0.64.0'
|
||||||
implementation (name: 'preferencesfx-core-11.15.0')
|
|
||||||
implementation (group: 'com.dlsc.formsfx', name: 'formsfx-core', version: '11.6.0') {
|
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||||
exclude group: 'org.openjfx', module: 'javafx-controls'
|
api 'info.picocli:picocli:4.7.5'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-fxml'
|
api 'org.kohsuke:github-api:1.318'
|
||||||
}
|
api 'io.sentry:sentry:7.3.0'
|
||||||
implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.7'
|
api 'org.ocpsoft.prettytime:prettytime:5.0.2.Final'
|
||||||
implementation 'io.xpipe:modulefs:0.1.4'
|
api 'commons-io:commons-io:2.15.1'
|
||||||
implementation 'com.jfoenix:jfoenix:9.0.10'
|
api 'net.java.dev.jna:jna-jpms:5.14.0'
|
||||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
api 'net.java.dev.jna:jna-platform-jpms:5.14.0'
|
||||||
implementation 'net.synedra:validatorfx:0.4.2'
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.16.1"
|
||||||
implementation ('io.github.mkpaz:atlantafx-base:2.0.1') {
|
api group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.16.1"
|
||||||
|
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.16.1"
|
||||||
|
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.16.1"
|
||||||
|
api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
|
||||||
|
api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
||||||
|
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||||
|
api group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: "12.2.0"
|
||||||
|
api group: 'org.kordamp.ikonli', name: 'ikonli-feather-pack', version: "12.2.0"
|
||||||
|
api group: 'org.slf4j', name: 'slf4j-api', version: '2.0.11'
|
||||||
|
api 'io.xpipe:modulefs:0.1.5'
|
||||||
|
api 'net.synedra:validatorfx:0.4.2'
|
||||||
|
api ('io.github.mkpaz:atlantafx-base:2.0.1') {
|
||||||
exclude group: 'org.openjfx', module: 'javafx-base'
|
exclude group: 'org.openjfx', module: 'javafx-base'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-controls'
|
exclude group: 'org.openjfx', module: 'javafx-controls'
|
||||||
}
|
}
|
||||||
implementation name: 'jSystemThemeDetector-3.8'
|
|
||||||
implementation group: 'com.github.oshi', name: 'oshi-core-java11', version: '6.4.2'
|
|
||||||
implementation 'org.jetbrains:annotations:24.0.1'
|
|
||||||
implementation ('de.jangassen:jfa:1.2.0') {
|
|
||||||
exclude group: 'net.java.dev.jna', module: 'jna'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/gradle/gradle_scripts/junit.gradle"
|
apply from: "$rootDir/gradle/gradle_scripts/local_junit_suite.gradle"
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
output.resourcesDir("${project.layout.buildDirectory.get()}/classes/java/main")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testImplementation project(':api')
|
|
||||||
testImplementation project(':core')
|
|
||||||
}
|
|
||||||
|
|
||||||
project.allExtensions.forEach((Project p) -> {
|
|
||||||
dependencies {
|
|
||||||
testCompileOnly p
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
project.ext {
|
|
||||||
jvmRunArgs = [
|
|
||||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.graphics/com.sun.javafx.stage=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.base/com.sun.javafx.binding=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.base/com.sun.javafx.event=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix",
|
|
||||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls",
|
|
||||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls",
|
|
||||||
"--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.app",
|
|
||||||
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
|
||||||
"--add-opens", "java.base/java.nio.file=io.xpipe.app",
|
|
||||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
|
||||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
|
||||||
"--add-opens", "java.base/java.lang=io.xpipe.core",
|
|
||||||
"--add-opens", "java.desktop/java.awt=io.xpipe.app",
|
|
||||||
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app",
|
|
||||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app',
|
|
||||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app',
|
|
||||||
"-Xmx8g",
|
|
||||||
"-Dio.xpipe.app.arch=$rootProject.arch",
|
|
||||||
"-Dfile.encoding=UTF-8",
|
|
||||||
// Disable this for now as it requires Windows 10+
|
|
||||||
// '-XX:+UseZGC',
|
|
||||||
"-Dvisualvm.display.name=XPipe"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
import org.gradle.internal.os.OperatingSystem
|
|
||||||
|
|
||||||
if (OperatingSystem.current() == OperatingSystem.LINUX) {
|
|
||||||
jvmRunArgs.addAll("--add-opens", "java.desktop/sun.awt.X11=io.xpipe.app")
|
|
||||||
}
|
|
||||||
|
|
||||||
def extensionJarDepList = project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)).toList();
|
def extensionJarDepList = project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)).toList();
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
finalizedBy(extensionJarDepList)
|
finalizedBy(extensionJarDepList)
|
||||||
}
|
}
|
||||||
|
@ -146,14 +85,19 @@ run {
|
||||||
systemProperty 'io.xpipe.app.developerMode', "true"
|
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||||
systemProperty 'io.xpipe.app.logLevel', "trace"
|
systemProperty 'io.xpipe.app.logLevel', "trace"
|
||||||
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
|
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
|
||||||
systemProperty 'io.xpipe.app.showcase', 'false'
|
systemProperty 'io.xpipe.app.showcase', 'true'
|
||||||
|
systemProperty 'io.xpipe.app.staging', isStage
|
||||||
// systemProperty "io.xpipe.beacon.port", "21724"
|
// systemProperty "io.xpipe.beacon.port", "21724"
|
||||||
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
||||||
// systemProperty 'io.xpipe.app.debugPlatform', "true"
|
// systemProperty 'io.xpipe.app.debugPlatform', "true"
|
||||||
|
|
||||||
// systemProperty "io.xpipe.beacon.localProxy", "true"
|
// Apply passed xpipe properties
|
||||||
|
for (final def e in System.getProperties().entrySet()) {
|
||||||
|
if (e.getKey().toString().contains("xpipe")) {
|
||||||
|
systemProperty e.getKey().toString(), e.getValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
systemProperty 'java.library.path', "./lib"
|
|
||||||
workingDir = rootDir
|
workingDir = rootDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +125,7 @@ processResources {
|
||||||
|
|
||||||
javaexec {
|
javaexec {
|
||||||
workingDir = project.projectDir
|
workingDir = project.projectDir
|
||||||
jvmArgs += "--module-path=$sourceSets.main.runtimeClasspath.asPath,"
|
jvmArgs += "--module-path=${configurations.javafx.asFileTree.asPath},"
|
||||||
jvmArgs += "--add-modules=javafx.graphics"
|
jvmArgs += "--add-modules=javafx.graphics"
|
||||||
main = "com.sun.javafx.css.parser.Css2Bin"
|
main = "com.sun.javafx.css.parser.Css2Bin"
|
||||||
args css
|
args css
|
||||||
|
|
4
app/src/localTest/java/module-info.java
Normal file
4
app/src/localTest/java/module-info.java
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
open module io.xpipe.app.localTest {
|
||||||
|
requires org.junit.jupiter.api;
|
||||||
|
requires io.xpipe.app;
|
||||||
|
}
|
13
app/src/localTest/java/test/Test.java
Normal file
13
app/src/localTest/java/test/Test.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package test;
|
||||||
|
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.test.LocalExtensionTest;
|
||||||
|
|
||||||
|
public class Test extends LocalExtensionTest {
|
||||||
|
|
||||||
|
@org.junit.jupiter.api.Test
|
||||||
|
public void test() {
|
||||||
|
System.out.println("a");
|
||||||
|
System.out.println(DataStorage.get().getStoreEntries());
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,42 @@ import io.xpipe.app.core.AppWindowHelper;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BrowserAlerts {
|
public class BrowserAlerts {
|
||||||
|
|
||||||
|
public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) {
|
||||||
|
var map = new LinkedHashMap<ButtonType, FileConflictChoice>();
|
||||||
|
map.put(new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL);
|
||||||
|
if (multiple) {
|
||||||
|
map.put(new ButtonType("Skip", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP);
|
||||||
|
map.put(new ButtonType("Skip All", ButtonBar.ButtonData.OTHER), FileConflictChoice.SKIP_ALL);
|
||||||
|
}
|
||||||
|
map.put(new ButtonType("Replace", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE);
|
||||||
|
if (multiple) {
|
||||||
|
map.put(new ButtonType("Replace All", ButtonBar.ButtonData.OTHER), FileConflictChoice.REPLACE_ALL);
|
||||||
|
}
|
||||||
|
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.setTitle(AppI18n.get("fileConflictAlertTitle"));
|
||||||
|
alert.setHeaderText(AppI18n.get("fileConflictAlertHeader"));
|
||||||
|
AppWindowHelper.setContent(
|
||||||
|
alert,
|
||||||
|
AppI18n.get(
|
||||||
|
multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file));
|
||||||
|
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||||
|
alert.getButtonTypes().clear();
|
||||||
|
map.sequencedKeySet()
|
||||||
|
.forEach(buttonType -> alert.getButtonTypes().add(buttonType));
|
||||||
|
})
|
||||||
|
.map(map::get)
|
||||||
|
.orElse(FileConflictChoice.CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean showMoveAlert(List<FileSystem.FileEntry> source, FileSystem.FileEntry target) {
|
public static boolean showMoveAlert(List<FileSystem.FileEntry> source, FileSystem.FileEntry target) {
|
||||||
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -52,4 +82,12 @@ public class BrowserAlerts {
|
||||||
}
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum FileConflictChoice {
|
||||||
|
CANCEL,
|
||||||
|
SKIP,
|
||||||
|
SKIP_ALL,
|
||||||
|
REPLACE,
|
||||||
|
REPLACE_ALL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
137
app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java
Normal file
137
app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
|
import io.xpipe.app.comp.store.StoreSection;
|
||||||
|
import io.xpipe.app.comp.store.StoreSectionMiniComp;
|
||||||
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.util.BooleanScope;
|
||||||
|
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||||
|
import io.xpipe.app.util.FixedHierarchyStore;
|
||||||
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
final class BrowserBookmarkComp extends SimpleComp {
|
||||||
|
|
||||||
|
public static final Timer DROP_TIMER = new Timer("dnd", true);
|
||||||
|
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||||
|
private final BrowserModel model;
|
||||||
|
private Point2D lastOver = new Point2D(-1, -1);
|
||||||
|
private TimerTask activeTask;
|
||||||
|
|
||||||
|
BrowserBookmarkComp(BrowserModel model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var filterText = new SimpleStringProperty();
|
||||||
|
var open = PlatformThread.sync(model.getSelected());
|
||||||
|
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||||
|
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|
||||||
|
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
|
||||||
|
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||||
|
};
|
||||||
|
var selectedCategory = new SimpleObjectProperty<>(
|
||||||
|
StoreViewState.get().getActiveCategory().getValue());
|
||||||
|
var section = StoreSectionMiniComp.createList(
|
||||||
|
StoreSection.createTopLevel(
|
||||||
|
StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory),
|
||||||
|
(s, comp) -> {
|
||||||
|
BooleanProperty busy = new SimpleBooleanProperty(false);
|
||||||
|
comp.disable(Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
return busy.get() || !applicable.test(s.getWrapper());
|
||||||
|
},
|
||||||
|
busy));
|
||||||
|
comp.apply(struc -> {
|
||||||
|
open.addListener((observable, oldValue, newValue) -> {
|
||||||
|
struc.get()
|
||||||
|
.pseudoClassStateChanged(
|
||||||
|
SELECTED,
|
||||||
|
newValue != null
|
||||||
|
&& newValue.getEntry()
|
||||||
|
.get()
|
||||||
|
.equals(s.getWrapper()
|
||||||
|
.getEntry()));
|
||||||
|
});
|
||||||
|
struc.get().setOnAction(event -> {
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
var entry = s.getWrapper().getEntry();
|
||||||
|
if (!entry.getValidity().isUsable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.getStore() instanceof ShellStore fileSystem) {
|
||||||
|
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||||
|
} else if (entry.getStore() instanceof FixedHierarchyStore) {
|
||||||
|
BooleanScope.execute(busy, () -> {
|
||||||
|
s.getWrapper().refreshChildren();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var category = new DataStoreCategoryChoiceComp(
|
||||||
|
StoreViewState.get().getAllConnectionsCategory(),
|
||||||
|
StoreViewState.get().getActiveCategory(),
|
||||||
|
selectedCategory)
|
||||||
|
.styleClass(Styles.LEFT_PILL);
|
||||||
|
var filter =
|
||||||
|
new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
|
||||||
|
|
||||||
|
var top = new HorizontalComp(List.of(category.minWidth(Region.USE_PREF_SIZE), filter.hgrow()))
|
||||||
|
.styleClass("categories")
|
||||||
|
.apply(struc -> {
|
||||||
|
AppFont.medium(struc.get());
|
||||||
|
struc.get().setFillHeight(true);
|
||||||
|
})
|
||||||
|
.createRegion();
|
||||||
|
var r = section.vgrow().createRegion();
|
||||||
|
var content = new VBox(top, r);
|
||||||
|
content.setFillWidth(true);
|
||||||
|
|
||||||
|
content.getStyleClass().add("bookmark-list");
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHoverTimer(DataStore store, DragEvent event) {
|
||||||
|
if (lastOver.getX() == event.getX() && lastOver.getY() == event.getY()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastOver = (new Point2D(event.getX(), event.getY()));
|
||||||
|
activeTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (activeTask != this) {}
|
||||||
|
|
||||||
|
// Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DROP_TIMER.schedule(activeTask, 500);
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ public class BrowserBreadcrumbBar extends SimpleComp {
|
||||||
var elements = FileNames.splitHierarchy(val);
|
var elements = FileNames.splitHierarchy(val);
|
||||||
var modifiedElements = new ArrayList<>(elements);
|
var modifiedElements = new ArrayList<>(elements);
|
||||||
if (val.startsWith("/")) {
|
if (val.startsWith("/")) {
|
||||||
modifiedElements.add(0, "/");
|
modifiedElements.addFirst("/");
|
||||||
}
|
}
|
||||||
Breadcrumbs.BreadCrumbItem<String> items =
|
Breadcrumbs.BreadCrumbItem<String> items =
|
||||||
Breadcrumbs.buildTreeModel(modifiedElements.toArray(String[]::new));
|
Breadcrumbs.buildTreeModel(modifiedElements.toArray(String[]::new));
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.ShellDialects;
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
import io.xpipe.core.util.FailableRunnable;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
@ -24,18 +24,6 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BrowserClipboard {
|
public class BrowserClipboard {
|
||||||
|
|
||||||
@Value
|
|
||||||
public static class Instance {
|
|
||||||
UUID uuid;
|
|
||||||
FileSystem.FileEntry baseDirectory;
|
|
||||||
List<FileSystem.FileEntry> entries;
|
|
||||||
|
|
||||||
public String toClipboardString() {
|
|
||||||
return entries.stream().map(fileEntry -> "\"" + fileEntry.getPath() + "\"").collect(
|
|
||||||
Collectors.joining(ShellDialects.getPlatformDefault().getNewLine().getNewLineString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
|
public static final Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
|
||||||
public static Instance currentDragClipboard;
|
public static Instance currentDragClipboard;
|
||||||
|
|
||||||
|
@ -45,7 +33,7 @@ public class BrowserClipboard {
|
||||||
.addFlavorListener(e -> ThreadHelper.runFailableAsync(new FailableRunnable<>() {
|
.addFlavorListener(e -> ThreadHelper.runFailableAsync(new FailableRunnable<>() {
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void run() throws Throwable {
|
public void run() {
|
||||||
Clipboard clipboard = (Clipboard) e.getSource();
|
Clipboard clipboard = (Clipboard) e.getSource();
|
||||||
try {
|
try {
|
||||||
if (!clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
|
if (!clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
|
||||||
|
@ -53,7 +41,8 @@ public class BrowserClipboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
|
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
|
||||||
var files = data.stream().map(string -> string.toPath()).toList();
|
var files =
|
||||||
|
data.stream().map(string -> string.toPath()).toList();
|
||||||
if (files.size() == 0) {
|
if (files.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -121,4 +110,20 @@ public class BrowserClipboard {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class Instance {
|
||||||
|
UUID uuid;
|
||||||
|
FileSystem.FileEntry baseDirectory;
|
||||||
|
List<FileSystem.FileEntry> entries;
|
||||||
|
|
||||||
|
public String toClipboardString() {
|
||||||
|
return entries.stream()
|
||||||
|
.map(fileEntry -> "\"" + fileEntry.getPath() + "\"")
|
||||||
|
.collect(Collectors.joining(ProcessControlProvider.get()
|
||||||
|
.getEffectiveLocalDialect()
|
||||||
|
.getNewLine()
|
||||||
|
.getNewLineString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,9 +58,10 @@ public class BrowserComp extends SimpleComp {
|
||||||
FileIconManager.loadIfNecessary();
|
FileIconManager.loadIfNecessary();
|
||||||
});
|
});
|
||||||
|
|
||||||
var bookmarksList = new BrowserBookmarkList(model).vgrow();
|
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
|
||||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
|
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
||||||
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
|
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
if (model.getOpenFileSystems().size() == 0) {
|
if (model.getOpenFileSystems().size() == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -69,20 +70,18 @@ public class BrowserComp extends SimpleComp {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also show on local
|
|
||||||
if (model.getSelected().getValue() != null) {
|
|
||||||
// return model.getSelected().getValue().isLocal();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, model.getOpenFileSystems(), model.getSelected())));
|
},
|
||||||
|
model.getOpenFileSystems(),
|
||||||
|
model.getSelected())));
|
||||||
localDownloadStage.prefHeight(200);
|
localDownloadStage.prefHeight(200);
|
||||||
localDownloadStage.maxHeight(200);
|
localDownloadStage.maxHeight(200);
|
||||||
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
|
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
|
||||||
|
|
||||||
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
|
var splitPane = new SideSplitPaneComp(vertical, createTabs())
|
||||||
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
|
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||||
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
|
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||||
|
.apply(struc -> {
|
||||||
struc.getLeft().setMinWidth(200);
|
struc.getLeft().setMinWidth(200);
|
||||||
struc.getLeft().setMaxWidth(500);
|
struc.getLeft().setMaxWidth(500);
|
||||||
});
|
});
|
||||||
|
@ -104,12 +103,16 @@ public class BrowserComp extends SimpleComp {
|
||||||
selected.setSpacing(10);
|
selected.setSpacing(10);
|
||||||
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
selected.getChildren().setAll(c.getList().stream().map(s -> {
|
selected.getChildren()
|
||||||
var field = new TextField(s.getRawFileEntry().getPath());
|
.setAll(c.getList().stream()
|
||||||
|
.map(s -> {
|
||||||
|
var field =
|
||||||
|
new TextField(s.getRawFileEntry().getPath());
|
||||||
field.setEditable(false);
|
field.setEditable(false);
|
||||||
field.setPrefWidth(500);
|
field.setPrefWidth(500);
|
||||||
return field;
|
return field;
|
||||||
}).toList());
|
})
|
||||||
|
.toList());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||||
|
@ -128,12 +131,16 @@ public class BrowserComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comp<?> createTabs() {
|
private Comp<?> createTabs() {
|
||||||
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(Comp.of(() -> createTabPane()),
|
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
|
||||||
|
Comp.of(() -> createTabPane()),
|
||||||
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
|
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
|
||||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
||||||
Bindings.createBooleanBinding(() -> {
|
Bindings.createBooleanBinding(
|
||||||
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
|
() -> {
|
||||||
}, model.getOpenFileSystems())));
|
return model.getOpenFileSystems().size() == 0
|
||||||
|
&& !model.getMode().isChooser();
|
||||||
|
},
|
||||||
|
model.getOpenFileSystems())));
|
||||||
return multi;
|
return multi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +161,8 @@ public class BrowserComp extends SimpleComp {
|
||||||
map.put(v, t);
|
map.put(v, t);
|
||||||
tabs.getTabs().add(t);
|
tabs.getTabs().add(t);
|
||||||
});
|
});
|
||||||
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
|
tabs.getSelectionModel()
|
||||||
|
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
|
||||||
|
|
||||||
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
|
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
|
||||||
var modifying = new SimpleBooleanProperty();
|
var modifying = new SimpleBooleanProperty();
|
||||||
|
@ -170,9 +178,9 @@ public class BrowserComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var source = map.entrySet()
|
var source = map.entrySet().stream()
|
||||||
.stream()
|
.filter(openFileSystemModelTabEntry ->
|
||||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
|
openFileSystemModelTabEntry.getValue().equals(newValue))
|
||||||
.findAny()
|
.findAny()
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -187,9 +195,9 @@ public class BrowserComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var toSelect = map.entrySet()
|
var toSelect = map.entrySet().stream()
|
||||||
.stream()
|
.filter(openFileSystemModelTabEntry ->
|
||||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
|
openFileSystemModelTabEntry.getKey().equals(newValue))
|
||||||
.findAny()
|
.findAny()
|
||||||
.map(Map.Entry::getValue)
|
.map(Map.Entry::getValue)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -228,9 +236,9 @@ public class BrowserComp extends SimpleComp {
|
||||||
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
|
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
|
||||||
while (c.next()) {
|
while (c.next()) {
|
||||||
for (var r : c.getRemoved()) {
|
for (var r : c.getRemoved()) {
|
||||||
var source = map.entrySet()
|
var source = map.entrySet().stream()
|
||||||
.stream()
|
.filter(openFileSystemModelTabEntry ->
|
||||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
|
openFileSystemModelTabEntry.getValue().equals(r))
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
|
@ -253,14 +261,22 @@ public class BrowserComp extends SimpleComp {
|
||||||
ring.setMinSize(16, 16);
|
ring.setMinSize(16, 16);
|
||||||
ring.setPrefSize(16, 16);
|
ring.setPrefSize(16, 16);
|
||||||
ring.setMaxSize(16, 16);
|
ring.setMaxSize(16, 16);
|
||||||
ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
ring.progressProperty()
|
||||||
|
.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
||||||
|
|
||||||
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
|
var image = model.getEntry()
|
||||||
|
.get()
|
||||||
|
.getProvider()
|
||||||
|
.getDisplayIconFileName(model.getEntry().getStore());
|
||||||
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
|
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
|
||||||
|
|
||||||
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
|
tab.graphicProperty()
|
||||||
|
.bind(Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
return model.getBusy().get() ? ring : logo;
|
return model.getBusy().get() ? ring : logo;
|
||||||
}, PlatformThread.sync(model.getBusy())));
|
},
|
||||||
|
PlatformThread.sync(model.getBusy())));
|
||||||
tab.setText(model.getName());
|
tab.setText(model.getName());
|
||||||
|
|
||||||
tab.setContent(new OpenFileSystemComp(model).createSimple());
|
tab.setContent(new OpenFileSystemComp(model).createSimple());
|
||||||
|
@ -281,12 +297,17 @@ public class BrowserComp extends SimpleComp {
|
||||||
|
|
||||||
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
||||||
c.getStyleClass().add("color-box");
|
c.getStyleClass().add("color-box");
|
||||||
var color = DataStorage.get().getRootForEntry(model.getEntry().get()).getColor();
|
var color = DataStorage.get()
|
||||||
|
.getRootForEntry(model.getEntry().get())
|
||||||
|
.getColor();
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
c.getStyleClass().add(color.getId());
|
c.getStyleClass().add(color.getId());
|
||||||
}
|
}
|
||||||
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
|
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
|
||||||
c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
|
c.addEventHandler(
|
||||||
|
DragEvent.DRAG_ENTERED,
|
||||||
|
mouseEvent -> Platform.runLater(
|
||||||
|
() -> tabs.getSelectionModel().select(tab)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,17 @@ final class BrowserContextMenu extends ContextMenu {
|
||||||
createMenu();
|
createMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<BrowserEntry> resolveIfNeeded(BrowserAction action, List<BrowserEntry> selected) {
|
||||||
|
return action.automaticallyResolveLinks()
|
||||||
|
? selected.stream()
|
||||||
|
.map(browserEntry -> new BrowserEntry(
|
||||||
|
browserEntry.getRawFileEntry().resolved(),
|
||||||
|
browserEntry.getModel(),
|
||||||
|
browserEntry.isSynthetic()))
|
||||||
|
.toList()
|
||||||
|
: selected;
|
||||||
|
}
|
||||||
|
|
||||||
private void createMenu() {
|
private void createMenu() {
|
||||||
AppFont.normal(this.getStyleableNode());
|
AppFont.normal(this.getStyleableNode());
|
||||||
|
|
||||||
|
@ -81,7 +92,10 @@ final class BrowserContextMenu extends ContextMenu {
|
||||||
}
|
}
|
||||||
m.setDisable(!a.isActive(model, used));
|
m.setDisable(!a.isActive(model, used));
|
||||||
|
|
||||||
if (la.getProFeatureId() != null && !LicenseProvider.get().getFeature(la.getProFeatureId()).isSupported()) {
|
if (la.getProFeatureId() != null
|
||||||
|
&& !LicenseProvider.get()
|
||||||
|
.getFeature(la.getProFeatureId())
|
||||||
|
.isSupported()) {
|
||||||
m.setDisable(true);
|
m.setDisable(true);
|
||||||
m.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
|
m.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
|
||||||
}
|
}
|
||||||
|
@ -91,15 +105,4 @@ final class BrowserContextMenu extends ContextMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<BrowserEntry> resolveIfNeeded(BrowserAction action, List<BrowserEntry> selected) {
|
|
||||||
return action.automaticallyResolveLinks()
|
|
||||||
? selected.stream()
|
|
||||||
.map(browserEntry -> new BrowserEntry(
|
|
||||||
browserEntry.getRawFileEntry().resolved(),
|
|
||||||
browserEntry.getModel(),
|
|
||||||
browserEntry.isSynthetic()))
|
|
||||||
.toList()
|
|
||||||
: selected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.browser.icon.DirectoryType;
|
import io.xpipe.app.browser.icon.DirectoryType;
|
||||||
import io.xpipe.app.browser.icon.FileType;
|
import io.xpipe.app.browser.icon.FileType;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.HumanReadableFormat;
|
import io.xpipe.app.util.HumanReadableFormat;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -81,18 +81,18 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
||||||
|
|
||||||
var sizeCol = new TableColumn<BrowserEntry, Number>("Size");
|
var sizeCol = new TableColumn<BrowserEntry, Number>("Size");
|
||||||
sizeCol.setCellValueFactory(param ->
|
sizeCol.setCellValueFactory(param -> new SimpleLongProperty(
|
||||||
new SimpleLongProperty(param.getValue().getRawFileEntry().resolved().getSize()));
|
param.getValue().getRawFileEntry().resolved().getSize()));
|
||||||
sizeCol.setCellFactory(col -> new FileSizeCell());
|
sizeCol.setCellFactory(col -> new FileSizeCell());
|
||||||
|
|
||||||
var mtimeCol = new TableColumn<BrowserEntry, Instant>("Modified");
|
var mtimeCol = new TableColumn<BrowserEntry, Instant>("Modified");
|
||||||
mtimeCol.setCellValueFactory(param ->
|
mtimeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
|
||||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getDate()));
|
param.getValue().getRawFileEntry().resolved().getDate()));
|
||||||
mtimeCol.setCellFactory(col -> new FileTimeCell());
|
mtimeCol.setCellFactory(col -> new FileTimeCell());
|
||||||
|
|
||||||
var modeCol = new TableColumn<BrowserEntry, String>("Attributes");
|
var modeCol = new TableColumn<BrowserEntry, String>("Attributes");
|
||||||
modeCol.setCellValueFactory(param ->
|
modeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
|
||||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getMode()));
|
param.getValue().getRawFileEntry().resolved().getMode()));
|
||||||
modeCol.setCellFactory(col -> new FileModeCell());
|
modeCol.setCellFactory(col -> new FileModeCell());
|
||||||
modeCol.setSortable(false);
|
modeCol.setSortable(false);
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||||
.toArray();
|
.toArray();
|
||||||
table.getSelectionModel()
|
table.getSelectionModel()
|
||||||
.selectIndices(table.getItems().indexOf(c.getList().get(0)), indices);
|
.selectIndices(table.getItems().indexOf(c.getList().getFirst()), indices);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -247,12 +247,20 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.getItem() != null
|
if (row.getItem() != null
|
||||||
&& row.getItem().getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
&& row.getItem()
|
||||||
|
.getRawFileEntry()
|
||||||
|
.resolved()
|
||||||
|
.getKind()
|
||||||
|
== FileKind.DIRECTORY) {
|
||||||
return event.getButton() == MouseButton.SECONDARY;
|
return event.getButton() == MouseButton.SECONDARY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.getItem() != null
|
if (row.getItem() != null
|
||||||
&& row.getItem().getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) {
|
&& row.getItem()
|
||||||
|
.getRawFileEntry()
|
||||||
|
.resolved()
|
||||||
|
.getKind()
|
||||||
|
!= FileKind.DIRECTORY) {
|
||||||
return event.getButton() == MouseButton.SECONDARY
|
return event.getButton() == MouseButton.SECONDARY
|
||||||
|| event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2;
|
|| event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2;
|
||||||
}
|
}
|
||||||
|
@ -409,9 +417,10 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
double proximity = 100;
|
double proximity = 100;
|
||||||
Bounds tableBounds = tableView.localToScene(tableView.getBoundsInParent());
|
Bounds tableBounds = tableView.localToScene(tableView.getBoundsInLocal());
|
||||||
double dragY = event.getSceneY();
|
double dragY = event.getSceneY();
|
||||||
double topYProximity = tableBounds.getMinY() + proximity;
|
// Include table header as well in calculations
|
||||||
|
double topYProximity = tableBounds.getMinY() + proximity + 20;
|
||||||
double bottomYProximity = tableBounds.getMaxY() - proximity;
|
double bottomYProximity = tableBounds.getMaxY() - proximity;
|
||||||
|
|
||||||
// clamp new values between 0 and 1 to prevent scrollbar flicking around at the edges
|
// clamp new values between 0 and 1 to prevent scrollbar flicking around at the edges
|
||||||
|
@ -424,15 +433,60 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class FileSizeCell extends TableCell<BrowserEntry, Number> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Number fileSize, boolean empty) {
|
||||||
|
super.updateItem(fileSize, empty);
|
||||||
|
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
||||||
|
setText(null);
|
||||||
|
} else {
|
||||||
|
var path = getTableRow().getItem();
|
||||||
|
if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
||||||
|
setText("");
|
||||||
|
} else {
|
||||||
|
setText(byteCount(fileSize.longValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FileModeCell extends TableCell<BrowserEntry, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(String mode, boolean empty) {
|
||||||
|
super.updateItem(mode, empty);
|
||||||
|
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
||||||
|
setText(null);
|
||||||
|
} else {
|
||||||
|
setText(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FileTimeCell extends TableCell<BrowserEntry, Instant> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Instant fileTime, boolean empty) {
|
||||||
|
super.updateItem(fileTime, empty);
|
||||||
|
if (empty) {
|
||||||
|
setText(null);
|
||||||
|
} else {
|
||||||
|
setText(
|
||||||
|
fileTime != null
|
||||||
|
? HumanReadableFormat.date(
|
||||||
|
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
|
||||||
|
: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class FilenameCell extends TableCell<BrowserEntry, String> {
|
private class FilenameCell extends TableCell<BrowserEntry, String> {
|
||||||
|
|
||||||
private final StringProperty img = new SimpleStringProperty();
|
private final StringProperty img = new SimpleStringProperty();
|
||||||
private final StringProperty text = new SimpleStringProperty();
|
private final StringProperty text = new SimpleStringProperty();
|
||||||
private final Node imageView = new PrettySvgComp(img, 24, 24)
|
|
||||||
.createRegion();
|
|
||||||
private final StackPane textField =
|
private final StackPane textField =
|
||||||
new LazyTextFieldComp(text).createStructure().get();
|
new LazyTextFieldComp(text).createStructure().get();
|
||||||
private final HBox graphic;
|
|
||||||
|
|
||||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
@ -463,7 +517,8 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
};
|
};
|
||||||
text.addListener(listener);
|
text.addListener(listener);
|
||||||
|
|
||||||
graphic = new HBox(imageView, textField);
|
Node imageView = new PrettySvgComp(img, 24, 24).createRegion();
|
||||||
|
HBox graphic = new HBox(imageView, textField);
|
||||||
graphic.setSpacing(10);
|
graphic.setSpacing(10);
|
||||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||||
|
@ -520,52 +575,4 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FileSizeCell extends TableCell<BrowserEntry, Number> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Number fileSize, boolean empty) {
|
|
||||||
super.updateItem(fileSize, empty);
|
|
||||||
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
|
||||||
setText(null);
|
|
||||||
} else {
|
|
||||||
var path = getTableRow().getItem();
|
|
||||||
if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
|
||||||
setText("");
|
|
||||||
} else {
|
|
||||||
setText(byteCount(fileSize.longValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FileModeCell extends TableCell<BrowserEntry, String> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(String mode, boolean empty) {
|
|
||||||
super.updateItem(mode, empty);
|
|
||||||
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
|
||||||
setText(null);
|
|
||||||
} else {
|
|
||||||
setText(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FileTimeCell extends TableCell<BrowserEntry, Instant> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Instant fileTime, boolean empty) {
|
|
||||||
super.updateItem(fileTime, empty);
|
|
||||||
if (empty) {
|
|
||||||
setText(null);
|
|
||||||
} else {
|
|
||||||
setText(
|
|
||||||
fileTime != null
|
|
||||||
? HumanReadableFormat.date(
|
|
||||||
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
|
|
||||||
: "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ public class BrowserFileListCompEntry {
|
||||||
private Point2D lastOver = new Point2D(-1, -1);
|
private Point2D lastOver = new Point2D(-1, -1);
|
||||||
private TimerTask activeTask;
|
private TimerTask activeTask;
|
||||||
|
|
||||||
public BrowserFileListCompEntry(TableView<BrowserEntry> tv, Node row, BrowserEntry item, BrowserFileListModel model) {
|
public BrowserFileListCompEntry(
|
||||||
|
TableView<BrowserEntry> tv, Node row, BrowserEntry item, BrowserFileListModel model) {
|
||||||
this.tv = tv;
|
this.tv = tv;
|
||||||
this.row = row;
|
this.row = row;
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
@ -59,11 +60,15 @@ public class BrowserFileListCompEntry {
|
||||||
|
|
||||||
var all = tv.getItems();
|
var all = tv.getItems();
|
||||||
var index = item != null ? all.indexOf(item) : all.size() - 1;
|
var index = item != null ? all.indexOf(item) : all.size() - 1;
|
||||||
var min = Math.min(index, tv.getSelectionModel().getSelectedIndices().stream()
|
var min = Math.min(
|
||||||
|
index,
|
||||||
|
tv.getSelectionModel().getSelectedIndices().stream()
|
||||||
.mapToInt(value -> value)
|
.mapToInt(value -> value)
|
||||||
.min()
|
.min()
|
||||||
.orElse(1));
|
.orElse(1));
|
||||||
var max = Math.max(index, tv.getSelectionModel().getSelectedIndices().stream()
|
var max = Math.max(
|
||||||
|
index,
|
||||||
|
tv.getSelectionModel().getSelectedIndices().stream()
|
||||||
.mapToInt(value -> value)
|
.mapToInt(value -> value)
|
||||||
.max()
|
.max()
|
||||||
.orElse(all.indexOf(item)));
|
.orElse(all.indexOf(item)));
|
||||||
|
@ -98,13 +103,15 @@ public class BrowserFileListCompEntry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Objects.equals(model.getFileSystemModel().getFileSystem(), cb.getEntries().get(0).getFileSystem())) {
|
if (!Objects.equals(
|
||||||
|
model.getFileSystemModel().getFileSystem(),
|
||||||
|
cb.getEntries().getFirst().getFileSystem())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent drag and drops of files into the current directory
|
// Prevent drag and drops of files into the current directory
|
||||||
if (cb.getBaseDirectory() != null && cb
|
if (cb.getBaseDirectory() != null
|
||||||
.getBaseDirectory()
|
&& cb.getBaseDirectory()
|
||||||
.getPath()
|
.getPath()
|
||||||
.equals(model.getFileSystemModel().getCurrentDirectory().getPath())
|
.equals(model.getFileSystemModel().getCurrentDirectory().getPath())
|
||||||
&& (item == null || item.getRawFileEntry().getKind() != FileKind.DIRECTORY)) {
|
&& (item == null || item.getRawFileEntry().getKind() != FileKind.DIRECTORY)) {
|
||||||
|
|
|
@ -2,8 +2,8 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -99,8 +99,9 @@ public final class BrowserFileListModel {
|
||||||
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
||||||
var comp = comparatorProperty.getValue();
|
var comp = comparatorProperty.getValue();
|
||||||
|
|
||||||
Comparator<? super BrowserEntry> us =
|
Comparator<? super BrowserEntry> us = comp != null
|
||||||
comp != null ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) : syntheticFirst.thenComparing(dirsFirst);
|
? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp)
|
||||||
|
: syntheticFirst.thenComparing(dirsFirst);
|
||||||
l.sort(us);
|
l.sort(us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,14 +111,17 @@ public final class BrowserFileListModel {
|
||||||
|
|
||||||
boolean exists;
|
boolean exists;
|
||||||
try {
|
try {
|
||||||
exists = fileSystemModel.getFileSystem().fileExists(newFullPath) || fileSystemModel.getFileSystem().directoryExists(newFullPath);
|
exists = fileSystemModel.getFileSystem().fileExists(newFullPath)
|
||||||
|
|| fileSystemModel.getFileSystem().directoryExists(newFullPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist").expected().handle();
|
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist")
|
||||||
|
.expected()
|
||||||
|
.handle();
|
||||||
fileSystemModel.refresh();
|
fileSystemModel.refresh();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,14 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
||||||
|
|
||||||
|
private final OpenFileSystemModel model;
|
||||||
|
private final Property<String> filterString;
|
||||||
|
|
||||||
|
public BrowserFilterComp(OpenFileSystemModel model, Property<String> filterString) {
|
||||||
|
this.model = model;
|
||||||
|
this.filterString = filterString;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Structure createBase() {
|
public Structure createBase() {
|
||||||
var expanded = new SimpleBooleanProperty();
|
var expanded = new SimpleBooleanProperty();
|
||||||
|
@ -98,12 +106,4 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final OpenFileSystemModel model;
|
|
||||||
private final Property<String> filterString;
|
|
||||||
|
|
||||||
public BrowserFilterComp(OpenFileSystemModel model, Property<String> filterString) {
|
|
||||||
this.model = model;
|
|
||||||
this.filterString = filterString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
@ -11,6 +13,16 @@ public class BrowserGreetingComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
|
var r = new Label(getText());
|
||||||
|
AppLayoutModel.get().getSelected().addListener((observableValue, entry, t1) -> {
|
||||||
|
r.setText(getText());
|
||||||
|
});
|
||||||
|
AppFont.setSize(r, 7);
|
||||||
|
r.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getText() {
|
||||||
var ldt = LocalDateTime.now();
|
var ldt = LocalDateTime.now();
|
||||||
var hour = ldt.getHour();
|
var hour = ldt.getHour();
|
||||||
String text;
|
String text;
|
||||||
|
@ -21,8 +33,6 @@ public class BrowserGreetingComp extends SimpleComp {
|
||||||
} else {
|
} else {
|
||||||
text = "Good afternoon";
|
text = "Good afternoon";
|
||||||
}
|
}
|
||||||
var r = new Label(text);
|
return text;
|
||||||
AppFont.setSize(r, 7);
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class BrowserModel {
|
||||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||||
private final BrowserSavedState savedState;
|
private final BrowserSavedState savedState;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private Consumer<List<FileReference>> onFinish;
|
private Consumer<List<FileReference>> onFinish;
|
||||||
|
|
||||||
|
@ -70,12 +71,21 @@ public class BrowserModel {
|
||||||
public void reset() {
|
public void reset() {
|
||||||
synchronized (BrowserModel.this) {
|
synchronized (BrowserModel.this) {
|
||||||
for (OpenFileSystemModel o : new ArrayList<>(openFileSystems)) {
|
for (OpenFileSystemModel o : new ArrayList<>(openFileSystems)) {
|
||||||
|
// Don't close busy connections gracefully
|
||||||
|
// as we otherwise might lock up
|
||||||
|
if (o.isBusy()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
closeFileSystemSync(o);
|
closeFileSystemSync(o);
|
||||||
}
|
}
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
savedState.save();
|
savedState.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete all files
|
||||||
|
localTransfersStage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void finishChooser() {
|
public void finishChooser() {
|
||||||
|
@ -95,8 +105,10 @@ public class BrowserModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stores = chosen.stream().map(
|
var stores = chosen.stream()
|
||||||
entry -> new FileReference(selected.getValue().getEntry(), entry.getRawFileEntry().getPath())).toList();
|
.map(entry -> new FileReference(
|
||||||
|
selected.getValue().getEntry(), entry.getRawFileEntry().getPath()))
|
||||||
|
.toList();
|
||||||
onFinish.accept(stores);
|
onFinish.accept(stores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +119,11 @@ public class BrowserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeFileSystemSync(OpenFileSystemModel open) {
|
private void closeFileSystemSync(OpenFileSystemModel open) {
|
||||||
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) && savedState != null && open.getCurrentPath().get() != null) {
|
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get())
|
||||||
savedState.add(new BrowserSavedState.Entry(open.getEntry().get().getUuid(), open.getCurrentPath().get()));
|
&& savedState != null
|
||||||
|
&& open.getCurrentPath().get() != null) {
|
||||||
|
savedState.add(new BrowserSavedState.Entry(
|
||||||
|
open.getEntry().get().getUuid(), open.getCurrentPath().get()));
|
||||||
}
|
}
|
||||||
open.closeSync();
|
open.closeSync();
|
||||||
synchronized (BrowserModel.this) {
|
synchronized (BrowserModel.this) {
|
||||||
|
@ -116,7 +131,10 @@ public class BrowserModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, FailableFunction<OpenFileSystemModel, String, Exception> path, BooleanProperty externalBusy) {
|
public void openFileSystemAsync(
|
||||||
|
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||||
|
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||||
|
BooleanProperty externalBusy) {
|
||||||
if (store == null) {
|
if (store == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,6 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
})
|
})
|
||||||
.augment(new SimpleCompStructure<>(homeButton));
|
.augment(new SimpleCompStructure<>(homeButton));
|
||||||
|
|
||||||
|
|
||||||
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
|
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
|
||||||
historyButton.setAccessibleText("History");
|
historyButton.setAccessibleText("History");
|
||||||
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
|
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
|
||||||
|
@ -146,7 +145,6 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
.maxHeightProperty()
|
.maxHeightProperty()
|
||||||
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
||||||
|
|
||||||
|
|
||||||
((Region) struc.get().getChildren().get(2))
|
((Region) struc.get().getChildren().get(2))
|
||||||
.minHeightProperty()
|
.minHeightProperty()
|
||||||
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
||||||
|
@ -197,7 +195,8 @@ public class BrowserNavBar extends SimpleComp {
|
||||||
cm.getItems().add(current);
|
cm.getItems().add(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream().toList();
|
var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream()
|
||||||
|
.toList();
|
||||||
if (!b.isEmpty()) {
|
if (!b.isEmpty()) {
|
||||||
cm.getItems().add(new SeparatorMenuItem());
|
cm.getItems().add(new SeparatorMenuItem());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import java.util.UUID;
|
||||||
|
|
||||||
public interface BrowserSavedState {
|
public interface BrowserSavedState {
|
||||||
|
|
||||||
public void add(Entry entry);
|
void add(Entry entry);
|
||||||
|
|
||||||
void save();
|
void save();
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ public interface BrowserSavedState {
|
||||||
@Value
|
@Value
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
@Builder
|
@Builder
|
||||||
public static class Entry {
|
class Entry {
|
||||||
|
|
||||||
UUID uuid;
|
UUID uuid;
|
||||||
String path;
|
String path;
|
||||||
|
|
|
@ -20,12 +20,6 @@ import java.util.List;
|
||||||
@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class)
|
@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class)
|
||||||
public class BrowserSavedStateImpl implements BrowserSavedState {
|
public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||||
|
|
||||||
static BrowserSavedStateImpl load() {
|
|
||||||
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
|
||||||
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerialize(as = List.class)
|
@JsonSerialize(as = List.class)
|
||||||
ObservableList<Entry> lastSystems;
|
ObservableList<Entry> lastSystems;
|
||||||
|
|
||||||
|
@ -33,25 +27,10 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||||
this.lastSystems = FXCollections.observableArrayList(lastSystems);
|
this.lastSystems = FXCollections.observableArrayList(lastSystems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Deserializer extends StdDeserializer<BrowserSavedStateImpl> {
|
static BrowserSavedStateImpl load() {
|
||||||
|
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
||||||
protected Deserializer() {
|
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
||||||
super(BrowserSavedStateImpl.class);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
|
|
||||||
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
|
||||||
JavaType javaType = JacksonMapper.getDefault()
|
|
||||||
.getTypeFactory()
|
|
||||||
.constructCollectionLikeType(List.class, Entry.class);
|
|
||||||
List<Entry> ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType);
|
|
||||||
if (ls == null) {
|
|
||||||
ls = List.of();
|
|
||||||
}
|
|
||||||
return new BrowserSavedStateImpl(ls);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,4 +51,24 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||||
public ObservableList<Entry> getEntries() {
|
public ObservableList<Entry> getEntries() {
|
||||||
return lastSystems;
|
return lastSystems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Deserializer extends StdDeserializer<BrowserSavedStateImpl> {
|
||||||
|
|
||||||
|
protected Deserializer() {
|
||||||
|
super(BrowserSavedStateImpl.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||||
|
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
||||||
|
JavaType javaType =
|
||||||
|
JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(List.class, Entry.class);
|
||||||
|
List<Entry> ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType);
|
||||||
|
if (ls == null) {
|
||||||
|
ls = List.of();
|
||||||
|
}
|
||||||
|
return new BrowserSavedStateImpl(ls);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,13 @@ import java.util.function.Function;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BrowserSelectionListComp extends SimpleComp {
|
public class BrowserSelectionListComp extends SimpleComp {
|
||||||
|
|
||||||
|
ObservableList<FileSystem.FileEntry> list;
|
||||||
|
Function<FileSystem.FileEntry, ObservableValue<String>> nameTransformation;
|
||||||
|
|
||||||
|
public BrowserSelectionListComp(ObservableList<FileSystem.FileEntry> list) {
|
||||||
|
this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath())));
|
||||||
|
}
|
||||||
|
|
||||||
public static Image snapshot(ObservableList<FileSystem.FileEntry> list) {
|
public static Image snapshot(ObservableList<FileSystem.FileEntry> list) {
|
||||||
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
||||||
var scene = new Scene(r);
|
var scene = new Scene(r);
|
||||||
|
@ -41,13 +48,6 @@ public class BrowserSelectionListComp extends SimpleComp {
|
||||||
return r.snapshot(parameters, null);
|
return r.snapshot(parameters, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ObservableList<FileSystem.FileEntry> list;
|
|
||||||
Function<FileSystem.FileEntry, ObservableValue<String>> nameTransformation;
|
|
||||||
|
|
||||||
public BrowserSelectionListComp(ObservableList<FileSystem.FileEntry> list) {
|
|
||||||
this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var c = new ListBoxViewComp<>(list, list, entry -> {
|
var c = new ListBoxViewComp<>(list, list, entry -> {
|
||||||
|
|
|
@ -2,11 +2,13 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
import atlantafx.base.controls.Spacer;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.util.HumanReadableFormat;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
@ -21,6 +23,57 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
|
var bar = new ToolBar();
|
||||||
|
bar.getItems()
|
||||||
|
.setAll(
|
||||||
|
createClipboardStatus().createRegion(),
|
||||||
|
createProgressStatus().createRegion(),
|
||||||
|
new Spacer(),
|
||||||
|
createSelectionStatus().createRegion());
|
||||||
|
bar.getStyleClass().add("status-bar");
|
||||||
|
bar.setOnDragDetected(event -> {
|
||||||
|
event.consume();
|
||||||
|
bar.startFullDrag();
|
||||||
|
});
|
||||||
|
AppFont.small(bar);
|
||||||
|
|
||||||
|
simulateEmptyCell(bar);
|
||||||
|
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> createProgressStatus() {
|
||||||
|
var transferredCount = PlatformThread.sync(Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
return HumanReadableFormat.byteCount(
|
||||||
|
model.getProgress().getValue().getTransferred());
|
||||||
|
},
|
||||||
|
model.getProgress()));
|
||||||
|
var allCount = PlatformThread.sync(Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
return HumanReadableFormat.byteCount(
|
||||||
|
model.getProgress().getValue().getTotal());
|
||||||
|
},
|
||||||
|
model.getProgress()));
|
||||||
|
var progressComp = new LabelComp(Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
if (model.getProgress().getValue() == null
|
||||||
|
|| model.getProgress().getValue().done()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var name = (model.getProgress().getValue().getName() != null
|
||||||
|
? " @ " + model.getProgress().getValue().getName() + " "
|
||||||
|
: "");
|
||||||
|
return transferredCount.getValue() + " / " + allCount.getValue() + name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transferredCount,
|
||||||
|
allCount,
|
||||||
|
model.getProgress()));
|
||||||
|
return progressComp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> createClipboardStatus() {
|
||||||
var cc = PlatformThread.sync(BrowserClipboard.currentCopyClipboard);
|
var cc = PlatformThread.sync(BrowserClipboard.currentCopyClipboard);
|
||||||
var ccCount = Bindings.createStringBinding(
|
var ccCount = Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -32,7 +85,10 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cc);
|
cc);
|
||||||
|
return new LabelComp(ccCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> createSelectionStatus() {
|
||||||
var selectedCount = PlatformThread.sync(Bindings.createIntegerBinding(
|
var selectedCount = PlatformThread.sync(Bindings.createIntegerBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return model.getFileList().getSelection().size();
|
return model.getFileList().getSelection().size();
|
||||||
|
@ -46,7 +102,6 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
.count();
|
.count();
|
||||||
},
|
},
|
||||||
model.getFileList().getAll()));
|
model.getFileList().getAll()));
|
||||||
|
|
||||||
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (selectedCount.getValue().intValue() == 0) {
|
if (selectedCount.getValue().intValue() == 0) {
|
||||||
|
@ -57,19 +112,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
},
|
},
|
||||||
selectedCount,
|
selectedCount,
|
||||||
allCount));
|
allCount));
|
||||||
|
return selectedComp;
|
||||||
var bar = new ToolBar();
|
|
||||||
bar.getItems().setAll(new LabelComp(ccCount).createRegion(), new Spacer(), selectedComp.createRegion());
|
|
||||||
bar.getStyleClass().add("status-bar");
|
|
||||||
bar.setOnDragDetected(event -> {
|
|
||||||
event.consume();
|
|
||||||
bar.startFullDrag();
|
|
||||||
});
|
|
||||||
AppFont.small(bar);
|
|
||||||
|
|
||||||
simulateEmptyCell(bar);
|
|
||||||
|
|
||||||
return bar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void simulateEmptyCell(Region r) {
|
private void simulateEmptyCell(Region r) {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import io.xpipe.app.fxcomps.impl.*;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import javafx.scene.input.Dragboard;
|
import javafx.scene.input.Dragboard;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
@ -29,48 +29,61 @@ import java.util.Optional;
|
||||||
|
|
||||||
public class BrowserTransferComp extends SimpleComp {
|
public class BrowserTransferComp extends SimpleComp {
|
||||||
|
|
||||||
private final BrowserTransferModel stage;
|
private final BrowserTransferModel model;
|
||||||
|
|
||||||
public BrowserTransferComp(BrowserTransferModel stage) {
|
public BrowserTransferComp(BrowserTransferModel model) {
|
||||||
this.stage = stage;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
||||||
.visible(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())));
|
.visible(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
|
||||||
var backgroundStack =
|
var backgroundStack =
|
||||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
||||||
|
|
||||||
var binding = BindingsHelper.mappedContentBinding(stage.getItems(), item -> item.getFileEntry());
|
var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
|
||||||
var list = new BrowserSelectionListComp(binding, entry -> Bindings.createStringBinding(() -> {
|
var list = new BrowserSelectionListComp(
|
||||||
var sourceItem = stage.getItems().stream().filter(item -> item.getFileEntry() == entry).findAny();
|
binding,
|
||||||
|
entry -> Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var sourceItem = model.getItems().stream()
|
||||||
|
.filter(item -> item.getFileEntry() == entry)
|
||||||
|
.findAny();
|
||||||
if (sourceItem.isEmpty()) {
|
if (sourceItem.isEmpty()) {
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
var name = sourceItem.get().getFinishedDownload().get() ? "Local" : DataStorage.get().getStoreDisplayName(entry.getFileSystem().getStore()).orElse("?");
|
var name =
|
||||||
|
sourceItem.get().downloadFinished().get()
|
||||||
|
? "Local"
|
||||||
|
: DataStorage.get()
|
||||||
|
.getStoreDisplayName(entry.getFileSystem()
|
||||||
|
.getStore())
|
||||||
|
.orElse("?");
|
||||||
return FileNames.getFileName(entry.getPath()) + " (" + name + ")";
|
return FileNames.getFileName(entry.getPath()) + " (" + name + ")";
|
||||||
}, stage.getAllDownloaded()))
|
},
|
||||||
|
model.getAllDownloaded()))
|
||||||
.apply(struc -> struc.get().setMinHeight(150))
|
.apply(struc -> struc.get().setMinHeight(150))
|
||||||
.grow(false, true);
|
.grow(false, true);
|
||||||
var dragNotice = new LabelComp(stage.getAllDownloaded().flatMap(aBoolean -> aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
|
var dragNotice = new LabelComp(model.getAllDownloaded()
|
||||||
|
.flatMap(aBoolean ->
|
||||||
|
aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export")))
|
||||||
.hide(PlatformThread.sync(
|
.hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
|
||||||
BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))))
|
|
||||||
.grow(true, false)
|
.grow(true, false)
|
||||||
.apply(struc -> struc.get().setPadding(new Insets(8)));
|
.apply(struc -> struc.get().setPadding(new Insets(8)));
|
||||||
|
|
||||||
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
|
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
|
||||||
stage.download();
|
model.download();
|
||||||
})
|
})
|
||||||
.hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())))
|
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())))
|
||||||
.disable(PlatformThread.sync(stage.getAllDownloaded()))
|
.disable(PlatformThread.sync(model.getAllDownloaded()))
|
||||||
.apply(new FancyTooltipAugment<>("downloadStageDescription"));
|
.apply(new FancyTooltipAugment<>("downloadStageDescription"));
|
||||||
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
||||||
stage.clear();
|
model.clear();
|
||||||
})
|
})
|
||||||
.hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())));
|
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
|
||||||
var clearPane = Comp.derive(
|
var clearPane = Comp.derive(
|
||||||
new HorizontalComp(List.of(downloadButton, clearButton))
|
new HorizontalComp(List.of(downloadButton, clearButton))
|
||||||
.apply(struc -> struc.get().setSpacing(10)),
|
.apply(struc -> struc.get().setSpacing(10)),
|
||||||
|
@ -93,37 +106,56 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
event.acceptTransferModes(TransferMode.ANY);
|
event.acceptTransferModes(TransferMode.ANY);
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accept drops from outside the app window
|
||||||
|
if (event.getGestureSource() == null
|
||||||
|
&& !event.getDragboard().getFiles().isEmpty()) {
|
||||||
|
event.acceptTransferModes(TransferMode.ANY);
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
struc.get().setOnDragDropped(event -> {
|
struc.get().setOnDragDropped(event -> {
|
||||||
|
// Accept drops from inside the app window
|
||||||
if (event.getGestureSource() != null) {
|
if (event.getGestureSource() != null) {
|
||||||
var files = BrowserClipboard.retrieveDrag(event.getDragboard())
|
var drag = BrowserClipboard.retrieveDrag(event.getDragboard());
|
||||||
.getEntries();
|
if (drag == null) {
|
||||||
stage.drop(files);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = drag.getEntries();
|
||||||
|
model.drop(
|
||||||
|
model.getBrowserModel()
|
||||||
|
.getSelected()
|
||||||
|
.getValue(),
|
||||||
|
files);
|
||||||
|
event.setDropCompleted(true);
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept drops from outside the app window
|
||||||
|
if (event.getGestureSource() == null) {
|
||||||
|
model.dropLocal(event.getDragboard().getFiles());
|
||||||
event.setDropCompleted(true);
|
event.setDropCompleted(true);
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
struc.get().setOnDragDetected(event -> {
|
struc.get().setOnDragDetected(event -> {
|
||||||
if (stage.getDownloading().get()) {
|
if (model.getDownloading().get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag within browser
|
var selected = model.getItems().stream()
|
||||||
if (!stage.getAllDownloaded().get()) {
|
.map(BrowserTransferModel.Item::getFileEntry)
|
||||||
var selected = stage.getItems().stream().map(item -> item.getFileEntry()).toList();
|
.toList();
|
||||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
||||||
db.setContent(BrowserClipboard.startDrag(null, selected));
|
|
||||||
|
|
||||||
Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected));
|
var cc = BrowserClipboard.startDrag(null, selected);
|
||||||
db.setDragView(image, -20, 15);
|
if (cc == null) {
|
||||||
|
|
||||||
event.setDragDetect(true);
|
|
||||||
event.consume();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag outside browser
|
var files = model.getItems().stream()
|
||||||
var files = stage.getItems().stream()
|
.filter(item -> item.downloadFinished().get())
|
||||||
.map(item -> {
|
.map(item -> {
|
||||||
try {
|
try {
|
||||||
var file = item.getLocalFile();
|
var file = item.getLocalFile();
|
||||||
|
@ -131,40 +163,35 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
return Optional.<File>empty();
|
return Optional.<File>empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(file
|
return Optional.of(
|
||||||
.toRealPath()
|
file.toRealPath().toFile());
|
||||||
.toFile());
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.flatMap(Optional::stream)
|
.flatMap(Optional::stream)
|
||||||
.toList();
|
.toList();
|
||||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.MOVE);
|
|
||||||
var cc = new ClipboardContent();
|
|
||||||
cc.putFiles(files);
|
cc.putFiles(files);
|
||||||
db.setContent(cc);
|
db.setContent(cc);
|
||||||
|
|
||||||
var image = BrowserSelectionListComp.snapshot(
|
Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected));
|
||||||
FXCollections.observableList(stage.getItems().stream()
|
|
||||||
.map(item -> item.getFileEntry())
|
|
||||||
.toList()));
|
|
||||||
db.setDragView(image, -20, 15);
|
db.setDragView(image, -20, 15);
|
||||||
|
|
||||||
event.setDragDetect(true);
|
event.setDragDetect(true);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
struc.get().setOnDragDone(event -> {
|
struc.get().setOnDragDone(event -> {
|
||||||
// macOS does always report false here
|
// macOS does always report false here, which is unfortunate
|
||||||
if (!event.isAccepted()) {
|
if (!event.isAccepted() && !OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stage.getItems().clear();
|
// Don't clear, it might be more convenient to keep the contents
|
||||||
|
// model.clear();
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
PlatformThread.sync(stage.getDownloading()));
|
PlatformThread.sync(model.getDownloading()));
|
||||||
return stack.styleClass("transfer").createRegion();
|
return stack.styleClass("transfer").createRegion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,23 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
|
import io.xpipe.app.util.ShellTemp;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -21,8 +28,7 @@ import java.util.concurrent.Executors;
|
||||||
@Value
|
@Value
|
||||||
public class BrowserTransferModel {
|
public class BrowserTransferModel {
|
||||||
|
|
||||||
private static final Path TEMP =
|
private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("download");
|
||||||
FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("download");
|
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||||
Thread t = Executors.defaultThreadFactory().newThread(r);
|
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||||
|
@ -30,30 +36,32 @@ public class BrowserTransferModel {
|
||||||
t.setName("file downloader");
|
t.setName("file downloader");
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
|
||||||
@Value
|
|
||||||
public static class Item {
|
|
||||||
String name;
|
|
||||||
FileSystem.FileEntry fileEntry;
|
|
||||||
Path localFile;
|
|
||||||
BooleanProperty finishedDownload = new SimpleBooleanProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
BrowserModel browserModel;
|
BrowserModel browserModel;
|
||||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||||
BooleanProperty downloading = new SimpleBooleanProperty();
|
BooleanProperty downloading = new SimpleBooleanProperty();
|
||||||
BooleanProperty allDownloaded = new SimpleBooleanProperty();
|
BooleanProperty allDownloaded = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public void clear() {
|
private void cleanDirectory() {
|
||||||
try {
|
if (!Files.isDirectory(TEMP)) {
|
||||||
FileUtils.deleteDirectory(TEMP.toFile());
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var ls = Files.list(TEMP)) {
|
||||||
|
var list = ls.toList();
|
||||||
|
for (Path path : list) {
|
||||||
|
FileUtils.forceDelete(path.toFile());
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
cleanDirectory();
|
||||||
items.clear();
|
items.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drop(List<FileSystem.FileEntry> entries) {
|
public void drop(OpenFileSystemModel model, List<FileSystem.FileEntry> entries) {
|
||||||
entries.forEach(entry -> {
|
entries.forEach(entry -> {
|
||||||
var name = FileNames.getFileName(entry.getPath());
|
var name = FileNames.getFileName(entry.getPath());
|
||||||
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
||||||
|
@ -61,12 +69,39 @@ public class BrowserTransferModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Path file = TEMP.resolve(name);
|
Path file = TEMP.resolve(name);
|
||||||
var item = new Item(name, entry, file);
|
var item = new Item(model, name, entry, file);
|
||||||
items.add(item);
|
items.add(item);
|
||||||
allDownloaded.set(false);
|
allDownloaded.set(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dropLocal(List<File> entries) {
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var empty = items.isEmpty();
|
||||||
|
try {
|
||||||
|
var paths = entries.stream().map(File::toPath).filter(Files::exists).toList();
|
||||||
|
for (Path path : paths) {
|
||||||
|
var entry = FileSystemHelper.getLocal(path);
|
||||||
|
var name = entry.getName();
|
||||||
|
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new Item(null, name, entry, path);
|
||||||
|
item.progress.setValue(BrowserTransferProgress.finished(entry.getName(), entry.getSize()));
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
}
|
||||||
|
if (empty) {
|
||||||
|
allDownloaded.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void download() {
|
public void download() {
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
|
@ -77,18 +112,23 @@ public class BrowserTransferModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Item item : new ArrayList<>(items)) {
|
for (Item item : new ArrayList<>(items)) {
|
||||||
if (item.getFinishedDownload().get()) {
|
if (item.downloadFinished().get()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getOpenFileSystemModel() != null
|
||||||
|
&& item.getOpenFileSystemModel().isClosed()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try (var b = new BooleanScope(downloading).start()) {
|
try (var b = new BooleanScope(downloading).start()) {
|
||||||
FileSystemHelper.dropFilesInto(
|
FileSystemHelper.dropFilesInto(
|
||||||
FileSystemHelper.getLocal(TEMP),
|
FileSystemHelper.getLocal(TEMP), List.of(item.getFileEntry()), true, false, progress -> {
|
||||||
List.of(item.getFileEntry()),
|
item.getProgress().setValue(progress);
|
||||||
true);
|
item.getOpenFileSystemModel().getProgress().setValue(progress);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
item.finishedDownload.set(true);
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ErrorEvent.fromThrowable(t).handle();
|
ErrorEvent.fromThrowable(t).handle();
|
||||||
items.remove(item);
|
items.remove(item);
|
||||||
|
@ -97,4 +137,31 @@ public class BrowserTransferModel {
|
||||||
allDownloaded.set(true);
|
allDownloaded.set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class Item {
|
||||||
|
OpenFileSystemModel openFileSystemModel;
|
||||||
|
String name;
|
||||||
|
FileSystem.FileEntry fileEntry;
|
||||||
|
Path localFile;
|
||||||
|
Property<BrowserTransferProgress> progress;
|
||||||
|
|
||||||
|
public Item(
|
||||||
|
OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) {
|
||||||
|
this.openFileSystemModel = openFileSystemModel;
|
||||||
|
this.name = name;
|
||||||
|
this.fileEntry = fileEntry;
|
||||||
|
this.localFile = localFile;
|
||||||
|
this.progress =
|
||||||
|
new SimpleObjectProperty<>(BrowserTransferProgress.empty(fileEntry.getName(), fileEntry.getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableBooleanValue downloadFinished() {
|
||||||
|
return Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
return progress.getValue().done();
|
||||||
|
},
|
||||||
|
progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class BrowserTransferProgress {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
long transferred;
|
||||||
|
long total;
|
||||||
|
|
||||||
|
static BrowserTransferProgress empty() {
|
||||||
|
return new BrowserTransferProgress(null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BrowserTransferProgress empty(String name, long size) {
|
||||||
|
return new BrowserTransferProgress(name, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BrowserTransferProgress finished(String name, long size) {
|
||||||
|
return new BrowserTransferProgress(name, size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean done() {
|
||||||
|
return transferred >= total;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
import atlantafx.base.controls.Spacer;
|
||||||
import atlantafx.base.theme.Styles;
|
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.TileButtonComp;
|
import io.xpipe.app.comp.base.TileButtonComp;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
|
@ -42,7 +42,9 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
||||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75).padding(new Insets(5, 0, 0, 0)).createRegion();
|
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75)
|
||||||
|
.padding(new Insets(5, 0, 0, 0))
|
||||||
|
.createRegion();
|
||||||
var hbox = new HBox(img, vbox);
|
var hbox = new HBox(img, vbox);
|
||||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
hbox.setSpacing(15);
|
hbox.setSpacing(15);
|
||||||
|
@ -68,11 +70,15 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||||
|
|
||||||
var header = new LabelComp(Bindings.createStringBinding(() -> {
|
var header = new LabelComp(Bindings.createStringBinding(
|
||||||
return !empty.get() ? "You were recently connected to the following systems:" :
|
() -> {
|
||||||
"Here you will be able to see where you left off last time.";
|
return !empty.get()
|
||||||
}, empty)).createRegion();
|
? "You were recently connected to the following systems:"
|
||||||
header.getStyleClass().add(Styles.TEXT_MUTED);
|
: "Here you will be able to see where you left off last time.";
|
||||||
|
},
|
||||||
|
empty))
|
||||||
|
.createRegion();
|
||||||
|
AppFont.setSize(header, 1);
|
||||||
vbox.getChildren().add(header);
|
vbox.getChildren().add(header);
|
||||||
|
|
||||||
var storeList = new VBox();
|
var storeList = new VBox();
|
||||||
|
@ -80,21 +86,31 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
|
|
||||||
var listBox = new ListBoxViewComp<>(list, list, e -> {
|
var listBox = new ListBoxViewComp<>(list, list, e -> {
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
var graphic = entry.get()
|
||||||
|
.getProvider()
|
||||||
|
.getDisplayIconFileName(entry.get().getStore());
|
||||||
var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40);
|
var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40);
|
||||||
view.padding(new Insets(2, 8, 2, 8));
|
view.padding(new Insets(2, 8, 2, 8));
|
||||||
var content =
|
var content = JfxHelper.createNamedEntry(
|
||||||
JfxHelper.createNamedEntry(DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
|
DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
|
||||||
var disable = new SimpleBooleanProperty();
|
var disable = new SimpleBooleanProperty();
|
||||||
return new ButtonComp(null, content, () -> {
|
return new ButtonComp(null, content, () -> {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
model.restoreStateAsync(e, disable);
|
model.restoreStateAsync(e, disable);
|
||||||
});
|
});
|
||||||
}).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-listBox").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
|
})
|
||||||
}).apply(struc -> {
|
.accessibleText(DataStorage.get().getStoreDisplayName(entry.get()))
|
||||||
|
.disable(disable)
|
||||||
|
.styleClass("color-listBox")
|
||||||
|
.apply(struc -> struc.get().setMaxWidth(2000))
|
||||||
|
.grow(true, false);
|
||||||
|
})
|
||||||
|
.apply(struc -> {
|
||||||
VBox vBox = (VBox) struc.get().getContent();
|
VBox vBox = (VBox) struc.get().getContent();
|
||||||
vBox.setSpacing(10);
|
vBox.setSpacing(10);
|
||||||
}).hide(empty).createRegion();
|
})
|
||||||
|
.hide(empty)
|
||||||
|
.createRegion();
|
||||||
|
|
||||||
var layout = new VBox();
|
var layout = new VBox();
|
||||||
layout.getStyleClass().add("welcome");
|
layout.getStyleClass().add("welcome");
|
||||||
|
@ -109,7 +125,10 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
||||||
model.restoreState(state);
|
model.restoreState(state);
|
||||||
actionEvent.consume();
|
actionEvent.consume();
|
||||||
}).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions");
|
})
|
||||||
|
.grow(true, false)
|
||||||
|
.hide(empty)
|
||||||
|
.accessibleTextKey("restoreAllSessions");
|
||||||
layout.getChildren().add(tile.createRegion());
|
layout.getChildren().add(tile.createRegion());
|
||||||
|
|
||||||
return layout;
|
return layout;
|
||||||
|
|
|
@ -7,13 +7,22 @@ import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import io.xpipe.core.store.LocalStore;
|
import io.xpipe.core.store.LocalStore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class FileSystemHelper {
|
public class FileSystemHelper {
|
||||||
|
|
||||||
|
private static final int DEFAULT_BUFFER_SIZE = 16384;
|
||||||
|
private static FileSystem localFileSystem;
|
||||||
|
|
||||||
public static String adjustPath(OpenFileSystemModel model, String path) {
|
public static String adjustPath(OpenFileSystemModel model, String path) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -114,7 +123,8 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!model.getFileSystem().directoryExists(path)) {
|
if (!model.getFileSystem().directoryExists(path)) {
|
||||||
throw ErrorEvent.unreportable(new IllegalArgumentException(String.format("Directory %s does not exist", path)));
|
throw ErrorEvent.unreportable(
|
||||||
|
new IllegalArgumentException(String.format("Directory %s does not exist", path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -125,8 +135,6 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileSystem localFileSystem;
|
|
||||||
|
|
||||||
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
|
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
|
||||||
if (localFileSystem == null) {
|
if (localFileSystem == null) {
|
||||||
localFileSystem = new LocalStore().createFileSystem();
|
localFileSystem = new LocalStore().createFileSystem();
|
||||||
|
@ -144,8 +152,8 @@ public class FileSystemHelper {
|
||||||
Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dropLocalFilesInto(FileSystem.FileEntry entry, List<Path> files) {
|
public static void dropLocalFilesInto(
|
||||||
try {
|
FileSystem.FileEntry entry, List<Path> files, Consumer<BrowserTransferProgress> progress, boolean checkConflicts) throws Exception {
|
||||||
var entries = files.stream()
|
var entries = files.stream()
|
||||||
.map(path -> {
|
.map(path -> {
|
||||||
try {
|
try {
|
||||||
|
@ -155,14 +163,11 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
dropFilesInto(entry, entries, false);
|
dropFilesInto(entry, entries, false, checkConflicts, progress);
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void delete(List<FileSystem.FileEntry> files) {
|
public static void delete(List<FileSystem.FileEntry> files) {
|
||||||
if (files.size() == 0) {
|
if (files.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,22 +181,43 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dropFilesInto(
|
public static void dropFilesInto(
|
||||||
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) throws Exception {
|
FileSystem.FileEntry target,
|
||||||
if (files.size() == 0) {
|
List<FileSystem.FileEntry> files,
|
||||||
|
boolean explicitCopy,
|
||||||
|
boolean checkConflicts,
|
||||||
|
Consumer<BrowserTransferProgress> progress)
|
||||||
|
throws Exception {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
progress.accept(BrowserTransferProgress.empty());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
|
||||||
|
if (same && !explicitCopy) {
|
||||||
|
if (!BrowserAlerts.showMoveAlert(files, target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice = new AtomicReference<>();
|
||||||
for (var file : files) {
|
for (var file : files) {
|
||||||
if (file.getFileSystem().equals(target.getFileSystem())) {
|
if (file.getFileSystem().equals(target.getFileSystem())) {
|
||||||
dropFileAcrossSameFileSystem(target, file, explicitCopy);
|
dropFileAcrossSameFileSystem(target, file, explicitCopy, lastConflictChoice, files.size() > 1, checkConflicts);
|
||||||
|
progress.accept(BrowserTransferProgress.finished(file.getName(), file.getSize()));
|
||||||
} else {
|
} else {
|
||||||
dropFileAcrossFileSystems(target, file);
|
dropFileAcrossFileSystems(target, file, progress, lastConflictChoice, files.size() > 1, checkConflicts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dropFileAcrossSameFileSystem(
|
private static void dropFileAcrossSameFileSystem(
|
||||||
FileSystem.FileEntry target, FileSystem.FileEntry source, boolean explicitCopy) throws Exception {
|
FileSystem.FileEntry target,
|
||||||
|
FileSystem.FileEntry source,
|
||||||
|
boolean explicitCopy,
|
||||||
|
AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice,
|
||||||
|
boolean multiple,
|
||||||
|
boolean checkConflicts)
|
||||||
|
throws Exception {
|
||||||
// Prevent dropping directory into itself
|
// Prevent dropping directory into itself
|
||||||
if (source.getPath().equals(target.getPath())) {
|
if (source.getPath().equals(target.getPath())) {
|
||||||
return;
|
return;
|
||||||
|
@ -205,7 +231,12 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) {
|
if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) {
|
||||||
throw ErrorEvent.unreportable(new IllegalArgumentException("Target directory " + targetFile + " does already exist"));
|
throw ErrorEvent.unreportable(
|
||||||
|
new IllegalArgumentException("Target directory " + targetFile + " does already exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkConflicts && !handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (explicitCopy) {
|
if (explicitCopy) {
|
||||||
|
@ -215,7 +246,13 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dropFileAcrossFileSystems(FileSystem.FileEntry target, FileSystem.FileEntry source)
|
private static void dropFileAcrossFileSystems(
|
||||||
|
FileSystem.FileEntry target,
|
||||||
|
FileSystem.FileEntry source,
|
||||||
|
Consumer<BrowserTransferProgress> progress,
|
||||||
|
AtomicReference<BrowserAlerts.FileConflictChoice> lastConflictChoice,
|
||||||
|
boolean multiple,
|
||||||
|
boolean checkConflicts)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
if (target.getKind() != FileKind.DIRECTORY) {
|
if (target.getKind() != FileKind.DIRECTORY) {
|
||||||
throw new IllegalStateException("Target " + target.getPath() + " is not a directory");
|
throw new IllegalStateException("Target " + target.getPath() + " is not a directory");
|
||||||
|
@ -229,19 +266,27 @@ public class FileSystemHelper {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AtomicLong totalSize = new AtomicLong();
|
||||||
if (source.getKind() == FileKind.DIRECTORY) {
|
if (source.getKind() == FileKind.DIRECTORY) {
|
||||||
var directoryName = FileNames.getFileName(source.getPath());
|
var directoryName = FileNames.getFileName(source.getPath());
|
||||||
flatFiles.put(source, directoryName);
|
flatFiles.put(source, directoryName);
|
||||||
|
|
||||||
var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath()));
|
var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath()));
|
||||||
List<FileSystem.FileEntry> list = source.getFileSystem().listFilesRecursively(source.getPath());
|
List<FileSystem.FileEntry> list = source.getFileSystem().listFilesRecursively(source.getPath());
|
||||||
list.forEach(fileEntry -> {
|
for (FileSystem.FileEntry fileEntry : list) {
|
||||||
flatFiles.put(fileEntry, FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath())));
|
flatFiles.put(fileEntry, FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath())));
|
||||||
});
|
if (fileEntry.getKind() == FileKind.FILE) {
|
||||||
|
// This one is up-to-date and does not need to be recalculated
|
||||||
|
totalSize.addAndGet(fileEntry.getSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
flatFiles.put(source, FileNames.getFileName(source.getPath()));
|
flatFiles.put(source, FileNames.getFileName(source.getPath()));
|
||||||
|
// Recalculate as it could have been changed meanwhile
|
||||||
|
totalSize.addAndGet(source.getFileSystem().getFileSize(source.getPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AtomicLong transferred = new AtomicLong();
|
||||||
for (var e : flatFiles.entrySet()) {
|
for (var e : flatFiles.entrySet()) {
|
||||||
var sourceFile = e.getKey();
|
var sourceFile = e.getKey();
|
||||||
var targetFile = FileNames.join(target.getPath(), e.getValue());
|
var targetFile = FileNames.join(target.getPath(), e.getValue());
|
||||||
|
@ -252,11 +297,127 @@ public class FileSystemHelper {
|
||||||
if (sourceFile.getKind() == FileKind.DIRECTORY) {
|
if (sourceFile.getKind() == FileKind.DIRECTORY) {
|
||||||
target.getFileSystem().mkdirs(targetFile);
|
target.getFileSystem().mkdirs(targetFile);
|
||||||
} else if (sourceFile.getKind() == FileKind.FILE) {
|
} else if (sourceFile.getKind() == FileKind.FILE) {
|
||||||
try (var in = sourceFile.getFileSystem().openInput(sourceFile.getPath());
|
if (checkConflicts && !handleChoice(
|
||||||
var out = target.getFileSystem().openOutput(targetFile)) {
|
lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) {
|
||||||
in.transferTo(out);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream inputStream = null;
|
||||||
|
OutputStream outputStream = null;
|
||||||
|
try {
|
||||||
|
var fileSize = sourceFile.getFileSystem().getFileSize(sourceFile.getPath());
|
||||||
|
inputStream = sourceFile.getFileSystem().openInput(sourceFile.getPath());
|
||||||
|
outputStream = target.getFileSystem().openOutput(targetFile, fileSize);
|
||||||
|
transferFile(sourceFile, inputStream, outputStream, transferred, totalSize, progress);
|
||||||
|
inputStream.transferTo(OutputStream.nullOutputStream());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Mark progress as finished to reset any progress display
|
||||||
|
progress.accept(BrowserTransferProgress.finished(sourceFile.getName(), transferred.get()));
|
||||||
|
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (Exception om) {
|
||||||
|
// This is expected as the process control has to be killed
|
||||||
|
// When calling close, it will throw an exception when it has to kill
|
||||||
|
// ErrorEvent.fromThrowable(om).handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outputStream != null) {
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
} catch (Exception om) {
|
||||||
|
// This is expected as the process control has to be killed
|
||||||
|
// When calling close, it will throw an exception when it has to kill
|
||||||
|
// ErrorEvent.fromThrowable(om).handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception exception = null;
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (Exception om) {
|
||||||
|
exception = om;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
} catch (Exception om) {
|
||||||
|
if (exception != null) {
|
||||||
|
ErrorEvent.fromThrowable(om).handle();
|
||||||
|
} else {
|
||||||
|
exception = om;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress.accept(BrowserTransferProgress.finished(source.getName(), totalSize.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean handleChoice(
|
||||||
|
AtomicReference<BrowserAlerts.FileConflictChoice> previous,
|
||||||
|
FileSystem fileSystem,
|
||||||
|
String target,
|
||||||
|
boolean multiple)
|
||||||
|
throws Exception {
|
||||||
|
if (previous.get() == BrowserAlerts.FileConflictChoice.CANCEL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous.get() == BrowserAlerts.FileConflictChoice.REPLACE_ALL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileSystem.fileExists(target)) {
|
||||||
|
if (previous.get() == BrowserAlerts.FileConflictChoice.SKIP_ALL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var choice = BrowserAlerts.showFileConflictAlert(target, multiple);
|
||||||
|
if (choice == BrowserAlerts.FileConflictChoice.CANCEL) {
|
||||||
|
previous.set(BrowserAlerts.FileConflictChoice.CANCEL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == BrowserAlerts.FileConflictChoice.SKIP) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == BrowserAlerts.FileConflictChoice.SKIP_ALL) {
|
||||||
|
previous.set(BrowserAlerts.FileConflictChoice.SKIP_ALL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) {
|
||||||
|
previous.set(BrowserAlerts.FileConflictChoice.REPLACE_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void transferFile(
|
||||||
|
FileSystem.FileEntry sourceFile,
|
||||||
|
InputStream inputStream,
|
||||||
|
OutputStream outputStream,
|
||||||
|
AtomicLong transferred,
|
||||||
|
AtomicLong total,
|
||||||
|
Consumer<BrowserTransferProgress> progress)
|
||||||
|
throws IOException {
|
||||||
|
// Initialize progress immediately prior to reading anything
|
||||||
|
progress.accept(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get()));
|
||||||
|
|
||||||
|
var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize());
|
||||||
|
byte[] buffer = new byte[bs];
|
||||||
|
int read;
|
||||||
|
while ((read = inputStream.read(buffer, 0, bs)) > 0) {
|
||||||
|
outputStream.write(buffer, 0, read);
|
||||||
|
transferred.addAndGet(read);
|
||||||
|
progress.accept(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,12 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalLauncher;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.*;
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
import io.xpipe.core.process.ShellDialects;
|
||||||
|
import io.xpipe.core.process.ShellOpenFunction;
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
import io.xpipe.core.util.FailableConsumer;
|
import io.xpipe.core.util.FailableConsumer;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -27,20 +30,21 @@ import java.util.stream.Stream;
|
||||||
public final class OpenFileSystemModel {
|
public final class OpenFileSystemModel {
|
||||||
|
|
||||||
private final DataStoreEntryRef<? extends FileSystemStore> entry;
|
private final DataStoreEntryRef<? extends FileSystemStore> entry;
|
||||||
private FileSystem fileSystem;
|
|
||||||
private final Property<String> filter = new SimpleStringProperty();
|
private final Property<String> filter = new SimpleStringProperty();
|
||||||
private final BrowserFileListModel fileList;
|
private final BrowserFileListModel fileList;
|
||||||
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
||||||
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
||||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
private final BrowserModel browserModel;
|
private final BrowserModel browserModel;
|
||||||
private OpenFileSystemSavedState savedState;
|
|
||||||
private OpenFileSystemCache cache;
|
|
||||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
||||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String tooltip;
|
private final String tooltip;
|
||||||
private boolean local;
|
private final Property<BrowserTransferProgress> progress =
|
||||||
|
new SimpleObjectProperty<>(BrowserTransferProgress.empty());
|
||||||
|
private FileSystem fileSystem;
|
||||||
|
private OpenFileSystemSavedState savedState;
|
||||||
|
private OpenFileSystemCache cache;
|
||||||
private int customScriptsStartIndex;
|
private int customScriptsStartIndex;
|
||||||
|
|
||||||
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
||||||
|
@ -56,6 +60,24 @@ public final class OpenFileSystemModel {
|
||||||
fileList = new BrowserFileListModel(this);
|
fileList = new BrowserFileListModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBusy() {
|
||||||
|
return !progress.getValue().done()
|
||||||
|
|| (fileSystem != null
|
||||||
|
&& fileSystem.getShell().isPresent()
|
||||||
|
&& fileSystem.getShell().get().getLock().isLocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startIfNeeded() throws Exception {
|
||||||
|
if (fileSystem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = fileSystem.getShell();
|
||||||
|
if (s.isPresent()) {
|
||||||
|
s.get().start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
|
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
|
@ -131,8 +153,13 @@ public final class OpenFileSystemModel {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Start shell in case we exited
|
// Start shell in case we exited
|
||||||
getFileSystem().getShell().orElseThrow().start();
|
startIfNeeded();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
return Optional.ofNullable(currentPath.get());
|
||||||
|
}
|
||||||
|
|
||||||
// Fix common issues with paths
|
// Fix common issues with paths
|
||||||
var adjustedPath = FileSystemHelper.adjustPath(this, path);
|
var adjustedPath = FileSystemHelper.adjustPath(this, path);
|
||||||
|
@ -158,26 +185,19 @@ public final class OpenFileSystemModel {
|
||||||
var directory = currentPath.get();
|
var directory = currentPath.get();
|
||||||
var name = adjustedPath + " - " + entry.get().getName();
|
var name = adjustedPath + " - " + entry.get().getName();
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
if (ShellDialects.getStartableDialects().stream()
|
||||||
TerminalHelper.open(
|
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) {
|
||||||
|
TerminalLauncher.open(
|
||||||
entry.getEntry(),
|
entry.getEntry(),
|
||||||
name,
|
name,
|
||||||
fileSystem
|
directory,
|
||||||
.getShell()
|
fileSystem.getShell().get().singularSubShell(ShellOpenFunction.of(adjustedPath)));
|
||||||
.get()
|
|
||||||
.subShell(processControl -> adjustedPath, (sc) -> adjustedPath)
|
|
||||||
.withInitSnippet(new SimpleScriptSnippet(
|
|
||||||
fileSystem
|
|
||||||
.getShell()
|
|
||||||
.get()
|
|
||||||
.getShellDialect()
|
|
||||||
.getCdCommand(currentPath.get()),
|
|
||||||
ScriptSnippet.ExecutionType.BOTH)));
|
|
||||||
} else {
|
} else {
|
||||||
TerminalHelper.open(
|
TerminalLauncher.open(
|
||||||
entry.getEntry(),
|
entry.getEntry(),
|
||||||
name,
|
name,
|
||||||
fileSystem.getShell().get().command(adjustedPath).withWorkingDirectory(directory));
|
directory,
|
||||||
|
fileSystem.getShell().get().command(adjustedPath));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Optional.ofNullable(currentPath.get());
|
return Optional.ofNullable(currentPath.get());
|
||||||
|
@ -227,6 +247,7 @@ public final class OpenFileSystemModel {
|
||||||
private boolean loadFilesSync(String dir) {
|
private boolean loadFilesSync(String dir) {
|
||||||
try {
|
try {
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
|
startIfNeeded();
|
||||||
var stream = getFileSystem().listFiles(dir);
|
var stream = getFileSystem().listFiles(dir);
|
||||||
fileList.setAll(stream);
|
fileList.setAll(stream);
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,7 +268,8 @@ public final class OpenFileSystemModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystemHelper.dropLocalFilesInto(entry, files);
|
startIfNeeded();
|
||||||
|
FileSystemHelper.dropLocalFilesInto(entry, files, progress::setValue, true);
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -266,14 +288,10 @@ public final class OpenFileSystemModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var same = files.get(0).getFileSystem().equals(target.getFileSystem());
|
startIfNeeded();
|
||||||
if (same && !explicitCopy) {
|
FileSystemHelper.dropFilesInto(target, files, explicitCopy, true, browserTransferProgress -> {
|
||||||
if (!BrowserAlerts.showMoveAlert(files, target)) {
|
progress.setValue(browserTransferProgress);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystemHelper.dropFilesInto(target, files, explicitCopy);
|
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -294,9 +312,11 @@ public final class OpenFileSystemModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startIfNeeded();
|
||||||
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
|
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
|
||||||
if (fileSystem.directoryExists(abs)) {
|
if (fileSystem.directoryExists(abs)) {
|
||||||
throw ErrorEvent.unreportable(new IllegalStateException(String.format("Directory %s already exists", abs)));
|
throw ErrorEvent.unreportable(
|
||||||
|
new IllegalStateException(String.format("Directory %s already exists", abs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fileSystem.mkdirs(abs);
|
fileSystem.mkdirs(abs);
|
||||||
|
@ -320,6 +340,7 @@ public final class OpenFileSystemModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startIfNeeded();
|
||||||
var abs = FileNames.join(getCurrentDirectory().getPath(), linkName);
|
var abs = FileNames.join(getCurrentDirectory().getPath(), linkName);
|
||||||
fileSystem.symbolicLink(abs, targetFile);
|
fileSystem.symbolicLink(abs, targetFile);
|
||||||
refreshSync();
|
refreshSync();
|
||||||
|
@ -370,14 +391,12 @@ public final class OpenFileSystemModel {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.execute(busy, () -> {
|
||||||
var fs = entry.getStore().createFileSystem();
|
var fs = entry.getStore().createFileSystem();
|
||||||
if (fs.getShell().isPresent()) {
|
if (fs.getShell().isPresent()) {
|
||||||
this.customScriptsStartIndex = fs.getShell().get().getInitCommands().size();
|
this.customScriptsStartIndex =
|
||||||
|
fs.getShell().get().getInitCommands().size();
|
||||||
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
||||||
}
|
}
|
||||||
fs.open();
|
fs.open();
|
||||||
this.fileSystem = fs;
|
this.fileSystem = fs;
|
||||||
this.local = fs.getShell()
|
|
||||||
.map(shellControl -> shellControl.hasLocalSystemAccess())
|
|
||||||
.orElse(false);
|
|
||||||
|
|
||||||
this.cache = new OpenFileSystemCache(this);
|
this.cache = new OpenFileSystemCache(this);
|
||||||
for (BrowserAction b : BrowserAction.ALL) {
|
for (BrowserAction b : BrowserAction.ALL) {
|
||||||
|
@ -408,21 +427,12 @@ public final class OpenFileSystemModel {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.execute(busy, () -> {
|
||||||
if (fileSystem.getShell().isPresent()) {
|
if (fileSystem.getShell().isPresent()) {
|
||||||
var connection = fileSystem.getShell().get();
|
var connection = fileSystem.getShell().get();
|
||||||
var snippet = directory != null ? new SimpleScriptSnippet(connection.getShellDialect().getCdCommand(directory),
|
var name = (directory != null ? directory + " - " : "")
|
||||||
ScriptSnippet.ExecutionType.BOTH) : null;
|
+ entry.get().getName();
|
||||||
if (snippet != null) {
|
TerminalLauncher.open(entry.getEntry(), name, directory, connection);
|
||||||
connection.getInitCommands().add(customScriptsStartIndex,snippet);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var name = (directory != null ? directory + " - " : "") + entry.get().getName();
|
|
||||||
TerminalHelper.open(entry.getEntry(), name, connection);
|
|
||||||
|
|
||||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||||
connection.start();
|
startIfNeeded();
|
||||||
} finally {
|
|
||||||
connection.getInitCommands().remove(snippet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,86 @@ import java.util.stream.Collectors;
|
||||||
@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class)
|
@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class)
|
||||||
public class OpenFileSystemSavedState {
|
public class OpenFileSystemSavedState {
|
||||||
|
|
||||||
|
private static final Timer TIMEOUT_TIMER = new Timer(true);
|
||||||
|
private static final int STORED = 10;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private OpenFileSystemModel model;
|
||||||
|
|
||||||
|
private String lastDirectory;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ObservableList<RecentEntry> recentDirectories;
|
||||||
|
|
||||||
|
public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
|
||||||
|
this.lastDirectory = lastDirectory;
|
||||||
|
this.recentDirectories = recentDirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenFileSystemSavedState() {
|
||||||
|
lastDirectory = null;
|
||||||
|
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
|
||||||
|
}
|
||||||
|
|
||||||
|
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
||||||
|
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
||||||
|
return new OpenFileSystemSavedState();
|
||||||
|
});
|
||||||
|
state.setModel(model);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
if (model == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cd(String dir) {
|
||||||
|
if (dir == null) {
|
||||||
|
lastDirectory = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDirectory = dir;
|
||||||
|
// After 10 seconds
|
||||||
|
TIMEOUT_TIMER.schedule(
|
||||||
|
new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Synchronize with platform thread
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (model.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Objects.equals(lastDirectory, dir)) {
|
||||||
|
updateRecent(dir);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRecent(String dir) {
|
||||||
|
var without = FileNames.removeTrailingSlash(dir);
|
||||||
|
var with = FileNames.toDirectory(dir);
|
||||||
|
recentDirectories.removeIf(recentEntry ->
|
||||||
|
Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with));
|
||||||
|
|
||||||
|
var o = new RecentEntry(with, Instant.now());
|
||||||
|
if (recentDirectories.size() < STORED) {
|
||||||
|
recentDirectories.addFirst(o);
|
||||||
|
} else {
|
||||||
|
recentDirectories.removeLast();
|
||||||
|
recentDirectories.addFirst(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Serializer extends StdSerializer<OpenFileSystemSavedState> {
|
public static class Serializer extends StdSerializer<OpenFileSystemSavedState> {
|
||||||
|
|
||||||
protected Serializer() {
|
protected Serializer() {
|
||||||
|
@ -79,14 +159,6 @@ public class OpenFileSystemSavedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
|
||||||
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
|
||||||
return new OpenFileSystemSavedState();
|
|
||||||
});
|
|
||||||
state.setModel(model);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -95,76 +167,4 @@ public class OpenFileSystemSavedState {
|
||||||
String directory;
|
String directory;
|
||||||
Instant time;
|
Instant time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Setter
|
|
||||||
private OpenFileSystemModel model;
|
|
||||||
|
|
||||||
private String lastDirectory;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private ObservableList<RecentEntry> recentDirectories;
|
|
||||||
|
|
||||||
public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
|
|
||||||
this.lastDirectory = lastDirectory;
|
|
||||||
this.recentDirectories = recentDirectories;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Timer TIMEOUT_TIMER = new Timer(true);
|
|
||||||
private static final int STORED = 10;
|
|
||||||
|
|
||||||
public OpenFileSystemSavedState() {
|
|
||||||
lastDirectory = null;
|
|
||||||
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
if (model == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cd(String dir) {
|
|
||||||
if (dir == null) {
|
|
||||||
lastDirectory = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDirectory = dir;
|
|
||||||
// After 10 seconds
|
|
||||||
TIMEOUT_TIMER.schedule(
|
|
||||||
new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Synchronize with platform thread
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (model.isClosed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Objects.equals(lastDirectory, dir)) {
|
|
||||||
updateRecent(dir);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRecent(String dir) {
|
|
||||||
var without = FileNames.removeTrailingSlash(dir);
|
|
||||||
var with = FileNames.toDirectory(dir);
|
|
||||||
recentDirectories.removeIf(recentEntry ->
|
|
||||||
Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with));
|
|
||||||
|
|
||||||
var o = new RecentEntry(with, Instant.now());
|
|
||||||
if (recentDirectories.size() < STORED) {
|
|
||||||
recentDirectories.add(0, o);
|
|
||||||
} else {
|
|
||||||
recentDirectories.remove(recentDirectories.size() - 1);
|
|
||||||
recentDirectories.add(0, o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,8 @@ public class StandaloneFileBrowser {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
|
public static void openSingleFile(
|
||||||
|
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
|
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
|
||||||
var comp = new BrowserComp(model)
|
var comp = new BrowserComp(model)
|
||||||
|
@ -47,7 +48,7 @@ public class StandaloneFileBrowser {
|
||||||
.apply(struc -> AppFont.normal(struc.get()));
|
.apply(struc -> AppFont.normal(struc.get()));
|
||||||
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
|
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
|
||||||
model.setOnFinish(fileStores -> {
|
model.setOnFinish(fileStores -> {
|
||||||
file.accept(fileStores.size() > 0 ? fileStores.get(0) : null);
|
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
window.show();
|
window.show();
|
||||||
|
@ -63,7 +64,7 @@ public class StandaloneFileBrowser {
|
||||||
.apply(struc -> AppFont.normal(struc.get()));
|
.apply(struc -> AppFont.normal(struc.get()));
|
||||||
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
|
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
|
||||||
model.setOnFinish(fileStores -> {
|
model.setOnFinish(fileStores -> {
|
||||||
file.setValue(fileStores.size() > 0 ? fileStores.get(0) : null);
|
file.setValue(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
window.show();
|
window.show();
|
||||||
|
|
|
@ -13,14 +13,6 @@ import java.util.ServiceLoader;
|
||||||
|
|
||||||
public interface BrowserAction {
|
public interface BrowserAction {
|
||||||
|
|
||||||
enum Category {
|
|
||||||
CUSTOM,
|
|
||||||
OPEN,
|
|
||||||
NATIVE,
|
|
||||||
COPY_PASTE,
|
|
||||||
MUTATION
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BrowserAction> ALL = new ArrayList<>();
|
List<BrowserAction> ALL = new ArrayList<>();
|
||||||
|
|
||||||
static List<LeafAction> getFlattened(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
static List<LeafAction> getFlattened(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
|
@ -39,7 +31,7 @@ public interface BrowserAction {
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
default void init(OpenFileSystemModel model) throws Exception {}
|
default void init(OpenFileSystemModel model) {}
|
||||||
|
|
||||||
default String getProFeatureId() {
|
default String getProFeatureId() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -75,6 +67,14 @@ public interface BrowserAction {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Category {
|
||||||
|
CUSTOM,
|
||||||
|
OPEN,
|
||||||
|
NATIVE,
|
||||||
|
COPY_PASTE,
|
||||||
|
MUTATION
|
||||||
|
}
|
||||||
|
|
||||||
class Loader implements ModuleLayerLoader {
|
class Loader implements ModuleLayerLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.List;
|
||||||
public class BrowserActionFormatter {
|
public class BrowserActionFormatter {
|
||||||
|
|
||||||
public static String filesArgument(List<BrowserEntry> entries) {
|
public static String filesArgument(List<BrowserEntry> entries) {
|
||||||
return entries.size() == 1 ? entries.get(0).getOptionallyQuotedFileName() : "(" + entries.size() + ")";
|
return entries.size() == 1 ? entries.getFirst().getOptionallyQuotedFileName() : "(" + entries.size() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String centerEllipsis(String input, int length) {
|
public static String centerEllipsis(String input, int length) {
|
||||||
|
|
|
@ -52,7 +52,8 @@ public interface LeafAction extends BrowserAction {
|
||||||
b.setDisable(!isActive(model, selected));
|
b.setDisable(!isActive(model, selected));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
if (getProFeatureId() != null
|
||||||
|
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||||
b.setDisable(true);
|
b.setDisable(true);
|
||||||
b.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
|
b.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
|
||||||
}
|
}
|
||||||
|
@ -83,7 +84,8 @@ public interface LeafAction extends BrowserAction {
|
||||||
mi.setMnemonicParsing(false);
|
mi.setMnemonicParsing(false);
|
||||||
mi.setDisable(!isActive(model, selected));
|
mi.setDisable(!isActive(model, selected));
|
||||||
|
|
||||||
if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
if (getProFeatureId() != null
|
||||||
|
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||||
mi.setDisable(true);
|
mi.setDisable(true);
|
||||||
mi.setText(mi.getText() + " (Pro)");
|
mi.setText(mi.getText() + " (Pro)");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import io.xpipe.app.browser.BrowserEntry;
|
||||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.ApplicationHelper;
|
import io.xpipe.app.util.ApplicationHelper;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalLauncher;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
|
@ -24,25 +24,30 @@ public abstract class MultiExecuteAction implements BranchAction {
|
||||||
model.withShell(
|
model.withShell(
|
||||||
pc -> {
|
pc -> {
|
||||||
for (BrowserEntry entry : entries) {
|
for (BrowserEntry entry : entries) {
|
||||||
TerminalHelper.open(model.getEntry().getEntry(), FilenameUtils.getBaseName(
|
TerminalLauncher.open(
|
||||||
entry.getRawFileEntry().getPath()), pc.command(createCommand(pc, model, entry))
|
model.getEntry().getEntry(),
|
||||||
.withWorkingDirectory(model.getCurrentDirectory()
|
FilenameUtils.getBaseName(
|
||||||
.getPath()));
|
entry.getRawFileEntry().getPath()),
|
||||||
|
model.getCurrentDirectory() != null
|
||||||
|
? model.getCurrentDirectory()
|
||||||
|
.getPath()
|
||||||
|
: null,
|
||||||
|
pc.command(createCommand(pc, model, entry)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
|
||||||
return AppPrefs.get().terminalType().getValue() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
var t = AppPrefs.get().terminalType().getValue();
|
var t = AppPrefs.get().terminalType().getValue();
|
||||||
return "in " + (t != null ? t.toTranslatedString() : "?");
|
return "in " + (t != null ? t.toTranslatedString() : "?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
|
return AppPrefs.get().terminalType().getValue() != null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new LeafAction() {
|
new LeafAction() {
|
||||||
|
|
||||||
|
@ -51,7 +56,8 @@ public abstract class MultiExecuteAction implements BranchAction {
|
||||||
model.withShell(
|
model.withShell(
|
||||||
pc -> {
|
pc -> {
|
||||||
for (BrowserEntry entry : entries) {
|
for (BrowserEntry entry : entries) {
|
||||||
var cmd = ApplicationHelper.createDetachCommand(pc, createCommand(pc, model, entry));
|
var cmd = ApplicationHelper.createDetachCommand(
|
||||||
|
pc, createCommand(pc, model, entry));
|
||||||
pc.command(cmd)
|
pc.command(cmd)
|
||||||
.withWorkingDirectory(model.getCurrentDirectory()
|
.withWorkingDirectory(model.getCurrentDirectory()
|
||||||
.getPath())
|
.getPath())
|
||||||
|
|
|
@ -9,7 +9,10 @@ import java.io.BufferedReader;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public interface DirectoryType {
|
public interface DirectoryType {
|
||||||
|
@ -71,6 +74,12 @@ public interface DirectoryType {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
boolean matches(FileSystem.FileEntry entry);
|
||||||
|
|
||||||
|
String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||||
|
|
||||||
class Simple implements DirectoryType {
|
class Simple implements DirectoryType {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -101,10 +110,4 @@ public interface DirectoryType {
|
||||||
return open ? this.open.getIcon() : this.closed.getIcon();
|
return open ? this.open.getIcon() : this.closed.getIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getId();
|
|
||||||
|
|
||||||
boolean matches(FileSystem.FileEntry entry);
|
|
||||||
|
|
||||||
String getIcon(FileSystem.FileEntry entry, boolean open);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,12 @@ public interface FileType {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
boolean matches(FileSystem.FileEntry entry);
|
||||||
|
|
||||||
|
String getIcon();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
class Simple implements FileType {
|
class Simple implements FileType {
|
||||||
|
|
||||||
|
@ -72,7 +78,9 @@ public interface FileType {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (entry.getExtension() != null && endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT))) || endings.contains(entry.getName());
|
return (entry.getExtension() != null
|
||||||
|
&& endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT)))
|
||||||
|
|| endings.contains(entry.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,10 +88,4 @@ public interface FileType {
|
||||||
return icon.getIcon();
|
return icon.getIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getId();
|
|
||||||
|
|
||||||
boolean matches(FileSystem.FileEntry entry);
|
|
||||||
|
|
||||||
String getIcon();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
@ -31,13 +32,14 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
||||||
model.getSelected())))));
|
model.getSelected())))));
|
||||||
|
|
||||||
var pane = new BorderPane();
|
var pane = new BorderPane();
|
||||||
var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries());
|
var sidebar = new SideMenuBarComp(model.getSelectedInternal(), model.getEntries());
|
||||||
pane.setCenter(multi.createRegion());
|
pane.setCenter(multi.createRegion());
|
||||||
pane.setRight(sidebar.createRegion());
|
pane.setRight(sidebar.createRegion());
|
||||||
pane.getStyleClass().add("background");
|
pane.getStyleClass().add("background");
|
||||||
model.getSelected().addListener((c, o, n) -> {
|
model.getSelected().addListener((c, o, n) -> {
|
||||||
if (o != null && o.equals(model.getEntries().get(2))) {
|
if (o != null && o.equals(model.getEntries().get(2))) {
|
||||||
AppPrefs.get().save();
|
AppPrefs.get().save();
|
||||||
|
DataStorage.get().saveAsync();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AppFont.normal(pane);
|
AppFont.normal(pane);
|
||||||
|
|
103
app/src/main/java/io/xpipe/app/comp/base/DialogComp.java
Normal file
103
app/src/main/java/io/xpipe/app/comp/base/DialogComp.java
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.AppWindowHelper;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
|
|
||||||
|
public static void showWindow(String titleKey, Function<Stage, DialogComp> f) {
|
||||||
|
var loading = new SimpleBooleanProperty();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var stage = AppWindowHelper.sideWindow(
|
||||||
|
AppI18n.get(titleKey),
|
||||||
|
window -> {
|
||||||
|
var c = f.apply(window);
|
||||||
|
loading.bind(c.busy());
|
||||||
|
return c;
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
loading);
|
||||||
|
stage.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Region createStepNavigation() {
|
||||||
|
HBox buttons = new HBox();
|
||||||
|
buttons.setFillHeight(true);
|
||||||
|
var customButton = bottom();
|
||||||
|
if (customButton != null) {
|
||||||
|
buttons.getChildren().add(customButton.createRegion());
|
||||||
|
}
|
||||||
|
buttons.getChildren().add(new Spacer());
|
||||||
|
buttons.getStyleClass().add("buttons");
|
||||||
|
buttons.setSpacing(5);
|
||||||
|
buttons.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
buttons.getChildren()
|
||||||
|
.addAll(customButtons().stream()
|
||||||
|
.map(buttonComp -> buttonComp.createRegion())
|
||||||
|
.toList());
|
||||||
|
var nextButton = new ButtonComp(AppI18n.observable("finishStep"), null, this::finish)
|
||||||
|
.apply(struc -> struc.get().setDefaultButton(true))
|
||||||
|
.styleClass(Styles.ACCENT)
|
||||||
|
.styleClass("next");
|
||||||
|
buttons.getChildren().add(nextButton.createRegion());
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Comp<?>> customButtons() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompStructure<Region> createBase() {
|
||||||
|
var sp = scrollPane(content()).createRegion();
|
||||||
|
VBox vbox = new VBox();
|
||||||
|
vbox.getChildren().addAll(sp, createStepNavigation());
|
||||||
|
vbox.getStyleClass().add("dialog-comp");
|
||||||
|
vbox.setFillWidth(true);
|
||||||
|
VBox.setVgrow(sp, Priority.ALWAYS);
|
||||||
|
return new SimpleCompStructure<>(vbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ObservableValue<Boolean> busy() {
|
||||||
|
return new SimpleBooleanProperty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void finish();
|
||||||
|
|
||||||
|
public abstract Comp<?> content();
|
||||||
|
|
||||||
|
protected Comp<?> scrollPane(Comp<?> content) {
|
||||||
|
var entry = content.styleClass("dialog-content");
|
||||||
|
return Comp.of(() -> {
|
||||||
|
var entryR = entry.createRegion();
|
||||||
|
var sp = new ScrollPane(entryR);
|
||||||
|
sp.setFitToWidth(true);
|
||||||
|
entryR.minHeightProperty().bind(sp.heightProperty());
|
||||||
|
return sp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comp<?> bottom() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ public class IntegratedTextAreaComp extends SimpleComp {
|
||||||
c.getChildren().addAll(textArea, pane);
|
c.getChildren().addAll(textArea, pane);
|
||||||
return c;
|
return c;
|
||||||
}),
|
}),
|
||||||
paths -> value.setValue(Files.readString(paths.get(0))));
|
paths -> value.setValue(Files.readString(paths.getFirst())));
|
||||||
return fileDrop.createRegion();
|
return fileDrop.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXTextField;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
@ -28,7 +27,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
|
||||||
@Override
|
@Override
|
||||||
public LazyTextFieldComp.Structure createBase() {
|
public LazyTextFieldComp.Structure createBase() {
|
||||||
var sp = new StackPane();
|
var sp = new StackPane();
|
||||||
var r = new JFXTextField();
|
var r = new TextField();
|
||||||
|
|
||||||
r.setOnKeyPressed(ke -> {
|
r.setOnKeyPressed(ke -> {
|
||||||
if (ke.getCode().equals(KeyCode.ESCAPE)) {
|
if (ke.getCode().equals(KeyCode.ESCAPE)) {
|
||||||
|
@ -69,8 +68,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
|
||||||
SimpleChangeListener.apply(currentValue, n -> {
|
SimpleChangeListener.apply(currentValue, n -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||||
if (Objects.equals(r.getText(), n)
|
if (Objects.equals(r.getText(), n) || (n == null && r.getText().isEmpty())) {
|
||||||
|| (n == null && r.getText().isEmpty())) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
return new SimpleCompStructure<>(scroll);
|
return new SimpleCompStructure<>(scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh(VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
private void refresh(
|
||||||
|
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
||||||
Runnable update = () -> {
|
Runnable update = () -> {
|
||||||
// Clear cache of unused values
|
// Clear cache of unused values
|
||||||
cache.keySet().removeIf(t -> !all.contains(t));
|
cache.keySet().removeIf(t -> !all.contains(t));
|
||||||
|
|
|
@ -65,7 +65,8 @@ public class ListSelectorComp<T> extends SimpleComp {
|
||||||
|
|
||||||
if (showAllSelector) {
|
if (showAllSelector) {
|
||||||
var allSelector = new CheckBox(null);
|
var allSelector = new CheckBox(null);
|
||||||
allSelector.setSelected(values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
allSelector.setSelected(
|
||||||
|
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||||
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
cbs.forEach(checkBox -> {
|
cbs.forEach(checkBox -> {
|
||||||
if (checkBox.isDisabled()) {
|
if (checkBox.isDisabled()) {
|
||||||
|
|
|
@ -16,10 +16,8 @@ import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||||
|
|
||||||
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
|
private static final double FPS = 30.0;
|
||||||
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
|
private static final double cycleDurationSeconds = 4.0;
|
||||||
}
|
|
||||||
|
|
||||||
private final Comp<?> comp;
|
private final Comp<?> comp;
|
||||||
private final ObservableValue<Boolean> showLoading;
|
private final ObservableValue<Boolean> showLoading;
|
||||||
private final ObservableValue<Number> progress;
|
private final ObservableValue<Number> progress;
|
||||||
|
@ -30,6 +28,10 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||||
this.progress = PlatformThread.sync(progress);
|
this.progress = PlatformThread.sync(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
|
||||||
|
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<StackPane> createBase() {
|
public CompStructure<StackPane> createBase() {
|
||||||
var compStruc = comp.createStructure();
|
var compStruc = comp.createStructure();
|
||||||
|
@ -39,6 +41,11 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||||
loading.progressProperty().bind(progress);
|
loading.progressProperty().bind(progress);
|
||||||
loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode()));
|
loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode()));
|
||||||
|
|
||||||
|
// var pane = new StackPane();
|
||||||
|
// Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode();
|
||||||
|
// pane.getChildren().add(node);
|
||||||
|
// pane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
var loadingOverlay = new StackPane(loading);
|
var loadingOverlay = new StackPane(loading);
|
||||||
loadingOverlay.getStyleClass().add("loading-comp");
|
loadingOverlay.getStyleClass().add("loading-comp");
|
||||||
loadingOverlay.setVisible(showLoading.getValue());
|
loadingOverlay.setVisible(showLoading.getValue());
|
||||||
|
|
|
@ -46,11 +46,14 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private WebView createWebView() {
|
private WebView createWebView() {
|
||||||
var wv = new WebView();
|
var wv = new WebView();
|
||||||
wv.getEngine().setUserDataDirectory(AppProperties.get().getDataDir().resolve("webview").toFile());
|
wv.getEngine()
|
||||||
|
.setUserDataDirectory(
|
||||||
|
AppProperties.get().getDataDir().resolve("webview").toFile());
|
||||||
wv.setPageFill(Color.TRANSPARENT);
|
wv.setPageFill(Color.TRANSPARENT);
|
||||||
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark() ? "web/github-markdown-dark.css" : "web/github-markdown-light.css";
|
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark()
|
||||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme)
|
? "web/github-markdown-dark.css"
|
||||||
.orElseThrow();
|
: "web/github-markdown-light.css";
|
||||||
|
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
|
||||||
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
||||||
|
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> {
|
SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> {
|
||||||
|
|
|
@ -7,15 +7,12 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ButtonBar;
|
import javafx.scene.control.ButtonBar;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyCodeCombination;
|
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
@ -23,23 +20,14 @@ import lombok.Value;
|
||||||
|
|
||||||
public class ModalOverlayComp extends SimpleComp {
|
public class ModalOverlayComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final Comp<?> background;
|
||||||
|
private final Property<OverlayContent> overlayContent;
|
||||||
|
|
||||||
public ModalOverlayComp(Comp<?> background, Property<OverlayContent> overlayContent) {
|
public ModalOverlayComp(Comp<?> background, Property<OverlayContent> overlayContent) {
|
||||||
this.background = background;
|
this.background = background;
|
||||||
this.overlayContent = overlayContent;
|
this.overlayContent = overlayContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
|
||||||
public static class OverlayContent {
|
|
||||||
|
|
||||||
String titleKey;
|
|
||||||
Comp<?> content;
|
|
||||||
String finishKey;
|
|
||||||
Runnable onFinish;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Comp<?> background;
|
|
||||||
private final Property<OverlayContent> overlayContent;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var bgRegion = background.createRegion();
|
var bgRegion = background.createRegion();
|
||||||
|
@ -62,7 +50,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||||
|
|
||||||
if (newValue.finishKey != null) {
|
if (newValue.finishKey != null) {
|
||||||
var finishButton = new Button(AppI18n.get(newValue.finishKey));
|
var finishButton = new Button(AppI18n.get(newValue.finishKey));
|
||||||
Shortcuts.addShortcut(finishButton, new KeyCodeCombination(KeyCode.ENTER));
|
finishButton.setDefaultButton(true);
|
||||||
Styles.toggleStyleClass(finishButton, Styles.FLAT);
|
Styles.toggleStyleClass(finishButton, Styles.FLAT);
|
||||||
finishButton.setOnAction(event -> {
|
finishButton.setOnAction(event -> {
|
||||||
newValue.onFinish.run();
|
newValue.onFinish.run();
|
||||||
|
@ -96,4 +84,13 @@ public class ModalOverlayComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class OverlayContent {
|
||||||
|
|
||||||
|
String titleKey;
|
||||||
|
Comp<?> content;
|
||||||
|
String finishKey;
|
||||||
|
Runnable onFinish;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ import java.util.Map;
|
||||||
|
|
||||||
public class OsLogoComp extends SimpleComp {
|
public class OsLogoComp extends SimpleComp {
|
||||||
|
|
||||||
|
private static final Map<String, String> ICONS = new HashMap<>();
|
||||||
|
private static final String LINUX_DEFAULT = "linux-24.png";
|
||||||
|
private static final String LINUX_DEFAULT_SVG = "linux.svg";
|
||||||
private final StoreEntryWrapper wrapper;
|
private final StoreEntryWrapper wrapper;
|
||||||
private final ObservableValue<SystemStateComp.State> state;
|
private final ObservableValue<SystemStateComp.State> state;
|
||||||
|
|
||||||
|
@ -47,7 +50,8 @@ public class OsLogoComp extends SimpleComp {
|
||||||
|
|
||||||
return getImage(ons.getOsName());
|
return getImage(ons.getOsName());
|
||||||
},
|
},
|
||||||
wrapper.getPersistentState(), state));
|
wrapper.getPersistentState(),
|
||||||
|
state));
|
||||||
var hide = BindingsHelper.map(img, s -> s != null);
|
var hide = BindingsHelper.map(img, s -> s != null);
|
||||||
return new StackComp(List.of(
|
return new StackComp(List.of(
|
||||||
new SystemStateComp(state).hide(hide),
|
new SystemStateComp(state).hide(hide),
|
||||||
|
@ -55,9 +59,6 @@ public class OsLogoComp extends SimpleComp {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, String> ICONS = new HashMap<>();
|
|
||||||
private static final String LINUX_DEFAULT = "linux-24.png";
|
|
||||||
|
|
||||||
private String getImage(String name) {
|
private String getImage(String name) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -66,8 +67,10 @@ public class OsLogoComp extends SimpleComp {
|
||||||
if (ICONS.isEmpty()) {
|
if (ICONS.isEmpty()) {
|
||||||
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
|
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
|
||||||
try (var list = Files.list(file)) {
|
try (var list = Files.list(file)) {
|
||||||
list.filter(path -> path.toString().endsWith(".svg") && !path.toString().endsWith(LINUX_DEFAULT))
|
list.filter(path -> path.toString().endsWith(".svg")
|
||||||
.map(path -> FileNames.getFileName(path.toString())).forEach(path -> {
|
&& !path.toString().endsWith(LINUX_DEFAULT_SVG))
|
||||||
|
.map(path -> FileNames.getFileName(path.toString()))
|
||||||
|
.forEach(path -> {
|
||||||
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
|
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
|
||||||
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
|
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
|
||||||
});
|
});
|
||||||
|
@ -75,6 +78,10 @@ public class OsLogoComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ICONS.entrySet().stream().filter(e->name.toLowerCase().contains(e.getKey())).findAny().map(e->e.getValue()).orElse("os/" + LINUX_DEFAULT);
|
return ICONS.entrySet().stream()
|
||||||
|
.filter(e -> name.toLowerCase().contains(e.getKey()))
|
||||||
|
.findAny()
|
||||||
|
.map(e -> e.getValue())
|
||||||
|
.orElse("os/" + LINUX_DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.core.AppLogs;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.augment.Augment;
|
||||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
@ -14,13 +15,13 @@ import io.xpipe.app.issue.UserReportComp;
|
||||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -39,6 +40,32 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
var vbox = new VBox();
|
var vbox = new VBox();
|
||||||
vbox.setFillWidth(true);
|
vbox.setFillWidth(true);
|
||||||
|
|
||||||
|
var selectedBorder = Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
var c = Platform.getPreferences().getAccentColor();
|
||||||
|
return new Border(new BorderStroke(
|
||||||
|
c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0)));
|
||||||
|
},
|
||||||
|
Platform.getPreferences().accentColorProperty());
|
||||||
|
|
||||||
|
var hoverBorder = Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
var c = Platform.getPreferences().getAccentColor().darker();
|
||||||
|
return new Border(new BorderStroke(
|
||||||
|
c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0)));
|
||||||
|
},
|
||||||
|
Platform.getPreferences().accentColorProperty());
|
||||||
|
|
||||||
|
var noneBorder = Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
return new Border(new BorderStroke(
|
||||||
|
Color.TRANSPARENT,
|
||||||
|
BorderStrokeStyle.SOLID,
|
||||||
|
CornerRadii.EMPTY,
|
||||||
|
new BorderWidths(0, 3, 0, 0)));
|
||||||
|
},
|
||||||
|
Platform.getPreferences().accentColorProperty());
|
||||||
|
|
||||||
var selected = PseudoClass.getPseudoClass("selected");
|
var selected = PseudoClass.getPseudoClass("selected");
|
||||||
entries.forEach(e -> {
|
entries.forEach(e -> {
|
||||||
var b = new IconButtonComp(e.icon(), () -> value.setValue(e)).apply(new FancyTooltipAugment<>(e.name()));
|
var b = new IconButtonComp(e.icon(), () -> value.setValue(e)).apply(new FancyTooltipAugment<>(e.name()));
|
||||||
|
@ -50,22 +77,59 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
struc.get().pseudoClassStateChanged(selected, n.equals(e));
|
struc.get().pseudoClassStateChanged(selected, n.equals(e));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
struc.get()
|
||||||
|
.borderProperty()
|
||||||
|
.bind(Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
if (value.getValue().equals(e)) {
|
||||||
|
return selectedBorder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (struc.get().isHover()) {
|
||||||
|
return hoverBorder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return noneBorder.get();
|
||||||
|
},
|
||||||
|
struc.get().hoverProperty(),
|
||||||
|
value,
|
||||||
|
hoverBorder,
|
||||||
|
selectedBorder,
|
||||||
|
noneBorder));
|
||||||
});
|
});
|
||||||
b.accessibleText(e.name());
|
b.accessibleText(e.name());
|
||||||
vbox.getChildren().add(b.createRegion());
|
vbox.getChildren().add(b.createRegion());
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
Augment<CompStructure<Button>> simpleBorders = struc -> {
|
||||||
var b = new IconButtonComp(
|
struc.get()
|
||||||
"mdal-bug_report",
|
.borderProperty()
|
||||||
|
.bind(Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
if (struc.get().isHover()) {
|
||||||
|
return hoverBorder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return noneBorder.get();
|
||||||
|
},
|
||||||
|
struc.get().hoverProperty(),
|
||||||
|
value,
|
||||||
|
hoverBorder,
|
||||||
|
selectedBorder,
|
||||||
|
noneBorder));
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
var b = new IconButtonComp("mdal-bug_report", () -> {
|
||||||
var event = ErrorEvent.fromMessage("User Report");
|
var event = ErrorEvent.fromMessage("User Report");
|
||||||
if (AppLogs.get().isWriteToFile()) {
|
if (AppLogs.get().isWriteToFile()) {
|
||||||
event.attachment(AppLogs.get().getSessionLogsDirectory());
|
event.attachment(AppLogs.get().getSessionLogsDirectory());
|
||||||
}
|
}
|
||||||
UserReportComp.show(event.build());
|
UserReportComp.show(event.build());
|
||||||
})
|
})
|
||||||
.apply(new FancyTooltipAugment<>("reportIssue")).accessibleTextKey("reportIssue");
|
.apply(new FancyTooltipAugment<>("reportIssue"))
|
||||||
|
.apply(simpleBorders)
|
||||||
|
.accessibleTextKey("reportIssue");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
});
|
});
|
||||||
|
@ -74,26 +138,20 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
|
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
|
||||||
.apply(new FancyTooltipAugment<>("visitGithubRepository")).accessibleTextKey("visitGithubRepository");
|
.apply(new FancyTooltipAugment<>("visitGithubRepository"))
|
||||||
|
.apply(simpleBorders)
|
||||||
|
.accessibleTextKey("visitGithubRepository");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
});
|
});
|
||||||
vbox.getChildren().add(b.createRegion());
|
vbox.getChildren().add(b.createRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
|
||||||
// var b = new IconButtonComp("mdi2c-comment-processing-outline", () -> Hyperlinks.open(Hyperlinks.ROADMAP))
|
|
||||||
// .apply(new FancyTooltipAugment<>("roadmap"));
|
|
||||||
// b.apply(struc -> {
|
|
||||||
// AppFont.setSize(struc.get(), 2);
|
|
||||||
// });
|
|
||||||
// vbox.getChildren().add(b.createRegion());
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
|
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
|
||||||
.apply(new FancyTooltipAugment<>("discord")).accessibleTextKey("discord");
|
.apply(new FancyTooltipAugment<>("discord"))
|
||||||
|
.apply(simpleBorders)
|
||||||
|
.accessibleTextKey("discord");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
});
|
});
|
||||||
|
@ -102,7 +160,8 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
||||||
.apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip");
|
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"))
|
||||||
|
.accessibleTextKey("updateAvailableTooltip");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
AppFont.setSize(struc.get(), 2);
|
||||||
});
|
});
|
||||||
|
@ -123,7 +182,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
filler.setMaxHeight(3000);
|
filler.setMaxHeight(3000);
|
||||||
vbox.getChildren().add(filler);
|
vbox.getChildren().add(filler);
|
||||||
VBox.setVgrow(filler, Priority.ALWAYS);
|
VBox.setVgrow(filler, Priority.ALWAYS);
|
||||||
filler.prefWidthProperty().bind(((Region) vbox.getChildren().get(0)).widthProperty());
|
filler.prefWidthProperty().bind(((Region) vbox.getChildren().getFirst()).widthProperty());
|
||||||
|
|
||||||
vbox.getStyleClass().add("sidebar-comp");
|
vbox.getStyleClass().add("sidebar-comp");
|
||||||
return new SimpleCompStructure<>(vbox);
|
return new SimpleCompStructure<>(vbox);
|
||||||
|
|
|
@ -15,6 +15,7 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||||
private final Comp<?> center;
|
private final Comp<?> center;
|
||||||
private Double initialWidth;
|
private Double initialWidth;
|
||||||
private Consumer<Double> onDividerChange;
|
private Consumer<Double> onDividerChange;
|
||||||
|
|
||||||
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||||
this.left = left;
|
this.left = left;
|
||||||
this.center = center;
|
this.center = center;
|
||||||
|
@ -36,13 +37,13 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setInitial.get() && initialWidth != null) {
|
if (!setInitial.get() && initialWidth != null) {
|
||||||
r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue());
|
r.getDividers().getFirst().setPosition(initialWidth / newValue.doubleValue());
|
||||||
setInitial.set(true);
|
setInitial.set(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SplitPane.setResizableWithParent(sidebar, false);
|
SplitPane.setResizableWithParent(sidebar, false);
|
||||||
r.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
|
r.getDividers().getFirst().positionProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (r.getWidth() <= 0) {
|
if (r.getWidth() <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
r.getStyleClass().add("side-split-pane-comp");
|
r.getStyleClass().add("side-split-pane-comp");
|
||||||
return new Structure(sidebar, c, r, r.getDividers().get(0));
|
return new Structure(sidebar, c, r, r.getDividers().getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SideSplitPaneComp withInitialWidth(double val) {
|
public SideSplitPaneComp withInitialWidth(double val) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class StoreToggleComp extends SimpleComp {
|
||||||
},
|
},
|
||||||
section.getWrapper().getValidity(),
|
section.getWrapper().getValidity(),
|
||||||
section.getShowDetails()));
|
section.getShowDetails()));
|
||||||
var t = new NamedToggleComp(value, AppI18n.observable(nameKey))
|
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
|
||||||
.visible(visible)
|
.visible(visible)
|
||||||
.disable(disable);
|
.disable(disable);
|
||||||
value.addListener((observable, oldValue, newValue) -> {
|
value.addListener((observable, oldValue, newValue) -> {
|
||||||
|
|
|
@ -18,29 +18,12 @@ import org.kordamp.ikonli.javafx.StackedFontIcon;
|
||||||
@Getter
|
@Getter
|
||||||
public class SystemStateComp extends SimpleComp {
|
public class SystemStateComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final ObservableValue<State> state;
|
||||||
|
|
||||||
public SystemStateComp(ObservableValue<State> state) {
|
public SystemStateComp(ObservableValue<State> state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum State {
|
|
||||||
FAILURE,
|
|
||||||
SUCCESS,
|
|
||||||
OTHER;
|
|
||||||
|
|
||||||
public static ObservableValue<State> shellState(StoreEntryWrapper w) {
|
|
||||||
return BindingsHelper.map(w.getPersistentState(),o -> {
|
|
||||||
if (o instanceof ShellStoreState shellStoreState) {
|
|
||||||
return shellStoreState.getRunning() != null ? shellStoreState.getRunning() ? SUCCESS : FAILURE : OTHER;
|
|
||||||
}
|
|
||||||
|
|
||||||
return OTHER;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ObservableValue<State> state;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
||||||
|
@ -58,15 +41,19 @@ public class SystemStateComp extends SimpleComp {
|
||||||
border.getStyleClass().add("outer-icon");
|
border.getStyleClass().add("outer-icon");
|
||||||
border.setOpacity(0.5);
|
border.setOpacity(0.5);
|
||||||
|
|
||||||
var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
|
var success = Styles.toDataURI(
|
||||||
var failure = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
|
".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
|
||||||
var other = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
|
var failure =
|
||||||
|
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
|
||||||
|
var other =
|
||||||
|
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
|
||||||
|
|
||||||
var pane = new StackedFontIcon();
|
var pane = new StackedFontIcon();
|
||||||
pane.getChildren().addAll(fi, border);
|
pane.getChildren().addAll(fi, border);
|
||||||
pane.setAlignment(Pos.CENTER);
|
pane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
var dataClass1 = """
|
var dataClass1 =
|
||||||
|
"""
|
||||||
.stacked-ikonli-font-icon > .outer-icon {
|
.stacked-ikonli-font-icon > .outer-icon {
|
||||||
-fx-icon-size: 22px;
|
-fx-icon-size: 22px;
|
||||||
}
|
}
|
||||||
|
@ -83,4 +70,26 @@ public class SystemStateComp extends SimpleComp {
|
||||||
|
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
FAILURE,
|
||||||
|
SUCCESS,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
public static ObservableValue<State> shellState(StoreEntryWrapper w) {
|
||||||
|
return BindingsHelper.map(w.getPersistentState(), o -> {
|
||||||
|
if (o instanceof ShellStoreState s) {
|
||||||
|
if (s.getShellDialect() != null && !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getRunning() != null
|
||||||
|
? s.getRunning() ? SUCCESS : FAILURE
|
||||||
|
: OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OTHER;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,18 @@ import java.util.function.Consumer;
|
||||||
@Getter
|
@Getter
|
||||||
public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
|
|
||||||
|
private final ObservableValue<String> name;
|
||||||
|
private final ObservableValue<String> description;
|
||||||
|
private final ObservableValue<String> icon;
|
||||||
|
private final Consumer<ActionEvent> action;
|
||||||
|
|
||||||
|
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
|
||||||
|
this.name = AppI18n.observable(nameKey);
|
||||||
|
this.description = AppI18n.observable(descriptionKey);
|
||||||
|
this.icon = new SimpleStringProperty(icon);
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Structure createBase() {
|
public Structure createBase() {
|
||||||
var bt = new Button();
|
var bt = new Button();
|
||||||
|
@ -68,7 +80,13 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
fi.setIconSize((int) (size * 0.55));
|
fi.setIconSize((int) (size * 0.55));
|
||||||
});
|
});
|
||||||
bt.setGraphic(hbox);
|
bt.setGraphic(hbox);
|
||||||
return Structure.builder().graphic(fi).button(bt).content(hbox).name(header).description(desc).build();
|
return Structure.builder()
|
||||||
|
.graphic(fi)
|
||||||
|
.button(bt)
|
||||||
|
.content(hbox)
|
||||||
|
.name(header)
|
||||||
|
.description(desc)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
|
@ -85,16 +103,4 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ObservableValue<String> name;
|
|
||||||
private final ObservableValue<String> description;
|
|
||||||
private final ObservableValue<String> icon;
|
|
||||||
private final Consumer<ActionEvent> action;
|
|
||||||
|
|
||||||
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
|
|
||||||
this.name = AppI18n.observable(nameKey);
|
|
||||||
this.description = AppI18n.observable(descriptionKey);
|
|
||||||
this.icon = new SimpleStringProperty(icon);
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.ToggleSwitch;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
public class ToggleSwitchComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final Property<Boolean> selected;
|
||||||
|
private final ObservableValue<String> name;
|
||||||
|
|
||||||
|
public ToggleSwitchComp(Property<Boolean> selected, ObservableValue<String> name) {
|
||||||
|
this.selected = selected;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var s = new ToggleSwitch();
|
||||||
|
s.setSelected(selected.getValue());
|
||||||
|
s.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
selected.setValue(newValue);
|
||||||
|
});
|
||||||
|
selected.addListener((observable, oldValue, newValue) -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
s.setSelected(newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (name != null) {
|
||||||
|
s.textProperty().bind(PlatformThread.sync(name));
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -32,16 +31,26 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||||
: Comp.empty();
|
: Comp.empty();
|
||||||
information.setGraphic(state.createRegion());
|
information.setGraphic(state.createRegion());
|
||||||
|
|
||||||
var summary = wrapper.getSummary();
|
|
||||||
var info = wrapper.getEntry().getProvider().informationString(wrapper);
|
var info = wrapper.getEntry().getProvider().informationString(wrapper);
|
||||||
SimpleChangeListener.apply(grid.hoverProperty(), val -> {
|
var summary = wrapper.getSummary();
|
||||||
if (val && summary.getValue() != null && wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
if (wrapper.getEntry().getProvider() != null) {
|
||||||
information.textProperty().bind(PlatformThread.sync(summary));
|
information
|
||||||
|
.textProperty()
|
||||||
|
.bind(PlatformThread.sync(Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var val = summary.getValue();
|
||||||
|
if (val != null
|
||||||
|
&& grid.isHover()
|
||||||
|
&& wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
||||||
|
return val;
|
||||||
} else {
|
} else {
|
||||||
information.textProperty().bind(PlatformThread.sync(info));
|
return info.getValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid.hoverProperty(),
|
||||||
|
info,
|
||||||
|
summary)));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return information;
|
return information;
|
||||||
}
|
}
|
||||||
|
@ -51,9 +60,12 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||||
grid.setHgap(8);
|
grid.setHgap(8);
|
||||||
|
|
||||||
var name = createName().createRegion();
|
var name = createName().createRegion();
|
||||||
name.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
name.maxWidthProperty()
|
||||||
|
.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> {
|
||||||
return grid.getWidth() / 2.5;
|
return grid.getWidth() / 2.5;
|
||||||
}, grid.widthProperty()));
|
},
|
||||||
|
grid.widthProperty()));
|
||||||
|
|
||||||
if (showIcon) {
|
if (showIcon) {
|
||||||
var storeIcon = createIcon(30, 24);
|
var storeIcon = createIcon(30, 24);
|
||||||
|
|
|
@ -12,7 +12,6 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||||
super(entry, content);
|
super(entry, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Region createContent() {
|
protected Region createContent() {
|
||||||
var name = createName().createRegion();
|
var name = createName().createRegion();
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -60,12 +60,12 @@ public class StoreCategoryWrapper {
|
||||||
return StoreViewState.get().getCategories().stream()
|
return StoreViewState.get().getCategories().stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
||||||
.findAny().orElse(null);
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(DataStoreEntry entry) {
|
public boolean contains(StoreEntryWrapper entry) {
|
||||||
return entry.getCategoryUuid().equals(category.getUuid())
|
return entry.getEntry().getCategoryUuid().equals(category.getUuid()) || containedEntries.contains(entry);
|
||||||
|| children.stream().anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void select() {
|
public void select() {
|
||||||
|
@ -87,6 +87,10 @@ public class StoreCategoryWrapper {
|
||||||
update();
|
update();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
AppPrefs.get().showChildCategoriesInParentCategory().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||||
category.setSortMode(newValue);
|
category.setSortMode(newValue);
|
||||||
});
|
});
|
||||||
|
@ -117,15 +121,21 @@ public class StoreCategoryWrapper {
|
||||||
share.setValue(category.isShare());
|
share.setValue(category.isShare());
|
||||||
|
|
||||||
containedEntries.setAll(StoreViewState.get().getAllEntries().stream()
|
containedEntries.setAll(StoreViewState.get().getAllEntries().stream()
|
||||||
.filter(entry -> contains(entry.getEntry()))
|
.filter(entry -> {
|
||||||
|
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
||||||
|
|| (AppPrefs.get()
|
||||||
|
.showChildCategoriesInParentCategory()
|
||||||
|
.get()
|
||||||
|
&& children.stream()
|
||||||
|
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
||||||
|
})
|
||||||
.toList());
|
.toList());
|
||||||
children.setAll(StoreViewState.get().getCategories().stream()
|
children.setAll(StoreViewState.get().getCategories().stream()
|
||||||
.filter(storeCategoryWrapper -> getCategory()
|
.filter(storeCategoryWrapper -> getCategory()
|
||||||
.getUuid()
|
.getUuid()
|
||||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||||
.toList());
|
.toList());
|
||||||
Optional.ofNullable(getParent())
|
Optional.ofNullable(getParent()).ifPresent(storeCategoryWrapper -> {
|
||||||
.ifPresent(storeCategoryWrapper -> {
|
|
||||||
storeCategoryWrapper.update();
|
storeCategoryWrapper.update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
|
import io.xpipe.app.comp.base.DialogComp;
|
||||||
import io.xpipe.app.comp.base.ErrorOverlayComp;
|
import io.xpipe.app.comp.base.ErrorOverlayComp;
|
||||||
import io.xpipe.app.comp.base.MultiStepComp;
|
|
||||||
import io.xpipe.app.comp.base.PopupMenuButtonComp;
|
import io.xpipe.app.comp.base.PopupMenuButtonComp;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.AppWindowHelper;
|
||||||
import io.xpipe.app.ext.DataStoreProvider;
|
import io.xpipe.app.ext.DataStoreProvider;
|
||||||
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||||
|
@ -25,12 +27,12 @@ import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Separator;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@ -41,9 +43,10 @@ import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
MultiStepComp parent;
|
Stage window;
|
||||||
|
Consumer<DataStoreEntry> consumer;
|
||||||
Property<DataStoreProvider> provider;
|
Property<DataStoreProvider> provider;
|
||||||
Property<DataStore> store;
|
Property<DataStore> store;
|
||||||
Predicate<DataStoreProvider> filter;
|
Predicate<DataStoreProvider> filter;
|
||||||
|
@ -53,19 +56,22 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
BooleanProperty finished = new SimpleBooleanProperty();
|
BooleanProperty finished = new SimpleBooleanProperty();
|
||||||
ObservableValue<DataStoreEntry> entry;
|
ObservableValue<DataStoreEntry> entry;
|
||||||
BooleanProperty changedSinceError = new SimpleBooleanProperty();
|
BooleanProperty changedSinceError = new SimpleBooleanProperty();
|
||||||
|
BooleanProperty skippable = new SimpleBooleanProperty();
|
||||||
StringProperty name;
|
StringProperty name;
|
||||||
DataStoreEntry existingEntry;
|
DataStoreEntry existingEntry;
|
||||||
boolean staticDisplay;
|
boolean staticDisplay;
|
||||||
|
|
||||||
public StoreCreationComp(
|
public StoreCreationComp(
|
||||||
MultiStepComp parent,
|
Stage window,
|
||||||
|
Consumer<DataStoreEntry> consumer,
|
||||||
Property<DataStoreProvider> provider,
|
Property<DataStoreProvider> provider,
|
||||||
Property<DataStore> store,
|
Property<DataStore> store,
|
||||||
Predicate<DataStoreProvider> filter,
|
Predicate<DataStoreProvider> filter,
|
||||||
String initialName,
|
String initialName,
|
||||||
DataStoreEntry existingEntry,
|
DataStoreEntry existingEntry,
|
||||||
boolean staticDisplay) {
|
boolean staticDisplay) {
|
||||||
this.parent = parent;
|
this.window = window;
|
||||||
|
this.consumer = consumer;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
@ -97,7 +103,8 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
newValue.validate();
|
newValue.validate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.entry = Bindings.createObjectBinding(() -> {
|
this.entry = Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
if (name.getValue() == null || store.getValue() == null) {
|
if (name.getValue() == null || store.getValue() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -111,22 +118,27 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
|
|
||||||
var targetCategory = p != null
|
var targetCategory = p != null
|
||||||
? p.getCategoryUuid()
|
? p.getCategoryUuid()
|
||||||
: DataStorage.get()
|
: DataStorage.get().getSelectedCategory().getUuid();
|
||||||
.getSelectedCategory()
|
var rootCategory = DataStorage.get()
|
||||||
.getUuid();
|
.getRootCategory(DataStorage.get()
|
||||||
var rootCategory = DataStorage.get().getRootCategory(DataStorage.get().getStoreCategoryIfPresent(targetCategory).orElseThrow());
|
.getStoreCategoryIfPresent(targetCategory)
|
||||||
|
.orElseThrow());
|
||||||
// Don't put connections in the scripts category ever
|
// Don't put connections in the scripts category ever
|
||||||
if ((provider.getValue().getCreationCategory() == null || !provider.getValue().getCreationCategory().equals(DataStoreProvider.CreationCategory.SCRIPT)) &&
|
if ((provider.getValue().getCreationCategory() == null
|
||||||
rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
|
|| !provider.getValue()
|
||||||
targetCategory = DataStorage.get().getDefaultCategory().getUuid();
|
.getCreationCategory()
|
||||||
|
.equals(DataStoreProvider.CreationCategory.SCRIPT))
|
||||||
|
&& rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
|
||||||
|
targetCategory = DataStorage.get()
|
||||||
|
.getDefaultConnectionsCategory()
|
||||||
|
.getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataStoreEntry.createNew(
|
return DataStoreEntry.createNew(
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(), targetCategory, name.getValue(), store.getValue());
|
||||||
targetCategory,
|
},
|
||||||
name.getValue(),
|
name,
|
||||||
store.getValue());
|
store);
|
||||||
}, name, store);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showEdit(DataStoreEntry e) {
|
public static void showEdit(DataStoreEntry e) {
|
||||||
|
@ -148,16 +160,23 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
e);
|
e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showCreation(DataStoreProvider selected, Predicate<DataStoreProvider> filter) {
|
public static void showCreation(DataStoreProvider selected, DataStoreProvider.CreationCategory category) {
|
||||||
|
showCreation(selected != null ? selected.defaultStore() : null, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showCreation(DataStore base, DataStoreProvider.CreationCategory category) {
|
||||||
show(
|
show(
|
||||||
null,
|
null,
|
||||||
selected,
|
base != null ? DataStoreProviders.byStore(base) : null,
|
||||||
selected != null ? selected.defaultStore() : null,
|
base,
|
||||||
filter,
|
dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()),
|
||||||
e -> {
|
e -> {
|
||||||
try {
|
try {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||||
if (e.getProvider().shouldHaveChildren()) {
|
if (e.getProvider().shouldHaveChildren()
|
||||||
|
&& AppPrefs.get()
|
||||||
|
.openConnectionSearchWindowOnConnectionCreation()
|
||||||
|
.get()) {
|
||||||
ScanAlert.showAsync(e);
|
ScanAlert.showAsync(e);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -178,38 +197,117 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
DataStoreEntry existingEntry) {
|
DataStoreEntry existingEntry) {
|
||||||
var prop = new SimpleObjectProperty<>(provider);
|
var prop = new SimpleObjectProperty<>(provider);
|
||||||
var store = new SimpleObjectProperty<>(s);
|
var store = new SimpleObjectProperty<>(s);
|
||||||
var loading = new SimpleBooleanProperty();
|
DialogComp.showWindow(
|
||||||
var name = "addConnection";
|
"addConnection",
|
||||||
Platform.runLater(() -> {
|
stage -> new StoreCreationComp(
|
||||||
var stage = AppWindowHelper.sideWindow(
|
stage, con, prop, store, filter, initialName, existingEntry, staticDisplay));
|
||||||
AppI18n.get(name),
|
}
|
||||||
window -> {
|
|
||||||
return new MultiStepComp() {
|
|
||||||
|
|
||||||
private final StoreCreationComp creator = new StoreCreationComp(
|
private static boolean showInvalidConfirmAlert() {
|
||||||
this, prop, store, filter, initialName, existingEntry, staticDisplay);
|
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.setTitle(AppI18n.get("confirmInvalidStoreTitle"));
|
||||||
|
alert.setHeaderText(AppI18n.get("confirmInvalidStoreHeader"));
|
||||||
|
alert.getDialogPane()
|
||||||
|
.setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent")));
|
||||||
|
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||||
|
alert.getButtonTypes().clear();
|
||||||
|
alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE));
|
||||||
|
alert.getButtonTypes().add(new ButtonType("Skip", ButtonBar.ButtonData.OK_DONE));
|
||||||
|
})
|
||||||
|
.map(b -> b.getButtonData().isDefaultButton())
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Entry> setup() {
|
protected List<Comp<?>> customButtons() {
|
||||||
loading.bind(creator.busy);
|
return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> {
|
||||||
return List.of(new Entry(AppI18n.observable("a"), creator));
|
if (showInvalidConfirmAlert()) {
|
||||||
|
commit();
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.visible(skippable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ObservableValue<Boolean> busy() {
|
||||||
|
return busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finish() {
|
protected void finish() {
|
||||||
window.close();
|
if (finished.get()) {
|
||||||
if (creator.entry.getValue() != null) {
|
return;
|
||||||
con.accept(creator.entry.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (store.getValue() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't change anything
|
||||||
|
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
||||||
|
commit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validator.getValue().validate()) {
|
||||||
|
var msg = validator
|
||||||
|
.getValue()
|
||||||
|
.getValidationResult()
|
||||||
|
.getMessages()
|
||||||
|
.getFirst()
|
||||||
|
.getText();
|
||||||
|
TrackEvent.info(msg);
|
||||||
|
var newMessage = msg;
|
||||||
|
// Temporary fix for equal error message not showing up again
|
||||||
|
if (Objects.equals(newMessage, messageProp.getValue())) {
|
||||||
|
newMessage = newMessage + " ";
|
||||||
|
}
|
||||||
|
messageProp.setValue(newMessage);
|
||||||
|
changedSinceError.setValue(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadHelper.runAsync(() -> {
|
||||||
|
try (var b = new BooleanScope(busy).start()) {
|
||||||
|
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
||||||
|
entry.getValue().validateOrThrow();
|
||||||
|
commit();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
if (ex instanceof ValidationException) {
|
||||||
|
ErrorEvent.unreportable(ex);
|
||||||
|
skippable.set(false);
|
||||||
|
} else {
|
||||||
|
skippable.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newMessage = ExceptionConverter.convertMessage(ex);
|
||||||
|
// Temporary fix for equal error message not showing up again
|
||||||
|
if (Objects.equals(newMessage, messageProp.getValue())) {
|
||||||
|
newMessage = newMessage + " ";
|
||||||
|
}
|
||||||
|
messageProp.setValue(newMessage);
|
||||||
|
changedSinceError.setValue(false);
|
||||||
|
|
||||||
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
|
} finally {
|
||||||
|
DataStorage.get().removeStoreEntryInProgress(entry.getValue());
|
||||||
}
|
}
|
||||||
};
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
loading);
|
|
||||||
stage.show();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> content() {
|
||||||
|
return Comp.of(this::createLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> scrollPane(Comp<?> content) {
|
||||||
|
var back = super.scrollPane(content);
|
||||||
|
return new ErrorOverlayComp(back, messageProp);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Comp<?> bottom() {
|
public Comp<?> bottom() {
|
||||||
var disable = Bindings.createBooleanBinding(
|
var disable = Bindings.createBooleanBinding(
|
||||||
|
@ -219,7 +317,9 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
|| !store.getValue().isComplete()
|
|| !store.getValue().isComplete()
|
||||||
// When switching providers, both observables change one after another.
|
// When switching providers, both observables change one after another.
|
||||||
// So temporarily there might be a store class mismatch
|
// So temporarily there might be a store class mismatch
|
||||||
|| provider.getValue().getStoreClasses().stream().noneMatch(aClass -> aClass.isAssignableFrom(store.getValue().getClass()))
|
|| provider.getValue().getStoreClasses().stream()
|
||||||
|
.noneMatch(aClass -> aClass.isAssignableFrom(
|
||||||
|
store.getValue().getClass()))
|
||||||
|| provider.getValue().createInsightsMarkdown(store.getValue()) == null;
|
|| provider.getValue().createInsightsMarkdown(store.getValue()) == null;
|
||||||
},
|
},
|
||||||
provider,
|
provider,
|
||||||
|
@ -238,17 +338,6 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
.styleClass("button-comp");
|
.styleClass("button-comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean showInvalidConfirmAlert() {
|
|
||||||
return AppWindowHelper.showBlockingAlert(alert -> {
|
|
||||||
alert.setTitle(AppI18n.get("confirmInvalidStoreTitle"));
|
|
||||||
alert.setHeaderText(AppI18n.get("confirmInvalidStoreHeader"));
|
|
||||||
alert.setContentText(AppI18n.get("confirmInvalidStoreContent"));
|
|
||||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
|
||||||
})
|
|
||||||
.map(b -> b.getButtonData().isDefaultButton())
|
|
||||||
.orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
|
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
|
||||||
return new OptionsBuilder()
|
return new OptionsBuilder()
|
||||||
.addComp(comp, store)
|
.addComp(comp, store)
|
||||||
|
@ -259,18 +348,26 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void commit() {
|
||||||
public CompStructure<? extends Region> createBase() {
|
if (finished.get()) {
|
||||||
var back = Comp.of(this::createLayout);
|
return;
|
||||||
var message = new ErrorOverlayComp(back, messageProp);
|
}
|
||||||
return message.createStructure();
|
finished.setValue(true);
|
||||||
|
|
||||||
|
if (entry.getValue() != null) {
|
||||||
|
consumer.accept(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region createLayout() {
|
private Region createLayout() {
|
||||||
var layout = new BorderPane();
|
var layout = new BorderPane();
|
||||||
layout.getStyleClass().add("store-creator");
|
layout.getStyleClass().add("store-creator");
|
||||||
layout.setPadding(new Insets(20));
|
layout.setPadding(new Insets(20));
|
||||||
var providerChoice = new DataStoreProviderChoiceComp(filter, provider, staticDisplay);
|
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
|
||||||
if (staticDisplay) {
|
if (staticDisplay) {
|
||||||
providerChoice.apply(struc -> struc.get().setDisable(true));
|
providerChoice.apply(struc -> struc.get().setDisable(true));
|
||||||
}
|
}
|
||||||
|
@ -297,93 +394,9 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
||||||
|
|
||||||
var sep = new Separator();
|
var sep = new Separator();
|
||||||
sep.getStyleClass().add("spacer");
|
sep.getStyleClass().add("spacer");
|
||||||
var top = new VBox(providerChoice.createRegion(), sep);
|
var top = new VBox(providerChoice.createRegion(), new Spacer(7, Orientation.VERTICAL), sep);
|
||||||
top.getStyleClass().add("top");
|
top.getStyleClass().add("top");
|
||||||
layout.setTop(top);
|
layout.setTop(top);
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canContinue() {
|
|
||||||
if (provider.getValue() != null) {
|
|
||||||
var install = provider.getValue().getRequiredAdditionalInstallation();
|
|
||||||
if (install != null && !AppExtensionManager.getInstance().isInstalled(install)) {
|
|
||||||
ThreadHelper.runAsync(() -> {
|
|
||||||
try (var ignored = new BooleanScope(busy).start()) {
|
|
||||||
AppExtensionManager.getInstance().installIfNeeded(install);
|
|
||||||
/*
|
|
||||||
TODO: Use reload
|
|
||||||
*/
|
|
||||||
finished.setValue(true);
|
|
||||||
OperationMode.shutdown(false, false);
|
|
||||||
PlatformThread.runLaterIfNeeded(parent::next);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finished.get()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.getValue() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't change anything
|
|
||||||
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messageProp.getValue() != null && !changedSinceError.get()) {
|
|
||||||
if (AppPrefs.get().developerMode().getValue() && showInvalidConfirmAlert()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator.getValue().validate()) {
|
|
||||||
var msg = validator
|
|
||||||
.getValue()
|
|
||||||
.getValidationResult()
|
|
||||||
.getMessages()
|
|
||||||
.getFirst()
|
|
||||||
.getText();
|
|
||||||
TrackEvent.info(msg);
|
|
||||||
var newMessage = msg;
|
|
||||||
// Temporary fix for equal error message not showing up again
|
|
||||||
if (Objects.equals(newMessage, messageProp.getValue())) {
|
|
||||||
newMessage = newMessage + " ";
|
|
||||||
}
|
|
||||||
messageProp.setValue(newMessage);
|
|
||||||
changedSinceError.setValue(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadHelper.runAsync(() -> {
|
|
||||||
try (var b = new BooleanScope(busy).start()) {
|
|
||||||
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
|
||||||
entry.getValue().validateOrThrow();
|
|
||||||
finished.setValue(true);
|
|
||||||
PlatformThread.runLaterIfNeeded(parent::next);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
var newMessage = ExceptionConverter.convertMessage(ex);
|
|
||||||
// Temporary fix for equal error message not showing up again
|
|
||||||
if (Objects.equals(newMessage, messageProp.getValue())) {
|
|
||||||
newMessage = newMessage + " ";
|
|
||||||
}
|
|
||||||
messageProp.setValue(newMessage);
|
|
||||||
changedSinceError.setValue(false);
|
|
||||||
if (ex instanceof ValidationException) {
|
|
||||||
ErrorEvent.unreportable(ex);
|
|
||||||
}
|
|
||||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
|
||||||
} finally {
|
|
||||||
DataStorage.get().removeStoreEntryInProgress(entry.getValue());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,37 +24,42 @@ public class StoreCreationMenu {
|
||||||
menu.getItems().add(automatically);
|
menu.getItems().add(automatically);
|
||||||
menu.getItems().add(new SeparatorMenuItem());
|
menu.getItems().add(new SeparatorMenuItem());
|
||||||
|
|
||||||
menu.getItems().add(category("addHost", "mdi2h-home-plus",
|
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh"));
|
||||||
DataStoreProvider.CreationCategory.HOST, "ssh"));
|
|
||||||
|
|
||||||
menu.getItems().add(category("addShell", "mdi2t-text-box-multiple",
|
menu.getItems()
|
||||||
DataStoreProvider.CreationCategory.SHELL, null));
|
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
|
||||||
|
|
||||||
menu.getItems().add(category("addScript", "mdi2s-script-text-outline",
|
menu.getItems()
|
||||||
DataStoreProvider.CreationCategory.SCRIPT, "script"));
|
.add(category(
|
||||||
|
"addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script"));
|
||||||
|
|
||||||
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than",
|
menu.getItems()
|
||||||
DataStoreProvider.CreationCategory.COMMAND, "cmd"));
|
.add(category(
|
||||||
|
"addCommand", "mdi2c-code-greater-than", DataStoreProvider.CreationCategory.COMMAND, "cmd"));
|
||||||
|
|
||||||
menu.getItems().add(category("addTunnel", "mdi2v-vector-polyline-plus",
|
menu.getItems()
|
||||||
DataStoreProvider.CreationCategory.TUNNEL, null));
|
.add(category(
|
||||||
|
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreProvider.CreationCategory.TUNNEL, null));
|
||||||
|
|
||||||
menu.getItems().add(category("addCluster", "mdi2d-domain-plus",
|
menu.getItems()
|
||||||
DataStoreProvider.CreationCategory.CLUSTER, null));
|
.add(category("addDatabase", "mdi2d-database-plus", DataStoreProvider.CreationCategory.DATABASE, null));
|
||||||
|
|
||||||
menu.getItems().add(category("addDatabase", "mdi2d-database-plus",
|
|
||||||
DataStoreProvider.CreationCategory.DATABASE, null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MenuItem category(String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) {
|
private static MenuItem category(
|
||||||
var sub = DataStoreProviders.getAll().stream().filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())).toList();
|
String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) {
|
||||||
|
var sub = DataStoreProviders.getAll().stream()
|
||||||
|
.filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()))
|
||||||
|
.toList();
|
||||||
if (sub.size() < 2) {
|
if (sub.size() < 2) {
|
||||||
var item = new MenuItem();
|
var item = new MenuItem();
|
||||||
item.setGraphic(new FontIcon(graphic));
|
item.setGraphic(new FontIcon(graphic));
|
||||||
item.textProperty().bind(AppI18n.observable(name));
|
item.textProperty().bind(AppI18n.observable(name));
|
||||||
item.setOnAction(event -> {
|
item.setOnAction(event -> {
|
||||||
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null,
|
StoreCreationComp.showCreation(
|
||||||
v -> category.equals(v.getCreationCategory()));
|
defaultProvider != null
|
||||||
|
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||||
|
: null,
|
||||||
|
category);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
return item;
|
return item;
|
||||||
|
@ -68,16 +73,19 @@ public class StoreCreationMenu {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null,
|
StoreCreationComp.showCreation(
|
||||||
v -> category.equals(v.getCreationCategory()));
|
defaultProvider != null
|
||||||
|
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||||
|
: null,
|
||||||
|
category);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
sub.forEach(dataStoreProvider -> {
|
sub.forEach(dataStoreProvider -> {
|
||||||
var item = new MenuItem(dataStoreProvider.getDisplayName());
|
var item = new MenuItem(dataStoreProvider.getDisplayName());
|
||||||
item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null)).createRegion());
|
item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null))
|
||||||
|
.createRegion());
|
||||||
item.setOnAction(event -> {
|
item.setOnAction(event -> {
|
||||||
StoreCreationComp.showCreation(dataStoreProvider,
|
StoreCreationComp.showCreation(dataStoreProvider, category);
|
||||||
v -> category.equals(v.getCreationCategory()));
|
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(item);
|
menu.getItems().add(item);
|
||||||
|
|
|
@ -40,24 +40,6 @@ import java.util.Arrays;
|
||||||
|
|
||||||
public abstract class StoreEntryComp extends SimpleComp {
|
public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
public static StoreEntryComp create(
|
|
||||||
StoreEntryWrapper entry, Comp<?> content, boolean preferLarge) {
|
|
||||||
if (!preferLarge) {
|
|
||||||
return new DenseStoreEntryComp(entry, true, content);
|
|
||||||
} else {
|
|
||||||
return new StandardStoreEntryComp(entry, content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
|
|
||||||
var prov = e.getWrapper().getEntry().getProvider();
|
|
||||||
if (prov != null) {
|
|
||||||
return prov.customEntryComp(e, topLevel);
|
|
||||||
} else {
|
|
||||||
return new StandardStoreEntryComp(e.getWrapper(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
||||||
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
||||||
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
|
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
|
||||||
|
@ -72,6 +54,29 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static StoreEntryComp create(StoreEntryWrapper entry, Comp<?> content, boolean preferLarge) {
|
||||||
|
var forceCondensed = AppPrefs.get() != null
|
||||||
|
&& AppPrefs.get().condenseConnectionDisplay().get();
|
||||||
|
if (!preferLarge || forceCondensed) {
|
||||||
|
return new DenseStoreEntryComp(entry, true, content);
|
||||||
|
} else {
|
||||||
|
return new StandardStoreEntryComp(entry, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
|
||||||
|
var prov = e.getWrapper().getEntry().getProvider();
|
||||||
|
if (prov != null) {
|
||||||
|
return prov.customEntryComp(e, topLevel);
|
||||||
|
} else {
|
||||||
|
var forceCondensed = AppPrefs.get() != null
|
||||||
|
&& AppPrefs.get().condenseConnectionDisplay().get();
|
||||||
|
return forceCondensed
|
||||||
|
? new DenseStoreEntryComp(e.getWrapper(), true, null)
|
||||||
|
: new StandardStoreEntryComp(e.getWrapper(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final Region createSimple() {
|
protected final Region createSimple() {
|
||||||
var r = createContent();
|
var r = createContent();
|
||||||
|
@ -83,8 +88,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
button.setPadding(Insets.EMPTY);
|
button.setPadding(Insets.EMPTY);
|
||||||
button.setMaxWidth(5000);
|
button.setMaxWidth(5000);
|
||||||
button.setFocusTraversable(true);
|
button.setFocusTraversable(true);
|
||||||
button.accessibleTextProperty()
|
button.accessibleTextProperty().bind(wrapper.nameProperty());
|
||||||
.bind(wrapper.nameProperty());
|
|
||||||
button.setOnAction(event -> {
|
button.setOnAction(event -> {
|
||||||
event.consume();
|
event.consume();
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
@ -105,8 +109,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
protected Label createInformation() {
|
protected Label createInformation() {
|
||||||
var information = new Label();
|
var information = new Label();
|
||||||
information.setGraphicTextGap(7);
|
information.setGraphicTextGap(7);
|
||||||
information.textProperty().bind(wrapper.getEntry().getProvider() != null ?
|
information
|
||||||
PlatformThread.sync(wrapper.getEntry().getProvider().informationString(wrapper)) : new SimpleStringProperty());
|
.textProperty()
|
||||||
|
.bind(
|
||||||
|
wrapper.getEntry().getProvider() != null
|
||||||
|
? PlatformThread.sync(
|
||||||
|
wrapper.getEntry().getProvider().informationString(wrapper))
|
||||||
|
: new SimpleStringProperty());
|
||||||
information.getStyleClass().add("information");
|
information.getStyleClass().add("information");
|
||||||
AppFont.header(information);
|
AppFont.header(information);
|
||||||
|
|
||||||
|
@ -191,15 +200,16 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var button = new IconButtonComp(
|
var button =
|
||||||
actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
|
new IconButtonComp(actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
var action = actionProvider.createAction(
|
var action = actionProvider.createAction(
|
||||||
wrapper.getEntry().ref());
|
wrapper.getEntry().ref());
|
||||||
action.execute();
|
action.execute();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
button.accessibleText(
|
||||||
|
actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
||||||
button.apply(new FancyTooltipAugment<>(
|
button.apply(new FancyTooltipAugment<>(
|
||||||
actionProvider.getName(wrapper.getEntry().ref())));
|
actionProvider.getName(wrapper.getEntry().ref())));
|
||||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
||||||
|
@ -213,11 +223,11 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
var settingsButton = createSettingsButton();
|
var settingsButton = createSettingsButton();
|
||||||
list.add(settingsButton);
|
list.add(settingsButton);
|
||||||
if (list.size() > 1) {
|
if (list.size() > 1) {
|
||||||
list.get(0).styleClass(Styles.LEFT_PILL);
|
list.getFirst().styleClass(Styles.LEFT_PILL);
|
||||||
for (int i = 1; i < list.size() - 1; i++) {
|
for (int i = 1; i < list.size() - 1; i++) {
|
||||||
list.get(i).styleClass(Styles.CENTER_PILL);
|
list.get(i).styleClass(Styles.CENTER_PILL);
|
||||||
}
|
}
|
||||||
list.get(list.size() - 1).styleClass(Styles.RIGHT_PILL);
|
list.getLast().styleClass(Styles.RIGHT_PILL);
|
||||||
}
|
}
|
||||||
list.forEach(comp -> {
|
list.forEach(comp -> {
|
||||||
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
|
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
|
||||||
|
@ -264,8 +274,10 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
? new Menu(null, new FontIcon(icon))
|
? new Menu(null, new FontIcon(icon))
|
||||||
: new MenuItem(null, new FontIcon(icon));
|
: new MenuItem(null, new FontIcon(icon));
|
||||||
|
|
||||||
var proRequired = p.getKey().getProFeatureId() != null &&
|
var proRequired = p.getKey().getProFeatureId() != null
|
||||||
!LicenseProvider.get().getFeature(p.getKey().getProFeatureId()).isSupported();
|
&& !LicenseProvider.get()
|
||||||
|
.getFeature(p.getKey().getProFeatureId())
|
||||||
|
.isSupported();
|
||||||
if (proRequired) {
|
if (proRequired) {
|
||||||
item.setDisable(true);
|
item.setDisable(true);
|
||||||
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
|
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
|
||||||
|
@ -285,8 +297,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
var action = actionProvider.createAction(
|
var action = actionProvider.createAction(wrapper.getEntry().ref());
|
||||||
wrapper.getEntry().ref());
|
|
||||||
action.execute();
|
action.execute();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -302,20 +313,27 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
run.textProperty().bind(AppI18n.observable("base.execute"));
|
run.textProperty().bind(AppI18n.observable("base.execute"));
|
||||||
run.setOnAction(event -> {
|
run.setOnAction(event -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
p.getKey().getDataStoreCallSite().createAction(wrapper.getEntry().ref()).execute();
|
p.getKey()
|
||||||
|
.getDataStoreCallSite()
|
||||||
|
.createAction(wrapper.getEntry().ref())
|
||||||
|
.execute();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
menu.getItems().add(run);
|
menu.getItems().add(run);
|
||||||
|
|
||||||
|
|
||||||
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
|
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
|
||||||
var url = "xpipe://action/" + p.getKey().getId() + "/"
|
var url = "xpipe://action/" + p.getKey().getId() + "/"
|
||||||
+ wrapper.getEntry().getUuid();
|
+ wrapper.getEntry().getUuid();
|
||||||
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
|
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
|
||||||
sc.setOnAction(event -> {
|
sc.setOnAction(event -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
DesktopShortcuts.create(url,
|
DesktopShortcuts.create(
|
||||||
wrapper.nameProperty().getValue() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().ref()).getValue() + ")");
|
url,
|
||||||
|
wrapper.nameProperty().getValue() + " ("
|
||||||
|
+ p.getKey()
|
||||||
|
.getDataStoreCallSite()
|
||||||
|
.getName(wrapper.getEntry().ref())
|
||||||
|
.getValue() + ")");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
menu.getItems().add(sc);
|
menu.getItems().add(sc);
|
||||||
|
@ -345,9 +363,12 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
contextMenu.getItems().add(browse);
|
contextMenu.getItems().add(browse);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
|
if (wrapper.getEntry().getProvider() != null
|
||||||
|
&& wrapper.getEntry().getProvider().canMoveCategories()) {
|
||||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||||
StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue().getRoot()).forEach(storeCategoryWrapper -> {
|
StoreViewState.get()
|
||||||
|
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||||
|
.forEach(storeCategoryWrapper -> {
|
||||||
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||||
|
@ -382,9 +403,16 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||||
del.disableProperty().bind(Bindings.createBooleanBinding(() -> {
|
del.disableProperty()
|
||||||
return !wrapper.getDeletable().get() && !AppPrefs.get().developerDisableGuiRestrictions().get();
|
.bind(Bindings.createBooleanBinding(
|
||||||
}, wrapper.getDeletable(), AppPrefs.get().developerDisableGuiRestrictions()));
|
() -> {
|
||||||
|
return !wrapper.getDeletable().get()
|
||||||
|
&& !AppPrefs.get()
|
||||||
|
.developerDisableGuiRestrictions()
|
||||||
|
.get();
|
||||||
|
},
|
||||||
|
wrapper.getDeletable(),
|
||||||
|
AppPrefs.get().developerDisableGuiRestrictions()));
|
||||||
del.setOnAction(event -> wrapper.delete());
|
del.setOnAction(event -> wrapper.delete());
|
||||||
contextMenu.getItems().add(del);
|
contextMenu.getItems().add(del);
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,18 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
var showIntro = Bindings.createBooleanBinding(
|
var showIntro = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var all = StoreViewState.get().getAllConnectionsCategory();
|
var all = StoreViewState.get().getAllConnectionsCategory();
|
||||||
var connections = StoreViewState.get().getAllEntries().stream().filter(wrapper -> all.contains(wrapper.getEntry())).toList();
|
var connections = StoreViewState.get().getAllEntries().stream()
|
||||||
return initialCount == connections.size() && StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory());
|
.filter(wrapper -> all.contains(wrapper))
|
||||||
|
.toList();
|
||||||
|
return initialCount == connections.size()
|
||||||
|
&& StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(StoreViewState.get().getAllConnectionsCategory());
|
||||||
},
|
},
|
||||||
StoreViewState.get().getAllEntries(), StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getAllEntries(),
|
||||||
|
StoreViewState.get().getActiveCategory());
|
||||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||||
map.put(
|
map.put(
|
||||||
createList(),
|
createList(),
|
||||||
|
|
|
@ -37,23 +37,43 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
public StoreEntryListStatusComp() {
|
public StoreEntryListStatusComp() {
|
||||||
this.sortMode = new SimpleObjectProperty<>();
|
this.sortMode = new SimpleObjectProperty<>();
|
||||||
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
|
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
|
||||||
sortMode.unbind();
|
sortMode.setValue(val.getSortMode().getValue());
|
||||||
sortMode.bindBidirectional(val.getSortMode());
|
});
|
||||||
|
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||||
|
var cat = StoreViewState.get().getActiveCategory().getValue();
|
||||||
|
if (cat == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cat.getSortMode().setValue(newValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region createGroupListHeader() {
|
private Region createGroupListHeader() {
|
||||||
var label = new Label();
|
var label = new Label();
|
||||||
label.textProperty().bind(Bindings.createStringBinding(() -> {
|
label.textProperty()
|
||||||
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "Connections" : "Scripts";
|
.bind(Bindings.createStringBinding(
|
||||||
}, StoreViewState.get().getActiveCategory()));
|
() -> {
|
||||||
|
return StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(StoreViewState.get().getAllConnectionsCategory())
|
||||||
|
? "Connections"
|
||||||
|
: "Scripts";
|
||||||
|
},
|
||||||
|
StoreViewState.get().getActiveCategory()));
|
||||||
label.getStyleClass().add("name");
|
label.getStyleClass().add("name");
|
||||||
|
|
||||||
var all = BindingsHelper.filteredContentBinding(
|
var all = BindingsHelper.filteredContentBinding(
|
||||||
StoreViewState.get().getAllEntries(),
|
StoreViewState.get().getAllEntries(),
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||||
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(storeRoot);
|
return StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(storeRoot);
|
||||||
},
|
},
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
var shownList = BindingsHelper.filteredContentBinding(
|
var shownList = BindingsHelper.filteredContentBinding(
|
||||||
|
@ -66,7 +86,13 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
var count = new CountComp<>(shownList, all);
|
var count = new CountComp<>(shownList, all);
|
||||||
|
|
||||||
var c = count.createRegion();
|
var c = count.createRegion();
|
||||||
var topBar = new HBox(label, c, Comp.hspacer().createRegion(), createDateSortButton().createRegion(), Comp.hspacer(2).createRegion(), createAlphabeticalSortButton().createRegion());
|
var topBar = new HBox(
|
||||||
|
label,
|
||||||
|
c,
|
||||||
|
Comp.hspacer().createRegion(),
|
||||||
|
createDateSortButton().createRegion(),
|
||||||
|
Comp.hspacer(2).createRegion(),
|
||||||
|
createAlphabeticalSortButton().createRegion());
|
||||||
AppFont.setSize(label, 3);
|
AppFont.setSize(label, 3);
|
||||||
AppFont.setSize(c, 3);
|
AppFont.setSize(c, 3);
|
||||||
topBar.setAlignment(Pos.CENTER);
|
topBar.setAlignment(Pos.CENTER);
|
||||||
|
@ -104,7 +130,6 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
||||||
f.setPadding(new Insets(-3, 0, -3, 0));
|
f.setPadding(new Insets(-3, 0, -3, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AppFont.medium(hbox);
|
AppFont.medium(hbox);
|
||||||
return hbox;
|
return hbox;
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,10 @@ public class StoreEntryWrapper {
|
||||||
deletable.setValue(entry.getConfiguration().isDeletable()
|
deletable.setValue(entry.getConfiguration().isDeletable()
|
||||||
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
|
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
|
||||||
|
|
||||||
category.setValue(StoreViewState.get().getCategoryWrapper(DataStorage.get().getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow()));
|
category.setValue(StoreViewState.get()
|
||||||
|
.getCategoryWrapper(DataStorage.get()
|
||||||
|
.getStoreCategoryIfPresent(entry.getCategoryUuid())
|
||||||
|
.orElseThrow()));
|
||||||
|
|
||||||
if (!entry.getValidity().isUsable()) {
|
if (!entry.getValidity().isUsable()) {
|
||||||
summary.setValue(null);
|
summary.setValue(null);
|
||||||
|
@ -155,8 +158,7 @@ public class StoreEntryWrapper {
|
||||||
&& e.getDefaultDataStoreCallSite()
|
&& e.getDefaultDataStoreCallSite()
|
||||||
.getApplicableClass()
|
.getApplicableClass()
|
||||||
.isAssignableFrom(entry.getStore().getClass())
|
.isAssignableFrom(entry.getStore().getClass())
|
||||||
&& e.getDefaultDataStoreCallSite()
|
&& e.getDefaultDataStoreCallSite().isApplicable(entry.ref()))
|
||||||
.isApplicable(entry.ref()))
|
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(ActionProvider::getDefaultDataStoreCallSite)
|
.map(ActionProvider::getDefaultDataStoreCallSite)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
@ -7,11 +8,9 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.ScanAlert;
|
import io.xpipe.app.util.ScanAlert;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.geometry.Orientation;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Separator;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
@ -23,23 +22,23 @@ public class StoreIntroComp extends SimpleComp {
|
||||||
@Override
|
@Override
|
||||||
public Region createSimple() {
|
public Region createSimple() {
|
||||||
var title = new Label(AppI18n.get("storeIntroTitle"));
|
var title = new Label(AppI18n.get("storeIntroTitle"));
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
AppFont.setSize(title, 7);
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
var introDesc = new Label(AppI18n.get("storeIntroDescription"));
|
var introDesc = new Label(AppI18n.get("storeIntroDescription"));
|
||||||
|
introDesc.setWrapText(true);
|
||||||
var mfi = new FontIcon("mdi2p-playlist-plus");
|
introDesc.setMaxWidth(470);
|
||||||
var machine = new Label(AppI18n.get("storeMachineDescription"));
|
|
||||||
machine.heightProperty().addListener((c, o, n) -> {
|
|
||||||
mfi.iconSizeProperty().set(n.intValue());
|
|
||||||
});
|
|
||||||
|
|
||||||
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
|
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
|
||||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
||||||
|
scanButton.setDefaultButton(true);
|
||||||
var scanPane = new StackPane(scanButton);
|
var scanPane = new StackPane(scanButton);
|
||||||
scanPane.setAlignment(Pos.CENTER);
|
scanPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion();
|
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150)
|
||||||
var text = new VBox(title, introDesc, new Separator(Orientation.HORIZONTAL), machine);
|
.createRegion();
|
||||||
|
var text = new VBox(title, introDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
text.setAlignment(Pos.CENTER_LEFT);
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
var hbox = new HBox(img, text);
|
var hbox = new HBox(img, text);
|
||||||
hbox.setSpacing(35);
|
hbox.setSpacing(35);
|
||||||
|
|
|
@ -19,10 +19,12 @@ public class StoreLayoutComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
|
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
||||||
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
|
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
|
||||||
|
.withOnDividerChange(aDouble -> {
|
||||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||||
}).createStructure();
|
})
|
||||||
|
.createStructure();
|
||||||
struc.getLeft().setMinWidth(260);
|
struc.getLeft().setMinWidth(260);
|
||||||
struc.getLeft().setMaxWidth(500);
|
struc.getLeft().setMaxWidth(500);
|
||||||
struc.get().getStyleClass().add("store-layout");
|
struc.get().getStyleClass().add("store-layout");
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.DataStoreProvider;
|
||||||
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.util.JfxHelper;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataStoreProvider>>> {
|
||||||
|
|
||||||
|
Predicate<DataStoreProvider> filter;
|
||||||
|
Property<DataStoreProvider> provider;
|
||||||
|
boolean staticDisplay;
|
||||||
|
|
||||||
|
private List<DataStoreProvider> getProviders() {
|
||||||
|
return DataStoreProviders.getAll().stream()
|
||||||
|
.filter(val -> filter == null || filter.test(val))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createGraphic(DataStoreProvider provider) {
|
||||||
|
if (provider == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var graphic = provider.getDisplayIconFileName(null);
|
||||||
|
return JfxHelper.createNamedEntry(provider.getDisplayName(), provider.getDisplayDescription(), graphic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompStructure<ComboBox<DataStoreProvider>> createBase() {
|
||||||
|
Supplier<ListCell<DataStoreProvider>> cellFactory = () -> new ListCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(DataStoreProvider item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
setGraphic(createGraphic(item));
|
||||||
|
setAccessibleText(item != null ? item.getDisplayName() : null);
|
||||||
|
setAccessibleHelp(item != null ? item.getDisplayDescription() : null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var cb = new ComboBox<DataStoreProvider>();
|
||||||
|
cb.setCellFactory(param -> {
|
||||||
|
return cellFactory.get();
|
||||||
|
});
|
||||||
|
cb.setButtonCell(cellFactory.get());
|
||||||
|
var l = getProviders().stream()
|
||||||
|
.filter(p -> p.getCreationCategory() != null || staticDisplay)
|
||||||
|
.toList();
|
||||||
|
l.forEach(dataStoreProvider -> cb.getItems().add(dataStoreProvider));
|
||||||
|
if (provider.getValue() == null) {
|
||||||
|
provider.setValue(l.getFirst());
|
||||||
|
}
|
||||||
|
cb.setValue(provider.getValue());
|
||||||
|
provider.bind(cb.valueProperty());
|
||||||
|
cb.getStyleClass().add("choice-comp");
|
||||||
|
cb.setAccessibleText("Choose connection type");
|
||||||
|
cb.setOnKeyPressed(event -> {
|
||||||
|
if (!event.getCode().equals(KeyCode.ENTER)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.show();
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
return new SimpleCompStructure<>(cb);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,15 +19,6 @@ import java.util.function.Predicate;
|
||||||
@Value
|
@Value
|
||||||
public class StoreSection {
|
public class StoreSection {
|
||||||
|
|
||||||
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
|
|
||||||
var prov = e.getWrapper().getEntry().getProvider();
|
|
||||||
if (prov != null) {
|
|
||||||
return prov.customSectionComp(e, topLevel);
|
|
||||||
} else {
|
|
||||||
return new StoreSectionComp(e, topLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StoreEntryWrapper wrapper;
|
StoreEntryWrapper wrapper;
|
||||||
ObservableList<StoreSection> allChildren;
|
ObservableList<StoreSection> allChildren;
|
||||||
ObservableList<StoreSection> shownChildren;
|
ObservableList<StoreSection> shownChildren;
|
||||||
|
@ -55,6 +46,15 @@ public class StoreSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
|
||||||
|
var prov = e.getWrapper().getEntry().getProvider();
|
||||||
|
if (prov != null) {
|
||||||
|
return prov.customSectionComp(e, topLevel);
|
||||||
|
} else {
|
||||||
|
return new StoreSectionComp(e, topLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static ObservableList<StoreSection> sorted(
|
private static ObservableList<StoreSection> sorted(
|
||||||
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
|
@ -63,14 +63,16 @@ public class StoreSection {
|
||||||
|
|
||||||
var c = Comparator.<StoreSection>comparingInt(
|
var c = Comparator.<StoreSection>comparingInt(
|
||||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
||||||
var mappedSortMode = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
var mappedSortMode = BindingsHelper.mappedBinding(
|
||||||
|
category,
|
||||||
|
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||||
return BindingsHelper.orderedContentBinding(
|
return BindingsHelper.orderedContentBinding(
|
||||||
list,
|
list,
|
||||||
(o1, o2) -> {
|
(o1, o2) -> {
|
||||||
var current = mappedSortMode.getValue();
|
var current = mappedSortMode.getValue();
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
return c.thenComparing(current.comparator())
|
return c.thenComparing(current.comparator())
|
||||||
.compare(o1, o2);
|
.compare(current.representative(o1), current.representative(o2));
|
||||||
} else {
|
} else {
|
||||||
return c.compare(o1, o2);
|
return c.compare(o1, o2);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,9 @@ public class StoreSection {
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
var matchesSelector = section.anyMatches(entryFilter);
|
var matchesSelector = section.anyMatches(entryFilter);
|
||||||
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
|
var sameCategory = category == null
|
||||||
|
|| category.getValue() == null
|
||||||
|
|| category.getValue().contains(section.getWrapper());
|
||||||
return showFilter && matchesSelector && sameCategory;
|
return showFilter && matchesSelector && sameCategory;
|
||||||
},
|
},
|
||||||
category,
|
category,
|
||||||
|
@ -117,7 +121,7 @@ public class StoreSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
||||||
// Legacy implementation that does not use caches. Use for testing
|
// Legacy implementation that does not use children caches. Use for testing
|
||||||
// if (true) return DataStorage.get()
|
// if (true) return DataStorage.get()
|
||||||
// .getDisplayParent(other.getEntry())
|
// .getDisplayParent(other.getEntry())
|
||||||
// .map(found -> found.equals(e.getEntry()))
|
// .map(found -> found.equals(e.getEntry()))
|
||||||
|
@ -134,9 +138,13 @@ public class StoreSection {
|
||||||
section -> {
|
section -> {
|
||||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
var matchesSelector = section.anyMatches(entryFilter);
|
var matchesSelector = section.anyMatches(entryFilter);
|
||||||
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
|
var sameCategory = category == null
|
||||||
// If this entry is already shown as root due to a different category than parent, don't show it again here
|
|| category.getValue() == null
|
||||||
var notRoot = !DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
|| category.getValue().contains(section.getWrapper());
|
||||||
|
// If this entry is already shown as root due to a different category than parent, don't show it
|
||||||
|
// again here
|
||||||
|
var notRoot =
|
||||||
|
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
||||||
return showFilter && matchesSelector && sameCategory && notRoot;
|
return showFilter && matchesSelector && sameCategory && notRoot;
|
||||||
},
|
},
|
||||||
category,
|
category,
|
||||||
|
|
|
@ -22,12 +22,11 @@ import java.util.List;
|
||||||
|
|
||||||
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
|
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||||
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
|
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
|
||||||
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
|
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
|
||||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
|
||||||
|
|
||||||
private final StoreSection section;
|
private final StoreSection section;
|
||||||
private final boolean topLevel;
|
private final boolean topLevel;
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<VBox> createBase() {
|
public CompStructure<VBox> createBase() {
|
||||||
var root = StandardStoreEntryComp.customSection(section, topLevel)
|
var root = StoreEntryComp.customSection(section, topLevel)
|
||||||
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||||
var button = new IconButtonComp(
|
var button = new IconButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
|
@ -54,9 +53,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
.apply(struc -> struc.get().setMinWidth(30))
|
.apply(struc -> struc.get().setMinWidth(30))
|
||||||
.apply(struc -> struc.get().setPrefWidth(30))
|
.apply(struc -> struc.get().setPrefWidth(30))
|
||||||
.focusTraversable()
|
.focusTraversable()
|
||||||
.accessibleText(Bindings.createStringBinding(() -> {
|
.accessibleText(Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
return "Expand " + section.getWrapper().getName().getValue();
|
return "Expand " + section.getWrapper().getName().getValue();
|
||||||
}, section.getWrapper().getName()))
|
},
|
||||||
|
section.getWrapper().getName()))
|
||||||
.disable(BindingsHelper.persist(
|
.disable(BindingsHelper.persist(
|
||||||
Bindings.size(section.getShownChildren()).isEqualTo(0)))
|
Bindings.size(section.getShownChildren()).isEqualTo(0)))
|
||||||
.grow(false, true)
|
.grow(false, true)
|
||||||
|
|
|
@ -28,19 +28,18 @@ import java.util.function.BiConsumer;
|
||||||
@Builder
|
@Builder
|
||||||
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
public static Comp<?> createList(StoreSection top, BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment) {
|
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||||
return new StoreSectionMiniComp(top, augment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
|
||||||
|
|
||||||
private final StoreSection section;
|
private final StoreSection section;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (section1, buttonComp) -> {};
|
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (section1, buttonComp) -> {};
|
||||||
|
|
||||||
|
public static Comp<?> createList(StoreSection top, BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment) {
|
||||||
|
return new StoreSectionMiniComp(top, augment);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<VBox> createBase() {
|
public CompStructure<VBox> createBase() {
|
||||||
var list = new ArrayList<Comp<?>>();
|
var list = new ArrayList<Comp<?>>();
|
||||||
|
@ -48,14 +47,14 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
if (section.getWrapper() != null) {
|
if (section.getWrapper() != null) {
|
||||||
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
var provider = section.getWrapper()
|
var provider = section.getWrapper().getEntry().getProvider();
|
||||||
.getEntry()
|
|
||||||
.getProvider();
|
|
||||||
struc.get()
|
struc.get()
|
||||||
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(provider != null ? provider
|
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(
|
||||||
.getDisplayIconFileName(section.getWrapper()
|
provider != null
|
||||||
|
? provider.getDisplayIconFileName(section.getWrapper()
|
||||||
.getEntry()
|
.getEntry()
|
||||||
.getStore()) : null)
|
.getStore())
|
||||||
|
: null)
|
||||||
.createRegion());
|
.createRegion());
|
||||||
})
|
})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
|
@ -79,31 +78,40 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
.apply(struc -> struc.get().setMinWidth(20))
|
.apply(struc -> struc.get().setMinWidth(20))
|
||||||
.apply(struc -> struc.get().setPrefWidth(20))
|
.apply(struc -> struc.get().setPrefWidth(20))
|
||||||
.focusTraversable()
|
.focusTraversable()
|
||||||
.accessibleText(Bindings.createStringBinding(() -> {
|
.accessibleText(Bindings.createStringBinding(
|
||||||
return "Expand " + section.getWrapper().getName().getValue();
|
() -> {
|
||||||
}, section.getWrapper().getName()))
|
return "Expand "
|
||||||
|
+ section.getWrapper().getName().getValue();
|
||||||
|
},
|
||||||
|
section.getWrapper().getName()))
|
||||||
.disable(BindingsHelper.persist(
|
.disable(BindingsHelper.persist(
|
||||||
Bindings.size(section.getAllChildren()).isEqualTo(0)))
|
Bindings.size(section.getAllChildren()).isEqualTo(0)))
|
||||||
.grow(false, true)
|
.grow(false, true)
|
||||||
.styleClass("expand-button");
|
.styleClass("expand-button");
|
||||||
List<Comp<?>> topEntryList = List.of(button, root);
|
List<Comp<?>> topEntryList = List.of(button, root);
|
||||||
list.add(new HorizontalComp(topEntryList)
|
list.add(new HorizontalComp(topEntryList).apply(struc -> struc.get().setFillHeight(true)));
|
||||||
.apply(struc -> struc.get().setFillHeight(true)));
|
|
||||||
} else {
|
} else {
|
||||||
expanded = new SimpleBooleanProperty(true);
|
expanded = new SimpleBooleanProperty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||||
// section is actually expanded
|
// section is actually expanded
|
||||||
var listSections = section.getWrapper() != null ? BindingsHelper.filteredContentBinding(
|
var listSections = section.getWrapper() != null
|
||||||
|
? BindingsHelper.filteredContentBinding(
|
||||||
section.getShownChildren(),
|
section.getShownChildren(),
|
||||||
storeSection -> section.getAllChildren().size() <= 20
|
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||||
|| expanded.get(),
|
|
||||||
expanded,
|
expanded,
|
||||||
section.getAllChildren()) : section.getShownChildren();
|
section.getAllChildren())
|
||||||
|
: section.getShownChildren();
|
||||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||||
return StoreSectionMiniComp.builder().section(e).augment(this.augment).build();
|
return StoreSectionMiniComp.builder()
|
||||||
}).withLimit(100).minHeight(0).hgrow();
|
.section(e)
|
||||||
|
.augment(this.augment)
|
||||||
|
.build();
|
||||||
|
})
|
||||||
|
.withLimit(100)
|
||||||
|
.minHeight(0)
|
||||||
|
.hgrow();
|
||||||
|
|
||||||
list.add(new HorizontalComp(List.of(content))
|
list.add(new HorizontalComp(List.of(content))
|
||||||
.styleClass("content")
|
.styleClass("content")
|
||||||
|
@ -130,8 +138,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struc.get().getStyleClass().removeIf(
|
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values())
|
||||||
s -> Arrays.stream(DataStoreColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
|
.anyMatch(dataStoreColor ->
|
||||||
|
dataStoreColor.getId().equals(s)));
|
||||||
struc.get().getStyleClass().remove("none");
|
struc.get().getStyleClass().remove("none");
|
||||||
struc.get().getStyleClass().add("color-box");
|
struc.get().getStyleClass().add("color-box");
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
|
|
|
@ -14,9 +14,18 @@ public class StoreSidebarComp extends SimpleComp {
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var sideBar = new VerticalComp(List.of(
|
var sideBar = new VerticalComp(List.of(
|
||||||
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
|
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
|
||||||
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").styleClass("gray"),
|
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory())
|
||||||
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()).styleClass("color-box").styleClass("gray"),
|
.styleClass("color-box")
|
||||||
Comp.of(() -> new Region()).styleClass("bar").styleClass("color-box").styleClass("gray").styleClass("filler-bar").vgrow()));
|
.styleClass("gray"),
|
||||||
|
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory())
|
||||||
|
.styleClass("color-box")
|
||||||
|
.styleClass("gray"),
|
||||||
|
Comp.of(() -> new Region())
|
||||||
|
.styleClass("bar")
|
||||||
|
.styleClass("color-box")
|
||||||
|
.styleClass("gray")
|
||||||
|
.styleClass("filler-bar")
|
||||||
|
.vgrow()));
|
||||||
sideBar.apply(struc -> struc.get().setFillWidth(true));
|
sideBar.apply(struc -> struc.get().setFillWidth(true));
|
||||||
sideBar.styleClass("sidebar");
|
sideBar.styleClass("sidebar");
|
||||||
sideBar.prefWidth(240);
|
sideBar.prefWidth(240);
|
||||||
|
|
|
@ -12,6 +12,11 @@ import java.util.stream.Stream;
|
||||||
public interface StoreSortMode {
|
public interface StoreSortMode {
|
||||||
|
|
||||||
StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
|
StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
|
||||||
|
@Override
|
||||||
|
public StoreSection representative(StoreSection s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "alphabetical-desc";
|
return "alphabetical-desc";
|
||||||
|
@ -23,8 +28,12 @@ public interface StoreSortMode {
|
||||||
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT));
|
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
|
StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
|
||||||
|
@Override
|
||||||
|
public StoreSection representative(StoreSection s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "alphabetical-asc";
|
return "alphabetical-asc";
|
||||||
|
@ -37,8 +46,21 @@ public interface StoreSortMode {
|
||||||
.reversed();
|
.reversed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StoreSortMode DATE_DESC = new StoreSortMode() {
|
StoreSortMode DATE_DESC = new StoreSortMode() {
|
||||||
|
@Override
|
||||||
|
public StoreSection representative(StoreSection s) {
|
||||||
|
var c = comparator();
|
||||||
|
return Stream.of(
|
||||||
|
s.getShownChildren().stream()
|
||||||
|
.max((o1, o2) -> {
|
||||||
|
return c.compare(representative(o1), representative(o2));
|
||||||
|
})
|
||||||
|
.orElse(s),
|
||||||
|
s)
|
||||||
|
.max(c)
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "date-desc";
|
return "date-desc";
|
||||||
|
@ -54,8 +76,21 @@ public interface StoreSortMode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
StoreSortMode DATE_ASC = new StoreSortMode() {
|
StoreSortMode DATE_ASC = new StoreSortMode() {
|
||||||
|
@Override
|
||||||
|
public StoreSection representative(StoreSection s) {
|
||||||
|
var c = comparator();
|
||||||
|
return Stream.of(
|
||||||
|
s.getShownChildren().stream()
|
||||||
|
.min((o1, o2) -> {
|
||||||
|
return c.compare(representative(o1), representative(o2));
|
||||||
|
})
|
||||||
|
.orElse(s),
|
||||||
|
s)
|
||||||
|
.min(c)
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "date-asc";
|
return "date-asc";
|
||||||
|
@ -68,9 +103,11 @@ public interface StoreSortMode {
|
||||||
.map(entry -> entry.getLastAccess())
|
.map(entry -> entry.getLastAccess())
|
||||||
.max(Comparator.naturalOrder())
|
.max(Comparator.naturalOrder())
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}).reversed();
|
})
|
||||||
|
.reversed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
|
||||||
|
|
||||||
static Stream<DataStoreEntry> flatten(StoreSection section) {
|
static Stream<DataStoreEntry> flatten(StoreSection section) {
|
||||||
return Stream.concat(
|
return Stream.concat(
|
||||||
|
@ -78,14 +115,14 @@ public interface StoreSortMode {
|
||||||
section.getAllChildren().stream().flatMap(section1 -> flatten(section1)));
|
section.getAllChildren().stream().flatMap(section1 -> flatten(section1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
|
|
||||||
|
|
||||||
static Optional<StoreSortMode> fromId(String id) {
|
static Optional<StoreSortMode> fromId(String id) {
|
||||||
return ALL.stream()
|
return ALL.stream()
|
||||||
.filter(storeSortMode -> storeSortMode.getId().equals(id))
|
.filter(storeSortMode -> storeSortMode.getId().equals(id))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StoreSection representative(StoreSection s);
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
Comparator<StoreSection> comparator();
|
Comparator<StoreSection> comparator();
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.comp.store;
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
@ -23,6 +24,26 @@ import java.util.stream.Collectors;
|
||||||
public class StoreViewState {
|
public class StoreViewState {
|
||||||
|
|
||||||
private static StoreViewState INSTANCE;
|
private static StoreViewState INSTANCE;
|
||||||
|
private final StringProperty filter = new SimpleStringProperty();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ObservableList<StoreEntryWrapper> allEntries =
|
||||||
|
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ObservableList<StoreCategoryWrapper> categories =
|
||||||
|
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private StoreSection currentTopLevelSection;
|
||||||
|
|
||||||
|
private StoreViewState() {
|
||||||
|
initContent();
|
||||||
|
addListeners();
|
||||||
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
if (INSTANCE != null) {
|
if (INSTANCE != null) {
|
||||||
|
@ -52,27 +73,6 @@ public class StoreViewState {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final StringProperty filter = new SimpleStringProperty();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ObservableList<StoreEntryWrapper> allEntries =
|
|
||||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ObservableList<StoreCategoryWrapper> categories =
|
|
||||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private StoreSection currentTopLevelSection;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
private StoreViewState() {
|
|
||||||
initContent();
|
|
||||||
addStorageListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateContent() {
|
private void updateContent() {
|
||||||
categories.forEach(c -> c.update());
|
categories.forEach(c -> c.update());
|
||||||
allEntries.forEach(e -> e.update());
|
allEntries.forEach(e -> e.update());
|
||||||
|
@ -112,12 +112,27 @@ public class StoreViewState {
|
||||||
.orElseThrow()));
|
.orElseThrow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStorageListeners() {
|
private void addListeners() {
|
||||||
|
if (AppPrefs.get() != null) {
|
||||||
|
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
synchronized (this) {
|
||||||
|
var l = new ArrayList<>(allEntries);
|
||||||
|
allEntries.clear();
|
||||||
|
allEntries.setAll(l);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Watch out for synchronizing all calls to the entries and categories list!
|
// Watch out for synchronizing all calls to the entries and categories list!
|
||||||
DataStorage.get().addListener(new StorageListener() {
|
DataStorage.get().addListener(new StorageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onStoreAdd(DataStoreEntry... entry) {
|
public void onStoreAdd(DataStoreEntry... entry) {
|
||||||
var l = Arrays.stream(entry).map(StoreEntryWrapper::new).peek(storeEntryWrapper -> storeEntryWrapper.update()).toList();
|
var l = Arrays.stream(entry)
|
||||||
|
.map(StoreEntryWrapper::new)
|
||||||
|
.peek(storeEntryWrapper -> storeEntryWrapper.update())
|
||||||
|
.toList();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
// Don't update anything if we have already reset
|
// Don't update anything if we have already reset
|
||||||
if (INSTANCE == null) {
|
if (INSTANCE == null) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.Main;
|
|
||||||
import io.xpipe.app.comp.AppLayoutComp;
|
import io.xpipe.app.comp.AppLayoutComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
@ -12,8 +11,8 @@ import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -27,26 +26,13 @@ public class App extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
public void start(Stage primaryStage) {
|
public void start(Stage primaryStage) {
|
||||||
TrackEvent.info("Application launched");
|
TrackEvent.info("Application launched");
|
||||||
APP = this;
|
APP = this;
|
||||||
stage = primaryStage;
|
stage = primaryStage;
|
||||||
stage.opacityProperty().bind(AppPrefs.get().windowOpacity());
|
stage.opacityProperty().bind(AppPrefs.get().windowOpacity());
|
||||||
|
|
||||||
// Set dock icon explicitly on mac
|
|
||||||
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
|
|
||||||
if (OsType.getLocal().equals(OsType.MACOS) && AppProperties.get().isDeveloperMode() && AppLogs.get().isWriteToSysout()) {
|
|
||||||
try {
|
|
||||||
var iconUrl = Main.class.getResourceAsStream("resources/img/logo/logo_macos_128x128.png");
|
|
||||||
if (iconUrl != null) {
|
|
||||||
var awtIcon = ImageIO.read(iconUrl);
|
|
||||||
Taskbar.getTaskbar().setIconImage(awtIcon);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
Desktop.getDesktop().setPreferencesHandler(e -> {
|
Desktop.getDesktop().setPreferencesHandler(e -> {
|
||||||
AppLayoutModel.get().selectSettings();
|
AppLayoutModel.get().selectSettings();
|
||||||
|
@ -56,7 +42,8 @@ public class App extends Application {
|
||||||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
try {
|
try {
|
||||||
Toolkit xToolkit = Toolkit.getDefaultToolkit();
|
Toolkit xToolkit = Toolkit.getDefaultToolkit();
|
||||||
java.lang.reflect.Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
|
java.lang.reflect.Field awtAppClassNameField =
|
||||||
|
xToolkit.getClass().getDeclaredField("awtAppClassName");
|
||||||
awtAppClassNameField.setAccessible(true);
|
awtAppClassNameField.setAccessible(true);
|
||||||
awtAppClassNameField.set(xToolkit, "XPipe");
|
awtAppClassNameField.set(xToolkit, "XPipe");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -103,10 +90,7 @@ public class App extends Application {
|
||||||
|
|
||||||
public void focus() {
|
public void focus() {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
stage.setAlwaysOnTop(true);
|
|
||||||
stage.setAlwaysOnTop(false);
|
|
||||||
stage.requestFocus();
|
stage.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ public class AppBundledFonts {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.setProperty("prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
|
System.setProperty(
|
||||||
|
"prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
|
||||||
System.setProperty("prism.embeddedfonts", "true");
|
System.setProperty("prism.embeddedfonts", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ public class AppDebugModeNotice {
|
||||||
}
|
}
|
||||||
|
|
||||||
var out = AppLogs.get().getOriginalSysOut();
|
var out = AppLogs.get().getOriginalSysOut();
|
||||||
var msg = """
|
var msg =
|
||||||
|
"""
|
||||||
|
|
||||||
****************************************
|
****************************************
|
||||||
* You are running XPipe in debug mode! *
|
* You are running XPipe in debug mode! *
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.exchange.MessageExchangeImpls;
|
import io.xpipe.app.exchange.MessageExchangeImpls;
|
||||||
import io.xpipe.app.ext.ExtensionException;
|
import io.xpipe.app.ext.ExtensionException;
|
||||||
import io.xpipe.app.ext.ModuleInstall;
|
|
||||||
import io.xpipe.app.ext.XPipeServiceProviders;
|
import io.xpipe.app.ext.XPipeServiceProviders;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
@ -29,6 +28,7 @@ public class AppExtensionManager {
|
||||||
private final List<ModuleLayer> leafModuleLayers = new ArrayList<>();
|
private final List<ModuleLayer> leafModuleLayers = new ArrayList<>();
|
||||||
private final List<Path> extensionBaseDirectories = new ArrayList<>();
|
private final List<Path> extensionBaseDirectories = new ArrayList<>();
|
||||||
private ModuleLayer baseLayer = ModuleLayer.boot();
|
private ModuleLayer baseLayer = ModuleLayer.boot();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private ModuleLayer extendedLayer;
|
private ModuleLayer extendedLayer;
|
||||||
|
|
||||||
|
@ -52,11 +52,20 @@ public class AppExtensionManager {
|
||||||
XPipeServiceProviders.load(INSTANCE.extendedLayer);
|
XPipeServiceProviders.load(INSTANCE.extendedLayer);
|
||||||
MessageExchangeImpls.loadAll();
|
MessageExchangeImpls.loadAll();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new ExtensionException("Service provider initialization failed. Is the installation data corrupt?", t);
|
throw new ExtensionException(
|
||||||
|
"Service provider initialization failed. Is the installation data corrupt?", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
INSTANCE = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppExtensionManager getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
private void loadBaseExtension() {
|
private void loadBaseExtension() {
|
||||||
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
|
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
|
||||||
if (baseModule.isEmpty()) {
|
if (baseModule.isEmpty()) {
|
||||||
|
@ -95,14 +104,6 @@ public class AppExtensionManager {
|
||||||
extensionBaseDirectories.add(productionRoot);
|
extensionBaseDirectories.add(productionRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reset() {
|
|
||||||
INSTANCE = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AppExtensionManager getInstance() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Module> getContentModules() {
|
public Set<Module> getContentModules() {
|
||||||
return Stream.concat(
|
return Stream.concat(
|
||||||
Stream.of(ModuleLayer.boot().findModule("io.xpipe.app").orElseThrow()),
|
Stream.of(ModuleLayer.boot().findModule("io.xpipe.app").orElseThrow()),
|
||||||
|
@ -110,87 +111,28 @@ public class AppExtensionManager {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInstalled(ModuleInstall install) {
|
|
||||||
var target =
|
|
||||||
AppExtensionManager.getInstance().getGeneratedModulesDirectory(install.getModule(), install.getId());
|
|
||||||
return Files.exists(target) && Files.isRegularFile(target.resolve("finished"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void installIfNeeded(ModuleInstall install) throws Exception {
|
|
||||||
var target =
|
|
||||||
AppExtensionManager.getInstance().getGeneratedModulesDirectory(install.getModule(), install.getId());
|
|
||||||
if (Files.exists(target) && Files.isRegularFile(target.resolve("finished"))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Files.createDirectories(target);
|
|
||||||
install.installInternal(target);
|
|
||||||
Files.createFile(target.resolve("finished"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getGeneratedModulesDirectory(String module, String ext) {
|
|
||||||
var base = AppProperties.get()
|
|
||||||
.getDataDir()
|
|
||||||
.resolve("generated_extensions")
|
|
||||||
.resolve(AppProperties.get().getVersion())
|
|
||||||
.resolve(module);
|
|
||||||
return ext != null ? base.resolve(ext) : base;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAllExtensions() {
|
private void loadAllExtensions() {
|
||||||
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
for (var ext : List.of("jdbc", "proc", "uacc")) {
|
||||||
loadExtensionRootDirectory(extensionBaseDirectory);
|
var extension = findAndParseExtension(ext, baseLayer)
|
||||||
|
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
|
||||||
|
loadedExtensions.add(extension);
|
||||||
|
leafModuleLayers.add(extension.getModule().getLayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leafModuleLayers.size() > 0) {
|
|
||||||
var scl = ClassLoader.getSystemClassLoader();
|
var scl = ClassLoader.getSystemClassLoader();
|
||||||
var cfs = leafModuleLayers.stream().map(ModuleLayer::configuration).toList();
|
var cfs = leafModuleLayers.stream().map(ModuleLayer::configuration).toList();
|
||||||
var finder = ModuleFinder.ofSystem();
|
var finder = ModuleFinder.ofSystem();
|
||||||
var cf = Configuration.resolve(finder, cfs, finder, List.of());
|
var cf = Configuration.resolve(finder, cfs, finder, List.of());
|
||||||
extendedLayer = ModuleLayer.defineModulesWithOneLoader(cf, leafModuleLayers, scl)
|
extendedLayer = ModuleLayer.defineModulesWithOneLoader(cf, leafModuleLayers, scl)
|
||||||
.layer();
|
.layer();
|
||||||
} else {
|
|
||||||
extendedLayer = baseLayer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadExtensionRootDirectory(Path dir) {
|
|
||||||
if (!Files.exists(dir)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order results as on unix systems the file list order is not deterministic
|
|
||||||
try (var s = Files.list(dir).sorted(Comparator.comparing(path -> path.toString()))) {
|
|
||||||
s.forEach(sub -> {
|
|
||||||
if (Files.isDirectory(sub)) {
|
|
||||||
// TODO: Better detection for x modules
|
|
||||||
if (sub.toString().endsWith("x")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var extension = parseExtensionDirectory(sub, baseLayer);
|
|
||||||
if (extension.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedExtensions.add(extension.get());
|
|
||||||
var xModule = findAndParseExtension(
|
|
||||||
extension.get().getId() + "x",
|
|
||||||
extension.get().getModule().getLayer());
|
|
||||||
if (xModule.isPresent()) {
|
|
||||||
loadedExtensions.add(xModule.get());
|
|
||||||
leafModuleLayers.add(xModule.get().getModule().getLayer());
|
|
||||||
} else {
|
|
||||||
leafModuleLayers.add(extension.get().getModule().getLayer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Extension> findAndParseExtension(String name, ModuleLayer parent) {
|
private Optional<Extension> findAndParseExtension(String name, ModuleLayer parent) {
|
||||||
|
var inModulePath = ModuleLayer.boot().findModule("io.xpipe.ext." + name);
|
||||||
|
if (inModulePath.isPresent()) {
|
||||||
|
return Optional.of(new Extension(null, inModulePath.get().getName(), name, inModulePath.get(), 0));
|
||||||
|
}
|
||||||
|
|
||||||
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
||||||
var found = parseExtensionDirectory(extensionBaseDirectory.resolve(name), parent);
|
var found = parseExtensionDirectory(extensionBaseDirectory.resolve(name), parent);
|
||||||
if (found.isPresent()) {
|
if (found.isPresent()) {
|
||||||
|
@ -206,7 +148,7 @@ public class AppExtensionManager {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedExtensions.stream().anyMatch(extension -> extension.dir.equals(dir))
|
if (loadedExtensions.stream().anyMatch(extension -> dir.equals(extension.dir))
|
||||||
|| loadedExtensions.stream()
|
|| loadedExtensions.stream()
|
||||||
.anyMatch(extension ->
|
.anyMatch(extension ->
|
||||||
extension.id.equals(dir.getFileName().toString()))) {
|
extension.id.equals(dir.getFileName().toString()))) {
|
||||||
|
|
|
@ -107,6 +107,7 @@ public class AppFileWatcher {
|
||||||
|
|
||||||
private class WatchedDirectory {
|
private class WatchedDirectory {
|
||||||
private final BiConsumer<Path, WatchEvent.Kind<Path>> listener;
|
private final BiConsumer<Path, WatchEvent.Kind<Path>> listener;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Path baseDir;
|
private final Path baseDir;
|
||||||
|
|
||||||
|
@ -114,9 +115,7 @@ public class AppFileWatcher {
|
||||||
this.baseDir = dir;
|
this.baseDir = dir;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
createRecursiveWatchers(dir);
|
createRecursiveWatchers(dir);
|
||||||
TrackEvent.withTrace("watcher", "Added watched directory")
|
TrackEvent.withTrace("Added watched directory").tag("location", dir).handle();
|
||||||
.tag("location", dir)
|
|
||||||
.handle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRecursiveWatchers(Path dir) {
|
private void createRecursiveWatchers(Path dir) {
|
||||||
|
@ -177,13 +176,12 @@ public class AppFileWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle event
|
// Handle event
|
||||||
TrackEvent.withTrace("watcher", "Watch event")
|
TrackEvent.withTrace("Watch event")
|
||||||
.tag("baseDir", baseDir)
|
.tag("baseDir", baseDir)
|
||||||
.tag("file", baseDir.relativize(file))
|
.tag("file", baseDir.relativize(file))
|
||||||
.tag("kind", event.kind().name())
|
.tag("kind", event.kind().name())
|
||||||
.handle();
|
.handle();
|
||||||
listener.accept(file, ev.kind());
|
listener.accept(file, ev.kind());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,8 @@ public class AppFont {
|
||||||
try (var in = Files.newInputStream(file)) {
|
try (var in = Files.newInputStream(file)) {
|
||||||
Font.loadFont(in, OsType.getLocal() == OsType.LINUX ? 11 : 12);
|
Font.loadFont(in, OsType.getLocal() == OsType.LINUX ? 11 : 12);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// Font loading can fail in rare cases. This is however not important, so we can just ignore it
|
// Font loading can fail in rare cases. This is however not important, so we can just ignore
|
||||||
|
// it
|
||||||
}
|
}
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXCheckBox;
|
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
@ -52,7 +51,7 @@ public class AppGreetings {
|
||||||
|
|
||||||
public static void showIfNeeded() {
|
public static void showIfNeeded() {
|
||||||
boolean set = AppCache.get("legalAccepted", Boolean.class, () -> false);
|
boolean set = AppCache.get("legalAccepted", Boolean.class, () -> false);
|
||||||
if (set || !AppState.get().isInitialLaunch()) {
|
if (set || AppProperties.get().isDevelopmentEnvironment()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var read = new SimpleBooleanProperty();
|
var read = new SimpleBooleanProperty();
|
||||||
|
@ -72,20 +71,21 @@ public class AppGreetings {
|
||||||
});
|
});
|
||||||
|
|
||||||
var acceptanceBox = Comp.of(() -> {
|
var acceptanceBox = Comp.of(() -> {
|
||||||
var cb = new JFXCheckBox();
|
var cb = new CheckBox();
|
||||||
cb.selectedProperty().bindBidirectional(accepted);
|
cb.selectedProperty().bindBidirectional(accepted);
|
||||||
|
|
||||||
var label = new Label(AppI18n.get("legalAccept"));
|
var label = new Label(AppI18n.get("legalAccept"));
|
||||||
label.setGraphic(cb);
|
label.setGraphic(cb);
|
||||||
AppFont.medium(label);
|
AppFont.medium(label);
|
||||||
label.setPadding(new Insets(40, 0, 10, 0));
|
label.setPadding(new Insets(20, 0, 10, 0));
|
||||||
label.setOnMouseClicked(event -> accepted.set(!accepted.get()));
|
label.setOnMouseClicked(event -> accepted.set(!accepted.get()));
|
||||||
|
label.setGraphicTextGap(10);
|
||||||
return label;
|
return label;
|
||||||
})
|
})
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
var layout = new BorderPane();
|
var layout = new BorderPane();
|
||||||
layout.getStyleClass().add("window-content");
|
layout.setPadding(new Insets(20));
|
||||||
layout.setCenter(accordion);
|
layout.setCenter(accordion);
|
||||||
layout.setBottom(acceptanceBox);
|
layout.setBottom(acceptanceBox);
|
||||||
layout.setPrefWidth(700);
|
layout.setPrefWidth(700);
|
||||||
|
|
|
@ -37,10 +37,10 @@ import java.util.regex.Pattern;
|
||||||
public class AppI18n {
|
public class AppI18n {
|
||||||
|
|
||||||
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
||||||
|
private static final AppI18n INSTANCE = new AppI18n();
|
||||||
private Map<String, String> translations;
|
private Map<String, String> translations;
|
||||||
private Map<String, String> markdownDocumentations;
|
private Map<String, String> markdownDocumentations;
|
||||||
private PrettyTime prettyTime;
|
private PrettyTime prettyTime;
|
||||||
private static final AppI18n INSTANCE = new AppI18n();
|
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
var i = INSTANCE;
|
var i = INSTANCE;
|
||||||
|
@ -51,7 +51,7 @@ public class AppI18n {
|
||||||
i.load();
|
i.load();
|
||||||
|
|
||||||
if (AppPrefs.get() != null) {
|
if (AppPrefs.get() != null) {
|
||||||
AppPrefs.get().language.addListener((c, o, n) -> {
|
AppPrefs.get().language().addListener((c, o, n) -> {
|
||||||
i.clear();
|
i.clear();
|
||||||
i.load();
|
i.load();
|
||||||
});
|
});
|
||||||
|
@ -98,8 +98,11 @@ public class AppI18n {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
return getInstance().prettyTime.formatDuration(
|
return getInstance()
|
||||||
getInstance().prettyTime.approximateDuration(Instant.now().plus(duration.getValue())));
|
.prettyTime
|
||||||
|
.formatDuration(getInstance()
|
||||||
|
.prettyTime
|
||||||
|
.approximateDuration(Instant.now().plus(duration.getValue())));
|
||||||
},
|
},
|
||||||
duration);
|
duration);
|
||||||
}
|
}
|
||||||
|
@ -136,20 +139,6 @@ public class AppI18n {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear() {
|
|
||||||
translations.clear();
|
|
||||||
prettyTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
|
||||||
public static class CallingClass extends SecurityManager {
|
|
||||||
public static final CallingClass INSTANCE = new CallingClass();
|
|
||||||
|
|
||||||
public Class<?>[] getCallingClasses() {
|
|
||||||
return getClassContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private static String getCallerModuleName() {
|
private static String getCallerModuleName() {
|
||||||
var callers = CallingClass.INSTANCE.getCallingClasses();
|
var callers = CallingClass.INSTANCE.getCallingClasses();
|
||||||
|
@ -161,6 +150,7 @@ public class AppI18n {
|
||||||
|| caller.equals(FancyTooltipAugment.class)
|
|| caller.equals(FancyTooltipAugment.class)
|
||||||
|| caller.equals(PrefsChoiceValue.class)
|
|| caller.equals(PrefsChoiceValue.class)
|
||||||
|| caller.equals(Translatable.class)
|
|| caller.equals(Translatable.class)
|
||||||
|
|| caller.equals(AppWindowHelper.class)
|
||||||
|| caller.equals(OptionsBuilder.class)) {
|
|| caller.equals(OptionsBuilder.class)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -170,6 +160,11 @@ public class AppI18n {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
translations.clear();
|
||||||
|
prettyTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
public String getKey(String s) {
|
public String getKey(String s) {
|
||||||
var key = s;
|
var key = s;
|
||||||
if (!s.contains(".")) {
|
if (!s.contains(".")) {
|
||||||
|
@ -210,7 +205,7 @@ public class AppI18n {
|
||||||
|
|
||||||
private boolean matchesLocale(Path f) {
|
private boolean matchesLocale(Path f) {
|
||||||
var l = AppPrefs.get() != null
|
var l = AppPrefs.get() != null
|
||||||
? AppPrefs.get().language.getValue().getLocale()
|
? AppPrefs.get().language().getValue().getLocale()
|
||||||
: SupportedLocale.ENGLISH.getLocale();
|
: SupportedLocale.ENGLISH.getLocale();
|
||||||
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
||||||
var ending = "_" + l.toLanguageTag();
|
var ending = "_" + l.toLanguageTag();
|
||||||
|
@ -219,7 +214,8 @@ public class AppI18n {
|
||||||
|
|
||||||
public String getMarkdownDocumentation(String name) {
|
public String getMarkdownDocumentation(String name) {
|
||||||
if (!markdownDocumentations.containsKey(name)) {
|
if (!markdownDocumentations.containsKey(name)) {
|
||||||
TrackEvent.withWarn("Markdown documentation for key " + name + " not found").handle();
|
TrackEvent.withWarn("Markdown documentation for key " + name + " not found")
|
||||||
|
.handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
return markdownDocumentations.getOrDefault(name, "");
|
return markdownDocumentations.getOrDefault(name, "");
|
||||||
|
@ -311,7 +307,16 @@ public class AppI18n {
|
||||||
|
|
||||||
this.prettyTime = new PrettyTime(
|
this.prettyTime = new PrettyTime(
|
||||||
AppPrefs.get() != null
|
AppPrefs.get() != null
|
||||||
? AppPrefs.get().language.getValue().getLocale()
|
? AppPrefs.get().language().getValue().getLocale()
|
||||||
: SupportedLocale.ENGLISH.getLocale());
|
: SupportedLocale.ENGLISH.getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
public static class CallingClass extends SecurityManager {
|
||||||
|
public static final CallingClass INSTANCE = new CallingClass();
|
||||||
|
|
||||||
|
public Class<?>[] getCallingClasses() {
|
||||||
|
return getClassContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import io.xpipe.app.browser.BrowserModel;
|
||||||
import io.xpipe.app.comp.DeveloperTabComp;
|
import io.xpipe.app.comp.DeveloperTabComp;
|
||||||
import io.xpipe.app.comp.store.StoreLayoutComp;
|
import io.xpipe.app.comp.store.StoreLayoutComp;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.prefs.PrefsComp;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.prefs.AppPrefsComp;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
@ -18,20 +19,26 @@ import lombok.extern.jackson.Jacksonized;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class AppLayoutModel {
|
public class AppLayoutModel {
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@Jacksonized
|
|
||||||
public static class SavedState {
|
|
||||||
|
|
||||||
double sidebarWidth;
|
|
||||||
double browserConnectionsWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AppLayoutModel INSTANCE;
|
private static AppLayoutModel INSTANCE;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final SavedState savedState;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<Entry> entries;
|
||||||
|
|
||||||
|
private final Property<Entry> selected;
|
||||||
|
private final ObservableValue<Entry> selectedWrapper;
|
||||||
|
|
||||||
|
public AppLayoutModel(SavedState savedState) {
|
||||||
|
this.savedState = savedState;
|
||||||
|
this.entries = createEntryList();
|
||||||
|
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
||||||
|
this.selectedWrapper = PlatformThread.sync(selected);
|
||||||
|
}
|
||||||
|
|
||||||
public static AppLayoutModel get() {
|
public static AppLayoutModel get() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
@ -46,19 +53,16 @@ public class AppLayoutModel {
|
||||||
INSTANCE = null;
|
INSTANCE = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
public Property<Entry> getSelectedInternal() {
|
||||||
private final SavedState savedState;
|
return selected;
|
||||||
private final List<Entry> entries;
|
}
|
||||||
private final Property<Entry> selected;
|
|
||||||
|
|
||||||
public AppLayoutModel(SavedState savedState) {
|
public ObservableValue<Entry> getSelected() {
|
||||||
this.savedState = savedState;
|
return selectedWrapper;
|
||||||
this.entries = createEntryList();
|
|
||||||
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectBrowser() {
|
public void selectBrowser() {
|
||||||
selected.setValue(entries.get(0));
|
selected.setValue(entries.getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectSettings() {
|
public void selectSettings() {
|
||||||
|
@ -75,17 +79,14 @@ public class AppLayoutModel {
|
||||||
|
|
||||||
private List<Entry> createEntryList() {
|
private List<Entry> createEntryList() {
|
||||||
var l = new ArrayList<>(List.of(
|
var l = new ArrayList<>(List.of(
|
||||||
new Entry(
|
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
|
||||||
AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
|
|
||||||
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
||||||
new Entry(
|
new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp())));
|
||||||
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this))));
|
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
||||||
// StorageLayoutComp()),
|
// StorageLayoutComp()),
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
|
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
|
||||||
if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) {
|
if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) {
|
||||||
l.add(new Entry(
|
l.add(new Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
||||||
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.add(new Entry(
|
l.add(new Entry(
|
||||||
|
@ -96,5 +97,14 @@ public class AppLayoutModel {
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
public static class SavedState {
|
||||||
|
|
||||||
|
double sidebarWidth;
|
||||||
|
double browserConnectionsWidth;
|
||||||
|
}
|
||||||
|
|
||||||
public record Entry(ObservableValue<String> name, String icon, Comp<?> comp) {}
|
public record Entry(ObservableValue<String> name, String icon, Comp<?> comp) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.ChronoField;
|
import java.time.temporal.ChronoField;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -32,23 +31,26 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class AppLogs {
|
public class AppLogs {
|
||||||
|
|
||||||
public static final List<String> DEFAULT_LEVELS = List.of("error", "warn", "info", "debug", "trace");
|
public static final List<String> LOG_LEVELS = List.of("error", "warn", "info", "debug", "trace");
|
||||||
private static final String WRITE_SYSOUT_PROP = "io.xpipe.app.writeSysOut";
|
private static final String WRITE_SYSOUT_PROP = "io.xpipe.app.writeSysOut";
|
||||||
private static final String WRITE_LOGS_PROP = "io.xpipe.app.writeLogs";
|
private static final String WRITE_LOGS_PROP = "io.xpipe.app.writeLogs";
|
||||||
private static final String DEBUG_PLATFORM_PROP = "io.xpipe.app.debugPlatform";
|
private static final String DEBUG_PLATFORM_PROP = "io.xpipe.app.debugPlatform";
|
||||||
private static final String LOG_LEVEL_PROP = "io.xpipe.app.logLevel";
|
private static final String LOG_LEVEL_PROP = "io.xpipe.app.logLevel";
|
||||||
private static final String DEFAULT_LOG_LEVEL = "info";
|
private static final String DEFAULT_LOG_LEVEL = "info";
|
||||||
|
|
||||||
private static final DateTimeFormatter FORMATTER =
|
private static final DateTimeFormatter NAME_FORMATTER =
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").withZone(ZoneId.systemDefault());
|
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").withZone(ZoneId.systemDefault());
|
||||||
private static final DateTimeFormatter MESSAGE_FORMATTER =
|
private static final DateTimeFormatter MESSAGE_FORMATTER =
|
||||||
DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(ZoneId.systemDefault());
|
DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
private static AppLogs INSTANCE;
|
private static AppLogs INSTANCE;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final PrintStream originalSysOut;
|
private final PrintStream originalSysOut;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final PrintStream originalSysErr;
|
private final PrintStream originalSysErr;
|
||||||
|
|
||||||
private final Path logDir;
|
private final Path logDir;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -60,16 +62,15 @@ public class AppLogs {
|
||||||
@Getter
|
@Getter
|
||||||
private final String logLevel;
|
private final String logLevel;
|
||||||
|
|
||||||
private final PrintStream outStream;
|
private final PrintStream outFileStream;
|
||||||
private final Map<String, PrintStream> categoryWriters;
|
|
||||||
|
|
||||||
public AppLogs(Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel) {
|
public AppLogs(
|
||||||
|
Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel, PrintStream outFileStream) {
|
||||||
this.logDir = logDir;
|
this.logDir = logDir;
|
||||||
this.writeToSysout = writeToSysout;
|
this.writeToSysout = writeToSysout;
|
||||||
this.writeToFile = writeToFile;
|
this.writeToFile = writeToFile;
|
||||||
this.logLevel = logLevel;
|
this.logLevel = logLevel;
|
||||||
this.outStream = System.out;
|
this.outFileStream = outFileStream;
|
||||||
this.categoryWriters = new HashMap<>();
|
|
||||||
|
|
||||||
this.originalSysOut = System.out;
|
this.originalSysOut = System.out;
|
||||||
this.originalSysErr = System.err;
|
this.originalSysErr = System.err;
|
||||||
|
@ -96,20 +97,34 @@ public class AppLogs {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
if (INSTANCE != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var logDir = AppProperties.get().getDataDir().resolve("logs");
|
var logDir = AppProperties.get().getDataDir().resolve("logs");
|
||||||
|
|
||||||
|
// Regularly clean logs dir
|
||||||
if (XPipeSession.get().isNewBuildSession() && Files.exists(logDir)) {
|
if (XPipeSession.get().isNewBuildSession() && Files.exists(logDir)) {
|
||||||
try {
|
try {
|
||||||
FileUtils.cleanDirectory(logDir.toFile());
|
List<Path> all;
|
||||||
|
try (var s = Files.list(logDir)) {
|
||||||
|
all = s.toList();
|
||||||
|
}
|
||||||
|
for (Path path : all) {
|
||||||
|
// Don't delete installer logs
|
||||||
|
if (path.getFileName().toString().contains("installer")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtils.forceDelete(path.toFile());
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldLogToFile = shouldWriteLogs();
|
|
||||||
|
|
||||||
var now = Instant.now();
|
var now = Instant.now();
|
||||||
var name = FORMATTER.format(now);
|
var name = NAME_FORMATTER.format(now);
|
||||||
Path usedLogsDir = logDir.resolve(name);
|
Path usedLogsDir = logDir.resolve(name);
|
||||||
|
|
||||||
// When two instances are being launched within the same second, add milliseconds
|
// When two instances are being launched within the same second, add milliseconds
|
||||||
|
@ -117,23 +132,34 @@ public class AppLogs {
|
||||||
usedLogsDir = logDir.resolve(name + "_" + now.get(ChronoField.MILLI_OF_SECOND));
|
usedLogsDir = logDir.resolve(name + "_" + now.get(ChronoField.MILLI_OF_SECOND));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrintStream outFileStream = null;
|
||||||
|
var shouldLogToFile = shouldWriteLogs();
|
||||||
if (shouldLogToFile) {
|
if (shouldLogToFile) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(usedLogsDir);
|
Files.createDirectories(usedLogsDir);
|
||||||
|
var file = usedLogsDir.resolve("xpipe.log");
|
||||||
|
var fos = new FileOutputStream(file.toFile(), true);
|
||||||
|
var buf = new BufferedOutputStream(fos);
|
||||||
|
outFileStream = new PrintStream(buf, false);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).build().handle();
|
ErrorEvent.fromThrowable(ex).build().handle();
|
||||||
shouldLogToFile = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldLogToSysout = shouldWriteSysout();
|
var shouldLogToSysout = shouldWriteSysout();
|
||||||
|
|
||||||
|
if (shouldLogToFile && outFileStream == null) {
|
||||||
|
TrackEvent.info("Log file initialization failed. Writing to standard out");
|
||||||
|
shouldLogToSysout = true;
|
||||||
|
shouldLogToFile = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldLogToFile && !shouldLogToSysout) {
|
if (shouldLogToFile && !shouldLogToSysout) {
|
||||||
TrackEvent.info("Writing log output to " + usedLogsDir + " from now on");
|
TrackEvent.info("Writing log output to " + usedLogsDir + " from now on");
|
||||||
}
|
}
|
||||||
|
|
||||||
var level = determineLogLevel();
|
var level = determineLogLevel();
|
||||||
INSTANCE = new AppLogs(usedLogsDir, shouldLogToSysout, shouldLogToFile, level);
|
INSTANCE = new AppLogs(usedLogsDir, shouldLogToSysout, shouldLogToFile, level, outFileStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void teardown() {
|
public static void teardown() {
|
||||||
|
@ -149,45 +175,19 @@ public class AppLogs {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String determineLogLevel() {
|
||||||
|
if (System.getProperty(LOG_LEVEL_PROP) != null) {
|
||||||
|
String p = System.getProperty(LOG_LEVEL_PROP);
|
||||||
|
return LOG_LEVELS.contains(p) ? p : "trace";
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_LOG_LEVEL;
|
||||||
|
}
|
||||||
|
|
||||||
private void close() {
|
private void close() {
|
||||||
outStream.close();
|
if (outFileStream != null) {
|
||||||
categoryWriters.forEach((k, s) -> {
|
outFileStream.close();
|
||||||
s.close();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCategory(TrackEvent event) {
|
|
||||||
if (event.getCategory() != null) {
|
|
||||||
return event.getCategory();
|
|
||||||
}
|
|
||||||
|
|
||||||
return "misc";
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized PrintStream getLogStream(TrackEvent e) {
|
|
||||||
return categoryWriters.computeIfAbsent(getCategory(e), (cat) -> {
|
|
||||||
var file = logDir.resolve(cat + ".log");
|
|
||||||
FileOutputStream fos;
|
|
||||||
try {
|
|
||||||
fos = new FileOutputStream(file.toFile(), true);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return outStream;
|
|
||||||
}
|
|
||||||
return new PrintStream(fos, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized PrintStream getCatchAllLogStream() {
|
|
||||||
return categoryWriters.computeIfAbsent("xpipe", (cat) -> {
|
|
||||||
var file = logDir.resolve(cat + ".log");
|
|
||||||
FileOutputStream fos;
|
|
||||||
try {
|
|
||||||
fos = new FileOutputStream(file.toFile(), true);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return outStream;
|
|
||||||
}
|
|
||||||
return new PrintStream(fos, false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldDebugPlatform() {
|
private boolean shouldDebugPlatform() {
|
||||||
|
@ -210,12 +210,7 @@ public class AppLogs {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.builder()
|
TrackEvent.builder().type("info").message(line).build().handle();
|
||||||
.type("info")
|
|
||||||
.category("sysout")
|
|
||||||
.message(line)
|
|
||||||
.build()
|
|
||||||
.handle();
|
|
||||||
baos.reset();
|
baos.reset();
|
||||||
} else {
|
} else {
|
||||||
baos.write(b);
|
baos.write(b);
|
||||||
|
@ -245,15 +240,6 @@ public class AppLogs {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String determineLogLevel() {
|
|
||||||
if (System.getProperty(LOG_LEVEL_PROP) != null) {
|
|
||||||
String p = System.getProperty(LOG_LEVEL_PROP);
|
|
||||||
return DEFAULT_LEVELS.contains(p) ? p : "info";
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULT_LOG_LEVEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logException(String description, Throwable e) {
|
public void logException(String description, Throwable e) {
|
||||||
var deob = Deobfuscator.deobfuscateToString(e);
|
var deob = Deobfuscator.deobfuscateToString(e);
|
||||||
var event = TrackEvent.builder()
|
var event = TrackEvent.builder()
|
||||||
|
@ -264,9 +250,9 @@ public class AppLogs {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void logEvent(TrackEvent event) {
|
public synchronized void logEvent(TrackEvent event) {
|
||||||
var li = DEFAULT_LEVELS.indexOf(determineLogLevel());
|
var li = LOG_LEVELS.indexOf(determineLogLevel());
|
||||||
int i = li == -1 ? 5 : li;
|
int i = li == -1 ? 5 : li;
|
||||||
int current = DEFAULT_LEVELS.indexOf(event.getType());
|
int current = LOG_LEVELS.indexOf(event.getType());
|
||||||
if (current <= i) {
|
if (current <= i) {
|
||||||
if (writeToSysout) {
|
if (writeToSysout) {
|
||||||
logSysOut(event);
|
logSysOut(event);
|
||||||
|
@ -281,12 +267,9 @@ public class AppLogs {
|
||||||
var time = MESSAGE_FORMATTER.format(event.getInstant());
|
var time = MESSAGE_FORMATTER.format(event.getInstant());
|
||||||
var string =
|
var string =
|
||||||
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
||||||
if (event.getCategory() != null) {
|
|
||||||
string.append("[").append(event.getCategory()).append("] ");
|
|
||||||
}
|
|
||||||
string.append(event);
|
string.append(event);
|
||||||
var toLog = string.toString();
|
var toLog = string.toString();
|
||||||
outStream.println(toLog);
|
this.originalSysOut.println(toLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logToFile(TrackEvent event) {
|
private void logToFile(TrackEvent event) {
|
||||||
|
@ -295,8 +278,7 @@ public class AppLogs {
|
||||||
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
||||||
string.append(event);
|
string.append(event);
|
||||||
var toLog = string.toString();
|
var toLog = string.toString();
|
||||||
getLogStream(event).println(toLog);
|
outFileStream.println(toLog);
|
||||||
getCatchAllLogStream().println(toLog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLogLevels() {
|
private void setLogLevels() {
|
||||||
|
@ -312,10 +294,6 @@ public class AppLogs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getLogsDirectory() {
|
|
||||||
return logDir.getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getSessionLogsDirectory() {
|
public Path getSessionLogsDirectory() {
|
||||||
return logDir;
|
return logDir;
|
||||||
}
|
}
|
||||||
|
@ -339,7 +317,7 @@ public class AppLogs {
|
||||||
normalizedName = name;
|
normalizedName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return loggers.computeIfAbsent(normalizedName, Slf4jLogger::new);
|
return loggers.computeIfAbsent(normalizedName, s -> new Slf4jLogger());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -369,12 +347,6 @@ public class AppLogs {
|
||||||
|
|
||||||
public static final class Slf4jLogger extends AbstractLogger {
|
public static final class Slf4jLogger extends AbstractLogger {
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public Slf4jLogger(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getFullyQualifiedCallerName() {
|
protected String getFullyQualifiedCallerName() {
|
||||||
return "logger";
|
return "logger";
|
||||||
|
@ -390,7 +362,6 @@ public class AppLogs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TrackEvent.builder()
|
TrackEvent.builder()
|
||||||
.category(name)
|
|
||||||
.type(level.toString().toLowerCase())
|
.type(level.toString().toLowerCase())
|
||||||
.message(msg)
|
.message(msg)
|
||||||
.build()
|
.build()
|
||||||
|
@ -399,62 +370,62 @@ public class AppLogs {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTraceEnabled() {
|
public boolean isTraceEnabled() {
|
||||||
return DEFAULT_LEVELS.indexOf("trace")
|
return LOG_LEVELS.indexOf("trace")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTraceEnabled(Marker marker) {
|
public boolean isTraceEnabled(Marker marker) {
|
||||||
return DEFAULT_LEVELS.indexOf("trace")
|
return LOG_LEVELS.indexOf("trace")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDebugEnabled() {
|
public boolean isDebugEnabled() {
|
||||||
return DEFAULT_LEVELS.indexOf("debug")
|
return LOG_LEVELS.indexOf("debug")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDebugEnabled(Marker marker) {
|
public boolean isDebugEnabled(Marker marker) {
|
||||||
return DEFAULT_LEVELS.indexOf("debug")
|
return LOG_LEVELS.indexOf("debug")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInfoEnabled() {
|
public boolean isInfoEnabled() {
|
||||||
return DEFAULT_LEVELS.indexOf("info")
|
return LOG_LEVELS.indexOf("info")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInfoEnabled(Marker marker) {
|
public boolean isInfoEnabled(Marker marker) {
|
||||||
return DEFAULT_LEVELS.indexOf("info")
|
return LOG_LEVELS.indexOf("info")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWarnEnabled() {
|
public boolean isWarnEnabled() {
|
||||||
return DEFAULT_LEVELS.indexOf("warn")
|
return LOG_LEVELS.indexOf("warn")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWarnEnabled(Marker marker) {
|
public boolean isWarnEnabled(Marker marker) {
|
||||||
return DEFAULT_LEVELS.indexOf("warn")
|
return LOG_LEVELS.indexOf("warn")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isErrorEnabled() {
|
public boolean isErrorEnabled() {
|
||||||
return DEFAULT_LEVELS.indexOf("error")
|
return LOG_LEVELS.indexOf("error")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isErrorEnabled(Marker marker) {
|
public boolean isErrorEnabled(Marker marker) {
|
||||||
return DEFAULT_LEVELS.indexOf("error")
|
return LOG_LEVELS.indexOf("error")
|
||||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,6 @@ public class AppMainWindow {
|
||||||
|
|
||||||
private void logChange() {
|
private void logChange() {
|
||||||
TrackEvent.withDebug("Window resize")
|
TrackEvent.withDebug("Window resize")
|
||||||
.windowCategory()
|
|
||||||
.tag("x", stage.getX())
|
.tag("x", stage.getX())
|
||||||
.tag("y", stage.getY())
|
.tag("y", stage.getY())
|
||||||
.tag("width", stage.getWidth())
|
.tag("width", stage.getWidth())
|
||||||
|
@ -98,7 +97,6 @@ public class AppMainWindow {
|
||||||
applyState(state);
|
applyState(state);
|
||||||
|
|
||||||
TrackEvent.withDebug("Window initialized")
|
TrackEvent.withDebug("Window initialized")
|
||||||
.windowCategory()
|
|
||||||
.tag("x", stage.getX())
|
.tag("x", stage.getX())
|
||||||
.tag("y", stage.getY())
|
.tag("y", stage.getY())
|
||||||
.tag("width", stage.getWidth())
|
.tag("width", stage.getWidth())
|
||||||
|
|
|
@ -19,15 +19,20 @@ public class AppProperties {
|
||||||
private static final String EXTENSION_PATHS_PROP = "io.xpipe.app.extensions";
|
private static final String EXTENSION_PATHS_PROP = "io.xpipe.app.extensions";
|
||||||
private static AppProperties INSTANCE;
|
private static AppProperties INSTANCE;
|
||||||
boolean fullVersion;
|
boolean fullVersion;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
String version;
|
String version;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
String build;
|
String build;
|
||||||
|
|
||||||
UUID buildUuid;
|
UUID buildUuid;
|
||||||
String sentryUrl;
|
String sentryUrl;
|
||||||
String arch;
|
String arch;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
boolean image;
|
boolean image;
|
||||||
|
|
||||||
boolean staging;
|
boolean staging;
|
||||||
boolean useVirtualThreads;
|
boolean useVirtualThreads;
|
||||||
boolean debugThreads;
|
boolean debugThreads;
|
||||||
|
@ -101,6 +106,10 @@ public class AppProperties {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDevelopmentEnvironment() {
|
||||||
|
return !AppProperties.get().isImage() && AppProperties.get().isDeveloperMode();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeveloperMode() {
|
public boolean isDeveloperMode() {
|
||||||
if (AppPrefs.get() == null) {
|
if (AppPrefs.get() == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -108,5 +117,4 @@ public class AppProperties {
|
||||||
|
|
||||||
return AppPrefs.get().developerMode().getValue();
|
return AppPrefs.get().developerMode().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,10 @@ public class AppSocketServer {
|
||||||
.handle();
|
.handle();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// Not terminal!
|
// Not terminal!
|
||||||
ErrorEvent.fromThrowable(ex).description("Unable to start local socket server on port " + port).build().handle();
|
ErrorEvent.fromThrowable(ex)
|
||||||
|
.description("Unable to start local socket server on port " + port)
|
||||||
|
.build()
|
||||||
|
.handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +115,7 @@ public class AppSocketServer {
|
||||||
|
|
||||||
private boolean performExchange(Socket clientSocket, int id) throws Exception {
|
private boolean performExchange(Socket clientSocket, int id) throws Exception {
|
||||||
if (clientSocket.isClosed()) {
|
if (clientSocket.isClosed()) {
|
||||||
TrackEvent.trace("beacon", "Socket closed");
|
TrackEvent.trace("Socket closed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,14 +124,14 @@ public class AppSocketServer {
|
||||||
node = JacksonMapper.getDefault().readTree(blockIn);
|
node = JacksonMapper.getDefault().readTree(blockIn);
|
||||||
}
|
}
|
||||||
if (node.isMissingNode()) {
|
if (node.isMissingNode()) {
|
||||||
TrackEvent.trace("beacon", "Received EOF");
|
TrackEvent.trace("Received EOF");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.trace("beacon", "Received raw request: \n" + node.toPrettyString());
|
TrackEvent.trace("Received raw request: \n" + node.toPrettyString());
|
||||||
|
|
||||||
var req = parseRequest(node);
|
var req = parseRequest(node);
|
||||||
TrackEvent.trace("beacon", "Parsed request: \n" + req.toString());
|
TrackEvent.trace("Parsed request: \n" + req.toString());
|
||||||
|
|
||||||
var prov = MessageExchangeImpls.byRequest(req);
|
var prov = MessageExchangeImpls.byRequest(req);
|
||||||
if (prov.isEmpty()) {
|
if (prov.isEmpty()) {
|
||||||
|
@ -145,19 +148,19 @@ public class AppSocketServer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream sendBody() throws IOException {
|
public OutputStream sendBody() throws IOException {
|
||||||
TrackEvent.trace("beacon", "Starting writing body for #" + id);
|
TrackEvent.trace("Starting writing body for #" + id);
|
||||||
return AppSocketServer.this.sendBody(clientSocket);
|
return AppSocketServer.this.sendBody(clientSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream receiveBody() throws IOException {
|
public InputStream receiveBody() throws IOException {
|
||||||
TrackEvent.trace("beacon", "Starting to read body for #" + id);
|
TrackEvent.trace("Starting to read body for #" + id);
|
||||||
return AppSocketServer.this.receiveBody(clientSocket);
|
return AppSocketServer.this.receiveBody(clientSocket);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
req);
|
req);
|
||||||
|
|
||||||
TrackEvent.trace("beacon", "Sending response to #" + id + ": \n" + res.toString());
|
TrackEvent.trace("Sending response to #" + id + ": \n" + res.toString());
|
||||||
AppSocketServer.this.sendResponse(clientSocket, res);
|
AppSocketServer.this.sendResponse(clientSocket, res);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -170,7 +173,6 @@ public class AppSocketServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.builder()
|
TrackEvent.builder()
|
||||||
.category("beacon")
|
|
||||||
.type("trace")
|
.type("trace")
|
||||||
.message("Socket connection #" + id + " performed exchange "
|
.message("Socket connection #" + id + " performed exchange "
|
||||||
+ req.getClass().getSimpleName())
|
+ req.getClass().getSimpleName())
|
||||||
|
@ -187,7 +189,7 @@ public class AppSocketServer {
|
||||||
informationNode = JacksonMapper.getDefault().readTree(blockIn);
|
informationNode = JacksonMapper.getDefault().readTree(blockIn);
|
||||||
}
|
}
|
||||||
if (informationNode.isMissingNode()) {
|
if (informationNode.isMissingNode()) {
|
||||||
TrackEvent.trace("beacon", "Received EOF");
|
TrackEvent.trace("Received EOF");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var information =
|
var information =
|
||||||
|
@ -197,7 +199,6 @@ public class AppSocketServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.builder()
|
TrackEvent.builder()
|
||||||
.category("beacon")
|
|
||||||
.type("trace")
|
.type("trace")
|
||||||
.message("Created new socket connection #" + id)
|
.message("Created new socket connection #" + id)
|
||||||
.tag("client", information != null ? information.toDisplayString() : "Unknown")
|
.tag("client", information != null ? information.toDisplayString() : "Unknown")
|
||||||
|
@ -211,29 +212,29 @@ public class AppSocketServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TrackEvent.builder()
|
TrackEvent.builder()
|
||||||
.category("beacon")
|
|
||||||
.type("trace")
|
.type("trace")
|
||||||
.message("Socket connection #" + id + " finished successfully")
|
.message("Socket connection #" + id + " finished successfully")
|
||||||
.build()
|
.build()
|
||||||
.handle();
|
.handle();
|
||||||
|
|
||||||
} catch (ClientException ce) {
|
} catch (ClientException ce) {
|
||||||
TrackEvent.trace("beacon", "Sending client error to #" + id + ": " + ce.getMessage());
|
TrackEvent.trace("Sending client error to #" + id + ": " + ce.getMessage());
|
||||||
sendClientErrorResponse(clientSocket, ce.getMessage());
|
sendClientErrorResponse(clientSocket, ce.getMessage());
|
||||||
} catch (ServerException se) {
|
} catch (ServerException se) {
|
||||||
TrackEvent.trace("beacon", "Sending server error to #" + id + ": " + se.getMessage());
|
TrackEvent.trace("Sending server error to #" + id + ": " + se.getMessage());
|
||||||
ErrorEvent.fromThrowable(se).build().handle();
|
|
||||||
Deobfuscator.deobfuscate(se);
|
Deobfuscator.deobfuscate(se);
|
||||||
sendServerErrorResponse(clientSocket, se);
|
sendServerErrorResponse(clientSocket, se);
|
||||||
|
var toReport = se.getCause() != null ? se.getCause() : se;
|
||||||
|
ErrorEvent.fromThrowable(toReport).build().handle();
|
||||||
} catch (SocketException ex) {
|
} catch (SocketException ex) {
|
||||||
// Do not send error and omit it, as this might happen often
|
// Do not send error and omit it, as this might happen often
|
||||||
// We do not send the error as the socket connection might be broken
|
// We do not send the error as the socket connection might be broken
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
TrackEvent.trace("beacon", "Sending internal server error to #" + id + ": " + ex.getMessage());
|
TrackEvent.trace("Sending internal server error to #" + id + ": " + ex.getMessage());
|
||||||
ErrorEvent.fromThrowable(ex).build().handle();
|
|
||||||
Deobfuscator.deobfuscate(ex);
|
Deobfuscator.deobfuscate(ex);
|
||||||
sendServerErrorResponse(clientSocket, ex);
|
sendServerErrorResponse(clientSocket, ex);
|
||||||
|
ErrorEvent.fromThrowable(ex).build().handle();
|
||||||
}
|
}
|
||||||
} catch (SocketException ex) {
|
} catch (SocketException ex) {
|
||||||
// Omit it, as this might happen often
|
// Omit it, as this might happen often
|
||||||
|
@ -243,16 +244,13 @@ public class AppSocketServer {
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
TrackEvent.trace("beacon", "Closed socket #" + id);
|
TrackEvent.trace("Closed socket #" + id);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ErrorEvent.fromThrowable(e).build().handle();
|
ErrorEvent.fromThrowable(e).build().handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.builder()
|
TrackEvent.builder().type("trace").message("Socket connection #" + id + " finished unsuccessfully");
|
||||||
.category("beacon")
|
|
||||||
.type("trace")
|
|
||||||
.message("Socket connection #" + id + " finished unsuccessfully");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performExchangesAsync(Socket clientSocket) {
|
private void performExchangesAsync(Socket clientSocket) {
|
||||||
|
@ -296,7 +294,7 @@ public class AppSocketServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = writer.toString();
|
var content = writer.toString();
|
||||||
TrackEvent.trace("beacon", "Sending raw response:\n" + content);
|
TrackEvent.trace("Sending raw response:\n" + content);
|
||||||
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
|
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
|
||||||
blockOut.write(content.getBytes(StandardCharsets.UTF_8));
|
blockOut.write(content.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
@ -336,7 +334,7 @@ public class AppSocketServer {
|
||||||
|
|
||||||
private <T extends RequestMessage> T parseRequest(JsonNode header) throws Exception {
|
private <T extends RequestMessage> T parseRequest(JsonNode header) throws Exception {
|
||||||
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
|
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
|
||||||
TrackEvent.trace("beacon", "Parsed raw request:\n" + content.toPrettyString());
|
TrackEvent.trace("Parsed raw request:\n" + content.toPrettyString());
|
||||||
|
|
||||||
var type = content.required("messageType").textValue();
|
var type = content.required("messageType").textValue();
|
||||||
var phase = content.required("messagePhase").textValue();
|
var phase = content.required("messagePhase").textValue();
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class AppState {
|
||||||
@NonFinal
|
@NonFinal
|
||||||
@Setter
|
@Setter
|
||||||
String userName;
|
String userName;
|
||||||
|
|
||||||
@NonFinal
|
@NonFinal
|
||||||
@Setter
|
@Setter
|
||||||
String userEmail;
|
String userEmail;
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class AppStyle {
|
||||||
loadStylesheets();
|
loadStylesheets();
|
||||||
|
|
||||||
if (AppPrefs.get() != null) {
|
if (AppPrefs.get() != null) {
|
||||||
AppPrefs.get().useSystemFont.addListener((c, o, n) -> {
|
AppPrefs.get().useSystemFont().addListener((c, o, n) -> {
|
||||||
changeFontUsage(n);
|
changeFontUsage(n);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -48,17 +48,19 @@ public class AppStyle {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEvent.trace("core", "Loading styles for module " + module.getName());
|
TrackEvent.trace("Loading styles for module " + module.getName());
|
||||||
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||||
try {
|
try {
|
||||||
var bytes = Files.readAllBytes(file);
|
var bytes = Files.readAllBytes(file);
|
||||||
if (file.getFileName().toString().endsWith(".bss")) {
|
if (file.getFileName().toString().endsWith(".bss")) {
|
||||||
var s = "data:application/octet-stream;base64," + Base64.getEncoder().encodeToString(bytes);
|
var s = "data:application/octet-stream;base64,"
|
||||||
|
+ Base64.getEncoder().encodeToString(bytes);
|
||||||
STYLESHEET_CONTENTS.put(file, s);
|
STYLESHEET_CONTENTS.put(file, s);
|
||||||
} else if (file.getFileName().toString().endsWith(".css")) {
|
} else if (file.getFileName().toString().endsWith(".css")) {
|
||||||
var s = "data:text/css;base64," + Base64.getEncoder().encodeToString(bytes);
|
var s = "data:text/css;base64,"
|
||||||
|
+ Base64.getEncoder().encodeToString(bytes);
|
||||||
STYLESHEET_CONTENTS.put(file, s);
|
STYLESHEET_CONTENTS.put(file, s);
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -93,7 +95,7 @@ public class AppStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addStylesheets(Scene scene) {
|
public static void addStylesheets(Scene scene) {
|
||||||
if (AppPrefs.get() != null && !AppPrefs.get().useSystemFont.get()) {
|
if (AppPrefs.get() != null && !AppPrefs.get().useSystemFont().getValue()) {
|
||||||
scene.getStylesheets().add(FONT_CONTENTS);
|
scene.getStylesheets().add(FONT_CONTENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import atlantafx.base.theme.*;
|
import atlantafx.base.theme.*;
|
||||||
import com.jthemedetecor.OsThemeDetector;
|
|
||||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||||
|
@ -14,7 +13,10 @@ import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.application.ColorScheme;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
@ -71,30 +73,23 @@ public class AppTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OsThemeDetector detector = OsThemeDetector.getDetector();
|
|
||||||
if (AppPrefs.get().theme.getValue() == null) {
|
if (AppPrefs.get().theme.getValue() == null) {
|
||||||
try {
|
setDefault(Platform.getPreferences().getColorScheme());
|
||||||
setDefault(detector.isDark());
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
|
||||||
setDefault(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The gnome detector sometimes runs into issues, also it's not that important
|
Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> {
|
||||||
if (!OsType.getLocal().equals(OsType.LINUX)) {
|
Platform.runLater(() -> {
|
||||||
detector.registerListener(dark -> {
|
if (t1 == ColorScheme.DARK
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
&& !AppPrefs.get().theme.getValue().isDark()) {
|
||||||
if (dark && !AppPrefs.get().theme.getValue().isDark()) {
|
|
||||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dark && AppPrefs.get().theme.getValue().isDark()) {
|
if (t1 != ColorScheme.DARK
|
||||||
|
&& AppPrefs.get().theme.getValue().isDark()) {
|
||||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ErrorEvent.fromThrowable(t).omit().handle();
|
ErrorEvent.fromThrowable(t).omit().handle();
|
||||||
}
|
}
|
||||||
|
@ -110,8 +105,8 @@ public class AppTheme {
|
||||||
init = true;
|
init = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setDefault(boolean dark) {
|
private static void setDefault(ColorScheme colorScheme) {
|
||||||
if (dark) {
|
if (colorScheme == ColorScheme.DARK) {
|
||||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||||
} else {
|
} else {
|
||||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||||
|
@ -189,8 +184,8 @@ public class AppTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toTranslatedString() {
|
public ObservableValue<String> toTranslatedString() {
|
||||||
return name;
|
return new SimpleStringProperty(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +206,12 @@ public class AppTheme {
|
||||||
// Also include your custom theme here
|
// Also include your custom theme here
|
||||||
public static final List<Theme> ALL =
|
public static final List<Theme> ALL =
|
||||||
List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA);
|
List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA);
|
||||||
|
protected final String id;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
protected final String cssId;
|
||||||
|
|
||||||
|
protected final atlantafx.base.theme.Theme theme;
|
||||||
|
|
||||||
static Theme getDefaultLightTheme() {
|
static Theme getDefaultLightTheme() {
|
||||||
return switch (OsType.getLocal()) {
|
return switch (OsType.getLocal()) {
|
||||||
|
@ -228,13 +229,6 @@ public class AppTheme {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final String id;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
protected final String cssId;
|
|
||||||
|
|
||||||
protected final atlantafx.base.theme.Theme theme;
|
|
||||||
|
|
||||||
public boolean isDark() {
|
public boolean isDark() {
|
||||||
return theme.isDarkMode();
|
return theme.isDarkMode();
|
||||||
}
|
}
|
||||||
|
@ -244,8 +238,8 @@ public class AppTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toTranslatedString() {
|
public ObservableValue<String> toTranslatedString() {
|
||||||
return theme.getName();
|
return new SimpleStringProperty(theme.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -14,6 +14,7 @@ public class AppTray {
|
||||||
|
|
||||||
private static AppTray INSTANCE;
|
private static AppTray INSTANCE;
|
||||||
private final AppTrayIcon icon;
|
private final AppTrayIcon icon;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ErrorHandler errorHandler;
|
private final ErrorHandler errorHandler;
|
||||||
|
|
||||||
|
|
|
@ -14,21 +14,23 @@ public class AppTrayIcon {
|
||||||
|
|
||||||
private final SystemTray tray;
|
private final SystemTray tray;
|
||||||
private final TrayIcon trayIcon;
|
private final TrayIcon trayIcon;
|
||||||
private final PopupMenu popupMenu = new PopupMenu();
|
|
||||||
|
|
||||||
public AppTrayIcon() {
|
public AppTrayIcon() {
|
||||||
ensureSystemTraySupported();
|
ensureSystemTraySupported();
|
||||||
|
|
||||||
tray = SystemTray.getSystemTray();
|
tray = SystemTray.getSystemTray();
|
||||||
|
|
||||||
var image = switch (OsType.getLocal()) {
|
var image =
|
||||||
|
switch (OsType.getLocal()) {
|
||||||
case OsType.Windows windows -> "img/logo/logo_16x16.png";
|
case OsType.Windows windows -> "img/logo/logo_16x16.png";
|
||||||
case OsType.Linux linux -> "img/logo/logo_24x24.png";
|
case OsType.Linux linux -> "img/logo/logo_24x24.png";
|
||||||
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
|
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
|
||||||
};
|
};
|
||||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow();
|
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow();
|
||||||
|
|
||||||
this.trayIcon = new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu);
|
PopupMenu popupMenu = new PopupMenu();
|
||||||
|
this.trayIcon =
|
||||||
|
new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu);
|
||||||
this.trayIcon.setToolTip("XPipe");
|
this.trayIcon.setToolTip("XPipe");
|
||||||
this.trayIcon.setImageAutoSize(true);
|
this.trayIcon.setImageAutoSize(true);
|
||||||
|
|
||||||
|
@ -58,6 +60,19 @@ public class AppTrayIcon {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Image loadImageFromURL(URL iconImagePath) {
|
||||||
|
try {
|
||||||
|
return ImageIO.read(iconImagePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
|
return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupported() {
|
||||||
|
return Desktop.isDesktopSupported() && SystemTray.isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
public final TrayIcon getAwtTrayIcon() {
|
public final TrayIcon getAwtTrayIcon() {
|
||||||
return trayIcon;
|
return trayIcon;
|
||||||
}
|
}
|
||||||
|
@ -65,17 +80,7 @@ public class AppTrayIcon {
|
||||||
private void ensureSystemTraySupported() {
|
private void ensureSystemTraySupported() {
|
||||||
if (!SystemTray.isSupported()) {
|
if (!SystemTray.isSupported()) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"SystemTray icons are not "
|
"SystemTray icons are not " + "supported by the current desktop environment.");
|
||||||
+ "supported by the current desktop environment.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Image loadImageFromURL(URL iconImagePath) {
|
|
||||||
try {
|
|
||||||
return ImageIO.read(iconImagePath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
|
||||||
return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,9 +136,7 @@ public class AppTrayIcon {
|
||||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
showMacAlert(title, message, "Information");
|
showMacAlert(title, message, "Information");
|
||||||
} else {
|
} else {
|
||||||
EventQueue.invokeLater(() ->
|
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.INFO));
|
||||||
this.trayIcon.displayMessage(
|
|
||||||
title, message, TrayIcon.MessageType.INFO));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,9 +148,7 @@ public class AppTrayIcon {
|
||||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
showMacAlert(title, message, "Warning");
|
showMacAlert(title, message, "Warning");
|
||||||
} else {
|
} else {
|
||||||
EventQueue.invokeLater(() ->
|
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.WARNING));
|
||||||
this.trayIcon.displayMessage(
|
|
||||||
title, message, TrayIcon.MessageType.WARNING));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,9 +160,7 @@ public class AppTrayIcon {
|
||||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
showMacAlert(title, message, "Error");
|
showMacAlert(title, message, "Error");
|
||||||
} else {
|
} else {
|
||||||
EventQueue.invokeLater(() ->
|
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.ERROR));
|
||||||
this.trayIcon.displayMessage(
|
|
||||||
title, message, TrayIcon.MessageType.ERROR));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,9 +172,7 @@ public class AppTrayIcon {
|
||||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
showMacAlert(title, message, "Message");
|
showMacAlert(title, message, "Message");
|
||||||
} else {
|
} else {
|
||||||
EventQueue.invokeLater(() ->
|
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.NONE));
|
||||||
this.trayIcon.displayMessage(
|
|
||||||
title, message, TrayIcon.MessageType.NONE));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,26 +180,14 @@ public class AppTrayIcon {
|
||||||
this.showMessage(null, message);
|
this.showMessage(null, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupported() {
|
|
||||||
return Desktop.isDesktopSupported() && SystemTray.isSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMacAlert(String subTitle, String message, String title) {
|
private void showMacAlert(String subTitle, String message, String title) {
|
||||||
String execute = String.format(
|
String execute = String.format(
|
||||||
"display notification \"%s\""
|
"display notification \"%s\"" + " with title \"%s\"" + " subtitle \"%s\"",
|
||||||
+ " with title \"%s\""
|
message != null ? message : "", title != null ? title : "", subTitle != null ? subTitle : "");
|
||||||
+ " subtitle \"%s\"",
|
|
||||||
message != null ? message : "",
|
|
||||||
title != null ? title : "",
|
|
||||||
subTitle != null ? subTitle : ""
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
Runtime.getRuntime()
|
Runtime.getRuntime().exec(new String[] {"osascript", "-e", execute});
|
||||||
.exec(new String[] { "osascript", "-e", execute });
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException("Cannot run osascript with given parameters.");
|
||||||
"Cannot run osascript with given parameters.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.core;
|
||||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -19,6 +20,7 @@ import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Screen;
|
import javafx.stage.Screen;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
@ -63,6 +65,9 @@ public class AppWindowHelper {
|
||||||
public static Stage sideWindow(
|
public static Stage sideWindow(
|
||||||
String title, Function<Stage, Comp<?>> contentFunc, boolean bindSize, ObservableValue<Boolean> loading) {
|
String title, Function<Stage, Comp<?>> contentFunc, boolean bindSize, ObservableValue<Boolean> loading) {
|
||||||
var stage = new Stage();
|
var stage = new Stage();
|
||||||
|
if (AppMainWindow.getInstance() != null) {
|
||||||
|
stage.initOwner(AppMainWindow.getInstance().getStage());
|
||||||
|
}
|
||||||
stage.setTitle(title);
|
stage.setTitle(title);
|
||||||
if (AppMainWindow.getInstance() != null) {
|
if (AppMainWindow.getInstance() != null) {
|
||||||
stage.initOwner(AppMainWindow.getInstance().getStage());
|
stage.initOwner(AppMainWindow.getInstance().getStage());
|
||||||
|
@ -72,6 +77,10 @@ public class AppWindowHelper {
|
||||||
setupContent(stage, contentFunc, bindSize, loading);
|
setupContent(stage, contentFunc, bindSize, loading);
|
||||||
setupStylesheets(stage.getScene());
|
setupStylesheets(stage.getScene());
|
||||||
|
|
||||||
|
if (AppPrefs.get() != null && AppPrefs.get().enforceWindowModality().get()) {
|
||||||
|
stage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
stage.setOnShown(e -> {
|
stage.setOnShown(e -> {
|
||||||
// If we set the theme pseudo classes earlier when the window is not shown
|
// If we set the theme pseudo classes earlier when the window is not shown
|
||||||
// they do not apply. Is this a bug in JavaFX?
|
// they do not apply. Is this a bug in JavaFX?
|
||||||
|
@ -100,8 +109,7 @@ public class AppWindowHelper {
|
||||||
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2);
|
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showAlert(
|
public static void showAlert(Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
|
||||||
Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
|
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
var r = showBlockingAlert(c);
|
var r = showBlockingAlert(c);
|
||||||
if (bt != null) {
|
if (bt != null) {
|
||||||
|
@ -110,6 +118,36 @@ public class AppWindowHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setContent(Alert alert, String s) {
|
||||||
|
alert.getDialogPane().setMinWidth(505);
|
||||||
|
alert.getDialogPane().setPrefWidth(505);
|
||||||
|
alert.getDialogPane().setMaxWidth(505);
|
||||||
|
alert.getDialogPane().setContent(AppWindowHelper.alertContentText(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showConfirmationAlert(String title, String header, String content) {
|
||||||
|
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.titleProperty().bind(AppI18n.observable(title));
|
||||||
|
alert.headerTextProperty().bind(AppI18n.observable(header));
|
||||||
|
setContent(alert, AppI18n.get(content));
|
||||||
|
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||||
|
})
|
||||||
|
.map(b -> b.getButtonData().isDefaultButton())
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showConfirmationAlert(
|
||||||
|
ObservableValue<String> title, ObservableValue<String> header, ObservableValue<String> content) {
|
||||||
|
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.titleProperty().bind(title);
|
||||||
|
alert.headerTextProperty().bind(header);
|
||||||
|
setContent(alert, content.getValue());
|
||||||
|
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||||
|
})
|
||||||
|
.map(b -> b.getButtonData().isDefaultButton())
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
||||||
Supplier<Alert> supplier = () -> {
|
Supplier<Alert> supplier = () -> {
|
||||||
Alert a = AppWindowHelper.createEmptyAlert();
|
Alert a = AppWindowHelper.createEmptyAlert();
|
||||||
|
@ -224,7 +262,6 @@ public class AppWindowHelper {
|
||||||
if (event.getCode().equals(KeyCode.W) && event.isShortcutDown()) {
|
if (event.getCode().equals(KeyCode.W) && event.isShortcutDown()) {
|
||||||
stage.close();
|
stage.close();
|
||||||
event.consume();
|
event.consume();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -236,7 +273,11 @@ public class AppWindowHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
var allScreenBounds = computeWindowScreenBounds(stage);
|
var allScreenBounds = computeWindowScreenBounds(stage);
|
||||||
if (!areNumbersValid(allScreenBounds.getMinX(), allScreenBounds.getMinY(), allScreenBounds.getMaxX(), allScreenBounds.getMaxY())) {
|
if (!areNumbersValid(
|
||||||
|
allScreenBounds.getMinX(),
|
||||||
|
allScreenBounds.getMinY(),
|
||||||
|
allScreenBounds.getMaxX(),
|
||||||
|
allScreenBounds.getMaxY())) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,10 +328,13 @@ public class AppWindowHelper {
|
||||||
|
|
||||||
private static List<Screen> getWindowScreens(Stage stage) {
|
private static List<Screen> getWindowScreens(Stage stage) {
|
||||||
if (!areNumbersValid(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())) {
|
if (!areNumbersValid(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())) {
|
||||||
return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage ? getWindowScreens(ownerStage) : List.of(Screen.getPrimary());
|
return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage
|
||||||
|
? getWindowScreens(ownerStage)
|
||||||
|
: List.of(Screen.getPrimary());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Screen.getScreensForRectangle(new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()));
|
return Screen.getScreensForRectangle(
|
||||||
|
new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Rectangle2D computeWindowScreenBounds(Stage stage) {
|
private static Rectangle2D computeWindowScreenBounds(Stage stage) {
|
||||||
|
|
|
@ -17,9 +17,60 @@ import java.util.Optional;
|
||||||
|
|
||||||
public class AppAvCheck {
|
public class AppAvCheck {
|
||||||
|
|
||||||
@Getter
|
private static Optional<AvType> detect() {
|
||||||
public static enum AvType {
|
for (AvType value : AvType.values()) {
|
||||||
|
if (value.isActive()) {
|
||||||
|
return Optional.of(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void check() throws Throwable {
|
||||||
|
// Only show this on first launch on windows
|
||||||
|
if (OsType.getLocal() != OsType.WINDOWS || !AppState.get().isInitialLaunch()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = detect();
|
||||||
|
if (found.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformState.initPlatformOrThrow();
|
||||||
|
AppStyle.init();
|
||||||
|
AppImages.init();
|
||||||
|
|
||||||
|
var a = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
||||||
|
alert.setAlertType(Alert.AlertType.NONE);
|
||||||
|
|
||||||
|
AppResources.with(AppResources.XPIPE_MODULE, "misc/antivirus.md", file -> {
|
||||||
|
var markdown = new MarkdownComp(Files.readString(file), s -> {
|
||||||
|
var t = found.get();
|
||||||
|
return s.formatted(
|
||||||
|
t.getName(),
|
||||||
|
t.getName(),
|
||||||
|
t.getDescription(),
|
||||||
|
AppProperties.get().getVersion(),
|
||||||
|
AppProperties.get().getVersion(),
|
||||||
|
t.getName());
|
||||||
|
})
|
||||||
|
.prefWidth(550)
|
||||||
|
.prefHeight(600)
|
||||||
|
.createRegion();
|
||||||
|
alert.getDialogPane().setContent(markdown);
|
||||||
|
alert.getDialogPane().setPadding(new Insets(15));
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
|
||||||
|
});
|
||||||
|
a.filter(b -> b.getButtonData().isDefaultButton())
|
||||||
|
.ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum AvType {
|
||||||
BITDEFENDER("Bitdefender") {
|
BITDEFENDER("Bitdefender") {
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
|
@ -28,7 +79,8 @@ public class AppAvCheck {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Bitdefender", "InstallDir");
|
return WindowsRegistry.exists(
|
||||||
|
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MALWAREBYTES("Malwarebytes") {
|
MALWAREBYTES("Malwarebytes") {
|
||||||
|
@ -64,50 +116,4 @@ public class AppAvCheck {
|
||||||
|
|
||||||
public abstract boolean isActive();
|
public abstract boolean isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<AvType> detect() {
|
|
||||||
for (AvType value : AvType.values()) {
|
|
||||||
if (value.isActive()) {
|
|
||||||
return Optional.of(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check() throws Throwable {
|
|
||||||
// Only show this on first launch on windows
|
|
||||||
if (OsType.getLocal() != OsType.WINDOWS || !AppState.get().isInitialLaunch()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var found = detect();
|
|
||||||
if (found.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformState.initPlatformOrThrow();
|
|
||||||
AppStyle.init();
|
|
||||||
AppImages.init();
|
|
||||||
|
|
||||||
var a = AppWindowHelper.showBlockingAlert(alert -> {
|
|
||||||
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
|
||||||
alert.setAlertType(Alert.AlertType.NONE);
|
|
||||||
|
|
||||||
AppResources.with(
|
|
||||||
AppResources.XPIPE_MODULE,
|
|
||||||
"misc/antivirus.md",
|
|
||||||
file -> {
|
|
||||||
var markdown = new MarkdownComp(Files.readString(file), s -> {
|
|
||||||
var t = found.get();
|
|
||||||
return s.formatted(t.getName(), t.getName(), t.getDescription(), AppProperties.get().getVersion(), AppProperties.get().getVersion(), t.getName());
|
|
||||||
}).prefWidth(550).prefHeight(600).createRegion();
|
|
||||||
alert.getDialogPane().setContent(markdown);
|
|
||||||
alert.getDialogPane().setPadding(new Insets(15));
|
|
||||||
});
|
|
||||||
|
|
||||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
|
|
||||||
});
|
|
||||||
a.filter(b -> b.getButtonData().isDefaultButton())
|
|
||||||
.ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package io.xpipe.app.core.check;
|
||||||
|
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class AppCertutilCheck {
|
||||||
|
|
||||||
|
private static boolean getResult() {
|
||||||
|
var fc = new ProcessBuilder(System.getenv("WINDIR") + "\\System32\\certutil")
|
||||||
|
.redirectError(ProcessBuilder.Redirect.DISCARD);
|
||||||
|
try {
|
||||||
|
var proc = fc.start();
|
||||||
|
var out = new String(proc.getInputStream().readAllBytes());
|
||||||
|
proc.waitFor(1, TimeUnit.SECONDS);
|
||||||
|
return proc.exitValue() == 0 && !out.contains("The system cannot execute the specified program");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void check() {
|
||||||
|
if (AppPrefs.get().disableCertutilUse().get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getResult()) {
|
||||||
|
AppPrefs.get().disableCertutilUse.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@ package io.xpipe.app.core.check;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.process.ProcessOutputException;
|
import io.xpipe.core.process.ProcessOutputException;
|
||||||
import io.xpipe.core.process.ShellDialects;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ public class AppShellCheck {
|
||||||
public static void check() {
|
public static void check() {
|
||||||
var err = selfTestErrorCheck();
|
var err = selfTestErrorCheck();
|
||||||
if (err.isPresent()) {
|
if (err.isPresent()) {
|
||||||
var msg = """
|
var msg =
|
||||||
|
"""
|
||||||
Shell self-test failed for %s:
|
Shell self-test failed for %s:
|
||||||
%s
|
%s
|
||||||
|
|
||||||
|
@ -24,7 +25,12 @@ public class AppShellCheck {
|
||||||
- The operating system is not supported
|
- The operating system is not supported
|
||||||
|
|
||||||
You can reach out to us if you want to properly diagnose the cause individually and hopefully fix it.
|
You can reach out to us if you want to properly diagnose the cause individually and hopefully fix it.
|
||||||
""".formatted(ShellDialects.getPlatformDefault().getDisplayName(), err.get());
|
"""
|
||||||
|
.formatted(
|
||||||
|
ProcessControlProvider.get()
|
||||||
|
.getEffectiveLocalDialect()
|
||||||
|
.getDisplayName(),
|
||||||
|
err.get());
|
||||||
ErrorEvent.fromThrowable(new IllegalStateException(msg)).handle();
|
ErrorEvent.fromThrowable(new IllegalStateException(msg)).handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ public class AppTempCheck {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir == null || !Files.exists(dir) || !Files.isDirectory(dir)) {
|
if (dir == null || !Files.exists(dir) || !Files.isDirectory(dir)) {
|
||||||
ErrorEvent.fromThrowable(
|
ErrorEvent.fromThrowable(new IOException("Specified temporary directory " + tmpdir
|
||||||
new IOException("Specified temporary directory " + tmpdir + ", set via the environment variable %TEMP% is invalid."))
|
+ ", set via the environment variable %TEMP% is invalid."))
|
||||||
.term()
|
.term()
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,18 @@ import io.xpipe.app.browser.BrowserModel;
|
||||||
import io.xpipe.app.comp.store.StoreViewState;
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.check.AppAvCheck;
|
import io.xpipe.app.core.check.AppAvCheck;
|
||||||
|
import io.xpipe.app.core.check.AppCertutilCheck;
|
||||||
import io.xpipe.app.core.check.AppShellCheck;
|
import io.xpipe.app.core.check.AppShellCheck;
|
||||||
import io.xpipe.app.ext.ActionProvider;
|
import io.xpipe.app.ext.ActionProvider;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.storage.GitStorageHandler;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.FileBridge;
|
import io.xpipe.app.util.FileBridge;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
import io.xpipe.app.util.LockedSecretValue;
|
import io.xpipe.app.util.UnlockAlert;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
public class BaseMode extends OperationMode {
|
public class BaseMode extends OperationMode {
|
||||||
|
@ -39,29 +41,29 @@ public class BaseMode extends OperationMode {
|
||||||
// For debugging
|
// For debugging
|
||||||
// if (true) throw new IllegalStateException();
|
// if (true) throw new IllegalStateException();
|
||||||
|
|
||||||
TrackEvent.info("mode", "Initializing base mode components ...");
|
TrackEvent.info("Initializing base mode components ...");
|
||||||
AppExtensionManager.init(true);
|
AppExtensionManager.init(true);
|
||||||
JacksonMapper.initModularized(AppExtensionManager.getInstance().getExtendedLayer());
|
JacksonMapper.initModularized(AppExtensionManager.getInstance().getExtendedLayer());
|
||||||
JacksonMapper.configure(objectMapper -> {
|
|
||||||
objectMapper.registerSubtypes(LockedSecretValue.class);
|
|
||||||
});
|
|
||||||
// Load translations before storage initialization to localize store error messages
|
|
||||||
// Also loaded before antivirus alert to localize that
|
|
||||||
AppI18n.init();
|
AppI18n.init();
|
||||||
LicenseProvider.get().init();
|
LicenseProvider.get().init();
|
||||||
|
AppPrefs.initLocal();
|
||||||
|
AppCertutilCheck.check();
|
||||||
AppAvCheck.check();
|
AppAvCheck.check();
|
||||||
LocalShell.init();
|
LocalShell.init();
|
||||||
AppShellCheck.check();
|
|
||||||
XPipeDistributionType.init();
|
XPipeDistributionType.init();
|
||||||
AppPrefs.init();
|
AppShellCheck.check();
|
||||||
AppCharsets.init();
|
AppPrefs.setDefaults();
|
||||||
AppCharsetter.init();
|
// Initialize socket server as we should be prepared for git askpass commands
|
||||||
AppSocketServer.init();
|
AppSocketServer.init();
|
||||||
|
GitStorageHandler.getInstance().init();
|
||||||
|
GitStorageHandler.getInstance().setupRepositoryAndPull();
|
||||||
|
AppPrefs.initSharedRemote();
|
||||||
|
UnlockAlert.showIfNeeded();
|
||||||
DataStorage.init();
|
DataStorage.init();
|
||||||
AppFileWatcher.init();
|
AppFileWatcher.init();
|
||||||
FileBridge.init();
|
FileBridge.init();
|
||||||
ActionProvider.initProviders();
|
ActionProvider.initProviders();
|
||||||
TrackEvent.info("mode", "Finished base components initialization");
|
TrackEvent.info("Finished base components initialization");
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ public class BaseMode extends OperationMode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finalTeardown() {
|
public void finalTeardown() {
|
||||||
TrackEvent.info("mode", "Background mode shutdown started");
|
TrackEvent.info("Background mode shutdown started");
|
||||||
BrowserModel.DEFAULT.reset();
|
BrowserModel.DEFAULT.reset();
|
||||||
StoreViewState.reset();
|
StoreViewState.reset();
|
||||||
DataStorage.reset();
|
DataStorage.reset();
|
||||||
|
@ -80,6 +82,6 @@ public class BaseMode extends OperationMode {
|
||||||
AppDataLock.unlock();
|
AppDataLock.unlock();
|
||||||
// Shut down socket server last to keep a non-daemon thread running
|
// Shut down socket server last to keep a non-daemon thread running
|
||||||
AppSocketServer.reset();
|
AppSocketServer.reset();
|
||||||
TrackEvent.info("mode", "Background mode shutdown finished");
|
TrackEvent.info("Background mode shutdown finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.update.UpdateChangelogAlert;
|
import io.xpipe.app.update.UpdateChangelogAlert;
|
||||||
import io.xpipe.app.util.UnlockAlert;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
public class GuiMode extends PlatformMode {
|
public class GuiMode extends PlatformMode {
|
||||||
|
@ -17,14 +16,23 @@ public class GuiMode extends PlatformMode {
|
||||||
return "gui";
|
return "gui";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwitchFrom() {
|
||||||
|
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||||
|
TrackEvent.info("Closing windows");
|
||||||
|
Stage.getWindows().stream().toList().forEach(w -> {
|
||||||
|
w.hide();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwitchTo() throws Throwable {
|
public void onSwitchTo() throws Throwable {
|
||||||
super.onSwitchTo();
|
super.onSwitchTo();
|
||||||
|
|
||||||
UnlockAlert.showIfNeeded();
|
|
||||||
AppGreetings.showIfNeeded();
|
AppGreetings.showIfNeeded();
|
||||||
|
|
||||||
TrackEvent.info("mode", "Waiting for window setup completion ...");
|
TrackEvent.info("Waiting for window setup completion ...");
|
||||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||||
if (AppMainWindow.getInstance() == null) {
|
if (AppMainWindow.getInstance() == null) {
|
||||||
try {
|
try {
|
||||||
|
@ -35,18 +43,8 @@ public class GuiMode extends PlatformMode {
|
||||||
}
|
}
|
||||||
AppMainWindow.getInstance().show();
|
AppMainWindow.getInstance().show();
|
||||||
});
|
});
|
||||||
TrackEvent.info("mode", "Window setup complete");
|
TrackEvent.info("Window setup complete");
|
||||||
|
|
||||||
UpdateChangelogAlert.showIfNeeded();
|
UpdateChangelogAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwitchFrom() {
|
|
||||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
|
||||||
TrackEvent.info("mode", "Closing windows");
|
|
||||||
Stage.getWindows().stream().toList().forEach(w -> {
|
|
||||||
w.hide();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue