mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10: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
|
||||
extensions.txt
|
||||
dev_storage
|
||||
local*/
|
||||
local/
|
||||
local_*/
|
||||
.vs
|
||||
.vscode
|
||||
obj
|
||||
|
@ -15,3 +16,5 @@ out
|
|||
bin
|
||||
.DS_Store
|
||||
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 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,
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -2,15 +2,11 @@ plugins {
|
|||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
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/junit.gradle"
|
||||
|
||||
System.setProperty('excludeExtensionLibrary', 'true')
|
||||
apply from: "$rootDir/gradle/gradle_scripts/extension_test.gradle"
|
||||
|
||||
version = rootProject.versionString
|
||||
group = 'io.xpipe'
|
||||
archivesBaseName = 'xpipe-api'
|
||||
|
@ -19,14 +15,14 @@ repositories {
|
|||
mavenCentral()
|
||||
}
|
||||
|
||||
test {
|
||||
enabled = false
|
||||
dependencies {
|
||||
testImplementation project(':api')
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':core')
|
||||
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 {
|
||||
|
@ -38,6 +34,5 @@ task dist(type: Copy) {
|
|||
into "${project(':dist').buildDir}/dist/libraries"
|
||||
}
|
||||
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
apply from: "$rootDir/gradle/gradle_scripts/publish-base.gradle"
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.api.test;
|
||||
|
||||
import io.xpipe.beacon.BeaconDaemonController;
|
||||
import io.xpipe.beacon.test.BeaconDaemonController;
|
||||
import io.xpipe.core.util.XPipeDaemonMode;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
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 {
|
||||
id 'application'
|
||||
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
||||
id 'jvm-test-suite'
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations {
|
||||
dep
|
||||
}
|
||||
|
||||
apply from: "$rootDir/gradle/gradle_scripts/java.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: "$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 {
|
||||
implementation.extendsFrom(dep)
|
||||
implementation.extendsFrom(javafx)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':api')
|
||||
implementation project(':core')
|
||||
implementation project(':beacon')
|
||||
api project(':core')
|
||||
api project(':beacon')
|
||||
|
||||
compileOnly 'org.hamcrest:hamcrest:2.2'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.3'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.9.3'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.10.2'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.10.2'
|
||||
|
||||
implementation 'net.java.dev.jna:jna-jpms:5.13.0'
|
||||
implementation 'net.java.dev.jna:jna-platform-jpms:5.13.0'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.15.2"
|
||||
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.15.2"
|
||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.15.2"
|
||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.15.2"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: "12.2.0"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-feather-pack', version: "12.2.0"
|
||||
implementation (name: 'preferencesfx-core-11.15.0')
|
||||
implementation (group: 'com.dlsc.formsfx', name: 'formsfx-core', version: '11.6.0') {
|
||||
exclude group: 'org.openjfx', module: 'javafx-controls'
|
||||
exclude group: 'org.openjfx', module: 'javafx-fxml'
|
||||
}
|
||||
implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.7'
|
||||
implementation 'io.xpipe:modulefs:0.1.4'
|
||||
implementation 'com.jfoenix:jfoenix:9.0.10'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
||||
implementation 'net.synedra:validatorfx:0.4.2'
|
||||
implementation ('io.github.mkpaz:atlantafx-base:2.0.1') {
|
||||
api 'com.vladsch.flexmark:flexmark:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-data:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-ast:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-builder:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-sequence:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-misc:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-dependency:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-collection:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-format:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-html:0.64.0'
|
||||
api 'com.vladsch.flexmark:flexmark-util-visitor:0.64.0'
|
||||
|
||||
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||
api 'info.picocli:picocli:4.7.5'
|
||||
api 'org.kohsuke:github-api:1.318'
|
||||
api 'io.sentry:sentry:7.3.0'
|
||||
api 'org.ocpsoft.prettytime:prettytime:5.0.2.Final'
|
||||
api 'commons-io:commons-io:2.15.1'
|
||||
api 'net.java.dev.jna:jna-jpms:5.14.0'
|
||||
api 'net.java.dev.jna:jna-platform-jpms:5.14.0'
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.16.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-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"
|
||||
|
||||
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")
|
||||
}
|
||||
apply from: "$rootDir/gradle/gradle_scripts/local_junit_suite.gradle"
|
||||
|
||||
def extensionJarDepList = project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)).toList();
|
||||
|
||||
jar {
|
||||
finalizedBy(extensionJarDepList)
|
||||
}
|
||||
|
@ -146,14 +85,19 @@ run {
|
|||
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||
systemProperty 'io.xpipe.app.logLevel', "trace"
|
||||
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.printMessages", "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
|
||||
}
|
||||
|
||||
|
@ -181,7 +125,7 @@ processResources {
|
|||
|
||||
javaexec {
|
||||
workingDir = project.projectDir
|
||||
jvmArgs += "--module-path=$sourceSets.main.runtimeClasspath.asPath,"
|
||||
jvmArgs += "--module-path=${configurations.javafx.asFileTree.asPath},"
|
||||
jvmArgs += "--add-modules=javafx.graphics"
|
||||
main = "com.sun.javafx.css.parser.Css2Bin"
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ public class Main {
|
|||
|
||||
// Since this is not marked as a console application, it will not print anything when you run it in a console
|
||||
// So sadly there can't be a help command
|
||||
// if (args.length == 1 && args[0].equals("--help")) {
|
||||
// System.out.println("HELP");
|
||||
// return;
|
||||
// }
|
||||
// if (args.length == 1 && args[0].equals("--help")) {
|
||||
// System.out.println("HELP");
|
||||
// return;
|
||||
// }
|
||||
|
||||
OperationMode.init(args);
|
||||
}
|
||||
|
|
|
@ -5,12 +5,42 @@ import io.xpipe.app.core.AppWindowHelper;
|
|||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
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.stream.Collectors;
|
||||
|
||||
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) {
|
||||
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
||||
return true;
|
||||
|
@ -52,4 +82,12 @@ public class BrowserAlerts {
|
|||
}
|
||||
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 modifiedElements = new ArrayList<>(elements);
|
||||
if (val.startsWith("/")) {
|
||||
modifiedElements.add(0, "/");
|
||||
modifiedElements.addFirst("/");
|
||||
}
|
||||
Breadcrumbs.BreadCrumbItem<String> items =
|
||||
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.util.ThreadHelper;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.process.ProcessControlProvider;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.util.FailableRunnable;
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -24,18 +24,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
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 Instance currentDragClipboard;
|
||||
|
||||
|
@ -45,7 +33,7 @@ public class BrowserClipboard {
|
|||
.addFlavorListener(e -> ThreadHelper.runFailableAsync(new FailableRunnable<>() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run() throws Throwable {
|
||||
public void run() {
|
||||
Clipboard clipboard = (Clipboard) e.getSource();
|
||||
try {
|
||||
if (!clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
|
||||
|
@ -53,7 +41,8 @@ public class BrowserClipboard {
|
|||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -121,4 +110,20 @@ public class BrowserClipboard {
|
|||
|
||||
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();
|
||||
});
|
||||
|
||||
var bookmarksList = new BrowserBookmarkList(model).vgrow();
|
||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
|
||||
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
|
||||
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
|
||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
||||
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (model.getOpenFileSystems().size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
@ -69,20 +70,18 @@ public class BrowserComp extends SimpleComp {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Also show on local
|
||||
if (model.getSelected().getValue() != null) {
|
||||
// return model.getSelected().getValue().isLocal();
|
||||
}
|
||||
|
||||
return false;
|
||||
}, model.getOpenFileSystems(), model.getSelected())));
|
||||
},
|
||||
model.getOpenFileSystems(),
|
||||
model.getSelected())));
|
||||
localDownloadStage.prefHeight(200);
|
||||
localDownloadStage.maxHeight(200);
|
||||
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
|
||||
|
||||
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
|
||||
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
|
||||
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
|
||||
var splitPane = new SideSplitPaneComp(vertical, createTabs())
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
});
|
||||
|
@ -104,12 +103,16 @@ public class BrowserComp extends SimpleComp {
|
|||
selected.setSpacing(10);
|
||||
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
selected.getChildren().setAll(c.getList().stream().map(s -> {
|
||||
var field = new TextField(s.getRawFileEntry().getPath());
|
||||
selected.getChildren()
|
||||
.setAll(c.getList().stream()
|
||||
.map(s -> {
|
||||
var field =
|
||||
new TextField(s.getRawFileEntry().getPath());
|
||||
field.setEditable(false);
|
||||
field.setPrefWidth(500);
|
||||
return field;
|
||||
}).toList());
|
||||
})
|
||||
.toList());
|
||||
});
|
||||
});
|
||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||
|
@ -128,12 +131,16 @@ public class BrowserComp extends SimpleComp {
|
|||
}
|
||||
|
||||
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())),
|
||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
||||
Bindings.createBooleanBinding(() -> {
|
||||
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
|
||||
}, model.getOpenFileSystems())));
|
||||
Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return model.getOpenFileSystems().size() == 0
|
||||
&& !model.getMode().isChooser();
|
||||
},
|
||||
model.getOpenFileSystems())));
|
||||
return multi;
|
||||
}
|
||||
|
||||
|
@ -154,7 +161,8 @@ public class BrowserComp extends SimpleComp {
|
|||
map.put(v, 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!
|
||||
var modifying = new SimpleBooleanProperty();
|
||||
|
@ -170,9 +178,9 @@ public class BrowserComp extends SimpleComp {
|
|||
return;
|
||||
}
|
||||
|
||||
var source = map.entrySet()
|
||||
.stream()
|
||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
|
||||
var source = map.entrySet().stream()
|
||||
.filter(openFileSystemModelTabEntry ->
|
||||
openFileSystemModelTabEntry.getValue().equals(newValue))
|
||||
.findAny()
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
|
@ -187,9 +195,9 @@ public class BrowserComp extends SimpleComp {
|
|||
return;
|
||||
}
|
||||
|
||||
var toSelect = map.entrySet()
|
||||
.stream()
|
||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
|
||||
var toSelect = map.entrySet().stream()
|
||||
.filter(openFileSystemModelTabEntry ->
|
||||
openFileSystemModelTabEntry.getKey().equals(newValue))
|
||||
.findAny()
|
||||
.map(Map.Entry::getValue)
|
||||
.orElse(null);
|
||||
|
@ -228,9 +236,9 @@ public class BrowserComp extends SimpleComp {
|
|||
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
|
||||
while (c.next()) {
|
||||
for (var r : c.getRemoved()) {
|
||||
var source = map.entrySet()
|
||||
.stream()
|
||||
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
|
||||
var source = map.entrySet().stream()
|
||||
.filter(openFileSystemModelTabEntry ->
|
||||
openFileSystemModelTabEntry.getValue().equals(r))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
|
||||
|
@ -253,14 +261,22 @@ public class BrowserComp extends SimpleComp {
|
|||
ring.setMinSize(16, 16);
|
||||
ring.setPrefSize(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();
|
||||
|
||||
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
tab.graphicProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return model.getBusy().get() ? ring : logo;
|
||||
}, PlatformThread.sync(model.getBusy())));
|
||||
},
|
||||
PlatformThread.sync(model.getBusy())));
|
||||
tab.setText(model.getName());
|
||||
|
||||
tab.setContent(new OpenFileSystemComp(model).createSimple());
|
||||
|
@ -281,12 +297,17 @@ public class BrowserComp extends SimpleComp {
|
|||
|
||||
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
||||
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) {
|
||||
c.getStyleClass().add(color.getId());
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
AppFont.normal(this.getStyleableNode());
|
||||
|
||||
|
@ -81,7 +92,10 @@ final class BrowserContextMenu extends ContextMenu {
|
|||
}
|
||||
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.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.FileType;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
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.HumanReadableFormat;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -81,18 +81,18 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
||||
|
||||
var sizeCol = new TableColumn<BrowserEntry, Number>("Size");
|
||||
sizeCol.setCellValueFactory(param ->
|
||||
new SimpleLongProperty(param.getValue().getRawFileEntry().resolved().getSize()));
|
||||
sizeCol.setCellValueFactory(param -> new SimpleLongProperty(
|
||||
param.getValue().getRawFileEntry().resolved().getSize()));
|
||||
sizeCol.setCellFactory(col -> new FileSizeCell());
|
||||
|
||||
var mtimeCol = new TableColumn<BrowserEntry, Instant>("Modified");
|
||||
mtimeCol.setCellValueFactory(param ->
|
||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getDate()));
|
||||
mtimeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
|
||||
param.getValue().getRawFileEntry().resolved().getDate()));
|
||||
mtimeCol.setCellFactory(col -> new FileTimeCell());
|
||||
|
||||
var modeCol = new TableColumn<BrowserEntry, String>("Attributes");
|
||||
modeCol.setCellValueFactory(param ->
|
||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getMode()));
|
||||
modeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
|
||||
param.getValue().getRawFileEntry().resolved().getMode()));
|
||||
modeCol.setCellFactory(col -> new FileModeCell());
|
||||
modeCol.setSortable(false);
|
||||
|
||||
|
@ -171,7 +171,7 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||
.toArray();
|
||||
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
|
||||
&& row.getItem().getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
||||
&& row.getItem()
|
||||
.getRawFileEntry()
|
||||
.resolved()
|
||||
.getKind()
|
||||
== FileKind.DIRECTORY) {
|
||||
return event.getButton() == MouseButton.SECONDARY;
|
||||
}
|
||||
|
||||
if (row.getItem() != null
|
||||
&& row.getItem().getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) {
|
||||
&& row.getItem()
|
||||
.getRawFileEntry()
|
||||
.resolved()
|
||||
.getKind()
|
||||
!= FileKind.DIRECTORY) {
|
||||
return event.getButton() == MouseButton.SECONDARY
|
||||
|| event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2;
|
||||
}
|
||||
|
@ -409,9 +417,10 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
|
||||
double proximity = 100;
|
||||
Bounds tableBounds = tableView.localToScene(tableView.getBoundsInParent());
|
||||
Bounds tableBounds = tableView.localToScene(tableView.getBoundsInLocal());
|
||||
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;
|
||||
|
||||
// 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 final StringProperty img = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
private final Node imageView = new PrettySvgComp(img, 24, 24)
|
||||
.createRegion();
|
||||
private final StackPane textField =
|
||||
new LazyTextFieldComp(text).createStructure().get();
|
||||
private final HBox graphic;
|
||||
|
||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||
|
||||
|
@ -463,7 +517,8 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
};
|
||||
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.setAlignment(Pos.CENTER_LEFT);
|
||||
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 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.row = row;
|
||||
this.item = item;
|
||||
|
@ -59,11 +60,15 @@ public class BrowserFileListCompEntry {
|
|||
|
||||
var all = tv.getItems();
|
||||
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)
|
||||
.min()
|
||||
.orElse(1));
|
||||
var max = Math.max(index, tv.getSelectionModel().getSelectedIndices().stream()
|
||||
var max = Math.max(
|
||||
index,
|
||||
tv.getSelectionModel().getSelectedIndices().stream()
|
||||
.mapToInt(value -> value)
|
||||
.max()
|
||||
.orElse(all.indexOf(item)));
|
||||
|
@ -98,13 +103,15 @@ public class BrowserFileListCompEntry {
|
|||
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;
|
||||
}
|
||||
|
||||
// Prevent drag and drops of files into the current directory
|
||||
if (cb.getBaseDirectory() != null && cb
|
||||
.getBaseDirectory()
|
||||
if (cb.getBaseDirectory() != null
|
||||
&& cb.getBaseDirectory()
|
||||
.getPath()
|
||||
.equals(model.getFileSystemModel().getCurrentDirectory().getPath())
|
||||
&& (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.issue.ErrorEvent;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -99,8 +99,9 @@ public final class BrowserFileListModel {
|
|||
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
||||
var comp = comparatorProperty.getValue();
|
||||
|
||||
Comparator<? super BrowserEntry> us =
|
||||
comp != null ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) : syntheticFirst.thenComparing(dirsFirst);
|
||||
Comparator<? super BrowserEntry> us = comp != null
|
||||
? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp)
|
||||
: syntheticFirst.thenComparing(dirsFirst);
|
||||
l.sort(us);
|
||||
}
|
||||
|
||||
|
@ -110,14 +111,17 @@ public final class BrowserFileListModel {
|
|||
|
||||
boolean exists;
|
||||
try {
|
||||
exists = fileSystemModel.getFileSystem().fileExists(newFullPath) || fileSystemModel.getFileSystem().directoryExists(newFullPath);
|
||||
exists = fileSystemModel.getFileSystem().fileExists(newFullPath)
|
||||
|| fileSystemModel.getFileSystem().directoryExists(newFullPath);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist").expected().handle();
|
||||
ErrorEvent.fromMessage("Target " + newFullPath + " does already exist")
|
||||
.expected()
|
||||
.handle();
|
||||
fileSystemModel.refresh();
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,14 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
|||
|
||||
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
|
||||
public Structure createBase() {
|
||||
var expanded = new SimpleBooleanProperty();
|
||||
|
@ -98,12 +106,4 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
|||
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;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -11,6 +13,16 @@ public class BrowserGreetingComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
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 hour = ldt.getHour();
|
||||
String text;
|
||||
|
@ -21,8 +33,6 @@ public class BrowserGreetingComp extends SimpleComp {
|
|||
} else {
|
||||
text = "Good afternoon";
|
||||
}
|
||||
var r = new Label(text);
|
||||
AppFont.setSize(r, 7);
|
||||
return r;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ public class BrowserModel {
|
|||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||
private final BrowserSavedState savedState;
|
||||
|
||||
@Setter
|
||||
private Consumer<List<FileReference>> onFinish;
|
||||
|
||||
|
@ -70,12 +71,21 @@ public class BrowserModel {
|
|||
public void reset() {
|
||||
synchronized (BrowserModel.this) {
|
||||
for (OpenFileSystemModel o : new ArrayList<>(openFileSystems)) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (o.isBusy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
closeFileSystemSync(o);
|
||||
}
|
||||
if (savedState != null) {
|
||||
savedState.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
localTransfersStage.clear();
|
||||
}
|
||||
|
||||
public void finishChooser() {
|
||||
|
@ -95,8 +105,10 @@ public class BrowserModel {
|
|||
return;
|
||||
}
|
||||
|
||||
var stores = chosen.stream().map(
|
||||
entry -> new FileReference(selected.getValue().getEntry(), entry.getRawFileEntry().getPath())).toList();
|
||||
var stores = chosen.stream()
|
||||
.map(entry -> new FileReference(
|
||||
selected.getValue().getEntry(), entry.getRawFileEntry().getPath()))
|
||||
.toList();
|
||||
onFinish.accept(stores);
|
||||
}
|
||||
|
||||
|
@ -107,8 +119,11 @@ public class BrowserModel {
|
|||
}
|
||||
|
||||
private void closeFileSystemSync(OpenFileSystemModel open) {
|
||||
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) && savedState != null && open.getCurrentPath().get() != null) {
|
||||
savedState.add(new BrowserSavedState.Entry(open.getEntry().get().getUuid(), open.getCurrentPath().get()));
|
||||
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get())
|
||||
&& savedState != null
|
||||
&& open.getCurrentPath().get() != null) {
|
||||
savedState.add(new BrowserSavedState.Entry(
|
||||
open.getEntry().get().getUuid(), open.getCurrentPath().get()));
|
||||
}
|
||||
open.closeSync();
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,6 @@ public class BrowserNavBar extends SimpleComp {
|
|||
})
|
||||
.augment(new SimpleCompStructure<>(homeButton));
|
||||
|
||||
|
||||
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
|
||||
historyButton.setAccessibleText("History");
|
||||
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
@ -146,7 +145,6 @@ public class BrowserNavBar extends SimpleComp {
|
|||
.maxHeightProperty()
|
||||
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
||||
|
||||
|
||||
((Region) struc.get().getChildren().get(2))
|
||||
.minHeightProperty()
|
||||
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
|
||||
|
@ -197,7 +195,8 @@ public class BrowserNavBar extends SimpleComp {
|
|||
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()) {
|
||||
cm.getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import java.util.UUID;
|
|||
|
||||
public interface BrowserSavedState {
|
||||
|
||||
public void add(Entry entry);
|
||||
void add(Entry entry);
|
||||
|
||||
void save();
|
||||
|
||||
|
@ -18,7 +18,7 @@ public interface BrowserSavedState {
|
|||
@Value
|
||||
@Jacksonized
|
||||
@Builder
|
||||
public static class Entry {
|
||||
class Entry {
|
||||
|
||||
UUID uuid;
|
||||
String path;
|
||||
|
|
|
@ -20,12 +20,6 @@ import java.util.List;
|
|||
@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class)
|
||||
public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||
|
||||
static BrowserSavedStateImpl load() {
|
||||
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
||||
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
||||
});
|
||||
}
|
||||
|
||||
@JsonSerialize(as = List.class)
|
||||
ObservableList<Entry> lastSystems;
|
||||
|
||||
|
@ -33,25 +27,10 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
|||
this.lastSystems = FXCollections.observableArrayList(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);
|
||||
}
|
||||
static BrowserSavedStateImpl load() {
|
||||
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
||||
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,4 +51,24 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
|||
public ObservableList<Entry> getEntries() {
|
||||
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
|
||||
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) {
|
||||
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
||||
var scene = new Scene(r);
|
||||
|
@ -41,13 +48,6 @@ public class BrowserSelectionListComp extends SimpleComp {
|
|||
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
|
||||
protected Region createSimple() {
|
||||
var c = new ListBoxViewComp<>(list, list, entry -> {
|
||||
|
|
|
@ -2,11 +2,13 @@ package io.xpipe.app.browser;
|
|||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -21,6 +23,57 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
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 ccCount = Bindings.createStringBinding(
|
||||
() -> {
|
||||
|
@ -32,7 +85,10 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
}
|
||||
},
|
||||
cc);
|
||||
return new LabelComp(ccCount);
|
||||
}
|
||||
|
||||
private Comp<?> createSelectionStatus() {
|
||||
var selectedCount = PlatformThread.sync(Bindings.createIntegerBinding(
|
||||
() -> {
|
||||
return model.getFileList().getSelection().size();
|
||||
|
@ -46,7 +102,6 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
.count();
|
||||
},
|
||||
model.getFileList().getAll()));
|
||||
|
||||
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (selectedCount.getValue().intValue() == 0) {
|
||||
|
@ -57,19 +112,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
},
|
||||
selectedCount,
|
||||
allCount));
|
||||
|
||||
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;
|
||||
return selectedComp;
|
||||
}
|
||||
|
||||
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.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
|
@ -29,48 +29,61 @@ import java.util.Optional;
|
|||
|
||||
public class BrowserTransferComp extends SimpleComp {
|
||||
|
||||
private final BrowserTransferModel stage;
|
||||
private final BrowserTransferModel model;
|
||||
|
||||
public BrowserTransferComp(BrowserTransferModel stage) {
|
||||
this.stage = stage;
|
||||
public BrowserTransferComp(BrowserTransferModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
||||
.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 =
|
||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
||||
|
||||
var binding = BindingsHelper.mappedContentBinding(stage.getItems(), item -> item.getFileEntry());
|
||||
var list = new BrowserSelectionListComp(binding, entry -> Bindings.createStringBinding(() -> {
|
||||
var sourceItem = stage.getItems().stream().filter(item -> item.getFileEntry() == entry).findAny();
|
||||
var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
|
||||
var list = new BrowserSelectionListComp(
|
||||
binding,
|
||||
entry -> Bindings.createStringBinding(
|
||||
() -> {
|
||||
var sourceItem = model.getItems().stream()
|
||||
.filter(item -> item.getFileEntry() == entry)
|
||||
.findAny();
|
||||
if (sourceItem.isEmpty()) {
|
||||
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 + ")";
|
||||
}, stage.getAllDownloaded()))
|
||||
},
|
||||
model.getAllDownloaded()))
|
||||
.apply(struc -> struc.get().setMinHeight(150))
|
||||
.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")))
|
||||
.hide(PlatformThread.sync(
|
||||
BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))))
|
||||
.hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
|
||||
.grow(true, false)
|
||||
.apply(struc -> struc.get().setPadding(new Insets(8)));
|
||||
|
||||
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
|
||||
stage.download();
|
||||
model.download();
|
||||
})
|
||||
.hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())))
|
||||
.disable(PlatformThread.sync(stage.getAllDownloaded()))
|
||||
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())))
|
||||
.disable(PlatformThread.sync(model.getAllDownloaded()))
|
||||
.apply(new FancyTooltipAugment<>("downloadStageDescription"));
|
||||
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(
|
||||
new HorizontalComp(List.of(downloadButton, clearButton))
|
||||
.apply(struc -> struc.get().setSpacing(10)),
|
||||
|
@ -93,37 +106,56 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
event.acceptTransferModes(TransferMode.ANY);
|
||||
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 -> {
|
||||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null) {
|
||||
var files = BrowserClipboard.retrieveDrag(event.getDragboard())
|
||||
.getEntries();
|
||||
stage.drop(files);
|
||||
var drag = BrowserClipboard.retrieveDrag(event.getDragboard());
|
||||
if (drag == null) {
|
||||
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.consume();
|
||||
}
|
||||
});
|
||||
struc.get().setOnDragDetected(event -> {
|
||||
if (stage.getDownloading().get()) {
|
||||
if (model.getDownloading().get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drag within browser
|
||||
if (!stage.getAllDownloaded().get()) {
|
||||
var selected = stage.getItems().stream().map(item -> item.getFileEntry()).toList();
|
||||
var selected = model.getItems().stream()
|
||||
.map(BrowserTransferModel.Item::getFileEntry)
|
||||
.toList();
|
||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
||||
db.setContent(BrowserClipboard.startDrag(null, selected));
|
||||
|
||||
Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected));
|
||||
db.setDragView(image, -20, 15);
|
||||
|
||||
event.setDragDetect(true);
|
||||
event.consume();
|
||||
var cc = BrowserClipboard.startDrag(null, selected);
|
||||
if (cc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drag outside browser
|
||||
var files = stage.getItems().stream()
|
||||
var files = model.getItems().stream()
|
||||
.filter(item -> item.downloadFinished().get())
|
||||
.map(item -> {
|
||||
try {
|
||||
var file = item.getLocalFile();
|
||||
|
@ -131,40 +163,35 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
return Optional.<File>empty();
|
||||
}
|
||||
|
||||
return Optional.of(file
|
||||
.toRealPath()
|
||||
.toFile());
|
||||
return Optional.of(
|
||||
file.toRealPath().toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.MOVE);
|
||||
var cc = new ClipboardContent();
|
||||
cc.putFiles(files);
|
||||
db.setContent(cc);
|
||||
|
||||
var image = BrowserSelectionListComp.snapshot(
|
||||
FXCollections.observableList(stage.getItems().stream()
|
||||
.map(item -> item.getFileEntry())
|
||||
.toList()));
|
||||
Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected));
|
||||
db.setDragView(image, -20, 15);
|
||||
|
||||
event.setDragDetect(true);
|
||||
event.consume();
|
||||
});
|
||||
struc.get().setOnDragDone(event -> {
|
||||
// macOS does always report false here
|
||||
if (!event.isAccepted()) {
|
||||
// macOS does always report false here, which is unfortunate
|
||||
if (!event.isAccepted() && !OsType.getLocal().equals(OsType.MACOS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
stage.getItems().clear();
|
||||
// Don't clear, it might be more convenient to keep the contents
|
||||
// model.clear();
|
||||
event.consume();
|
||||
});
|
||||
}),
|
||||
PlatformThread.sync(stage.getDownloading()));
|
||||
PlatformThread.sync(model.getDownloading()));
|
||||
return stack.styleClass("transfer").createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,23 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Value;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -21,8 +28,7 @@ import java.util.concurrent.Executors;
|
|||
@Value
|
||||
public class BrowserTransferModel {
|
||||
|
||||
private static final Path TEMP =
|
||||
FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("download");
|
||||
private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("download");
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||
|
@ -30,30 +36,32 @@ public class BrowserTransferModel {
|
|||
t.setName("file downloader");
|
||||
return t;
|
||||
});
|
||||
|
||||
@Value
|
||||
public static class Item {
|
||||
String name;
|
||||
FileSystem.FileEntry fileEntry;
|
||||
Path localFile;
|
||||
BooleanProperty finishedDownload = new SimpleBooleanProperty();
|
||||
}
|
||||
|
||||
BrowserModel browserModel;
|
||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||
BooleanProperty downloading = new SimpleBooleanProperty();
|
||||
BooleanProperty allDownloaded = new SimpleBooleanProperty();
|
||||
|
||||
public void clear() {
|
||||
try {
|
||||
FileUtils.deleteDirectory(TEMP.toFile());
|
||||
private void cleanDirectory() {
|
||||
if (!Files.isDirectory(TEMP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var ls = Files.list(TEMP)) {
|
||||
var list = ls.toList();
|
||||
for (Path path : list) {
|
||||
FileUtils.forceDelete(path.toFile());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
cleanDirectory();
|
||||
items.clear();
|
||||
}
|
||||
|
||||
public void drop(List<FileSystem.FileEntry> entries) {
|
||||
public void drop(OpenFileSystemModel model, List<FileSystem.FileEntry> entries) {
|
||||
entries.forEach(entry -> {
|
||||
var name = FileNames.getFileName(entry.getPath());
|
||||
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
||||
|
@ -61,12 +69,39 @@ public class BrowserTransferModel {
|
|||
}
|
||||
|
||||
Path file = TEMP.resolve(name);
|
||||
var item = new Item(name, entry, file);
|
||||
var item = new Item(model, name, entry, file);
|
||||
items.add(item);
|
||||
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() {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
|
@ -77,18 +112,23 @@ public class BrowserTransferModel {
|
|||
}
|
||||
|
||||
for (Item item : new ArrayList<>(items)) {
|
||||
if (item.getFinishedDownload().get()) {
|
||||
if (item.downloadFinished().get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.getOpenFileSystemModel() != null
|
||||
&& item.getOpenFileSystemModel().isClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
try (var b = new BooleanScope(downloading).start()) {
|
||||
FileSystemHelper.dropFilesInto(
|
||||
FileSystemHelper.getLocal(TEMP),
|
||||
List.of(item.getFileEntry()),
|
||||
true);
|
||||
FileSystemHelper.getLocal(TEMP), List.of(item.getFileEntry()), true, false, progress -> {
|
||||
item.getProgress().setValue(progress);
|
||||
item.getOpenFileSystemModel().getProgress().setValue(progress);
|
||||
});
|
||||
}
|
||||
item.finishedDownload.set(true);
|
||||
} catch (Throwable t) {
|
||||
ErrorEvent.fromThrowable(t).handle();
|
||||
items.remove(item);
|
||||
|
@ -97,4 +137,31 @@ public class BrowserTransferModel {
|
|||
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;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.TileButtonComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
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));
|
||||
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);
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
hbox.setSpacing(15);
|
||||
|
@ -68,11 +70,15 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
});
|
||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||
|
||||
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.";
|
||||
}, empty)).createRegion();
|
||||
header.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
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.";
|
||||
},
|
||||
empty))
|
||||
.createRegion();
|
||||
AppFont.setSize(header, 1);
|
||||
vbox.getChildren().add(header);
|
||||
|
||||
var storeList = new VBox();
|
||||
|
@ -80,21 +86,31 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
|
||||
var listBox = new ListBoxViewComp<>(list, list, e -> {
|
||||
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);
|
||||
view.padding(new Insets(2, 8, 2, 8));
|
||||
var content =
|
||||
JfxHelper.createNamedEntry(DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
|
||||
var content = JfxHelper.createNamedEntry(
|
||||
DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
|
||||
var disable = new SimpleBooleanProperty();
|
||||
return new ButtonComp(null, content, () -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
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.setSpacing(10);
|
||||
}).hide(empty).createRegion();
|
||||
})
|
||||
.hide(empty)
|
||||
.createRegion();
|
||||
|
||||
var layout = new VBox();
|
||||
layout.getStyleClass().add("welcome");
|
||||
|
@ -109,7 +125,10 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
||||
model.restoreState(state);
|
||||
actionEvent.consume();
|
||||
}).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions");
|
||||
})
|
||||
.grow(true, false)
|
||||
.hide(empty)
|
||||
.accessibleTextKey("restoreAllSessions");
|
||||
layout.getChildren().add(tile.createRegion());
|
||||
|
||||
return layout;
|
||||
|
|
|
@ -7,13 +7,22 @@ import io.xpipe.core.store.FileNames;
|
|||
import io.xpipe.core.store.FileSystem;
|
||||
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.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileSystemHelper {
|
||||
|
||||
private static final int DEFAULT_BUFFER_SIZE = 16384;
|
||||
private static FileSystem localFileSystem;
|
||||
|
||||
public static String adjustPath(OpenFileSystemModel model, String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
|
@ -114,7 +123,8 @@ public class FileSystemHelper {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -125,8 +135,6 @@ public class FileSystemHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private static FileSystem localFileSystem;
|
||||
|
||||
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
|
||||
if (localFileSystem == null) {
|
||||
localFileSystem = new LocalStore().createFileSystem();
|
||||
|
@ -144,8 +152,8 @@ public class FileSystemHelper {
|
|||
Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
||||
}
|
||||
|
||||
public static void dropLocalFilesInto(FileSystem.FileEntry entry, List<Path> files) {
|
||||
try {
|
||||
public static void dropLocalFilesInto(
|
||||
FileSystem.FileEntry entry, List<Path> files, Consumer<BrowserTransferProgress> progress, boolean checkConflicts) throws Exception {
|
||||
var entries = files.stream()
|
||||
.map(path -> {
|
||||
try {
|
||||
|
@ -155,14 +163,11 @@ public class FileSystemHelper {
|
|||
}
|
||||
})
|
||||
.toList();
|
||||
dropFilesInto(entry, entries, false);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
dropFilesInto(entry, entries, false, checkConflicts, progress);
|
||||
}
|
||||
|
||||
public static void delete(List<FileSystem.FileEntry> files) {
|
||||
if (files.size() == 0) {
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -176,22 +181,43 @@ public class FileSystemHelper {
|
|||
}
|
||||
|
||||
public static void dropFilesInto(
|
||||
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) throws Exception {
|
||||
if (files.size() == 0) {
|
||||
FileSystem.FileEntry target,
|
||||
List<FileSystem.FileEntry> files,
|
||||
boolean explicitCopy,
|
||||
boolean checkConflicts,
|
||||
Consumer<BrowserTransferProgress> progress)
|
||||
throws Exception {
|
||||
if (files.isEmpty()) {
|
||||
progress.accept(BrowserTransferProgress.empty());
|
||||
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) {
|
||||
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 {
|
||||
dropFileAcrossFileSystems(target, file);
|
||||
dropFileAcrossFileSystems(target, file, progress, lastConflictChoice, files.size() > 1, checkConflicts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (source.getPath().equals(target.getPath())) {
|
||||
return;
|
||||
|
@ -205,7 +231,12 @@ public class FileSystemHelper {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -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 {
|
||||
if (target.getKind() != FileKind.DIRECTORY) {
|
||||
throw new IllegalStateException("Target " + target.getPath() + " is not a directory");
|
||||
|
@ -229,19 +266,27 @@ public class FileSystemHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
AtomicLong totalSize = new AtomicLong();
|
||||
if (source.getKind() == FileKind.DIRECTORY) {
|
||||
var directoryName = FileNames.getFileName(source.getPath());
|
||||
flatFiles.put(source, directoryName);
|
||||
|
||||
var baseRelative = FileNames.toDirectory(FileNames.getParent(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())));
|
||||
});
|
||||
if (fileEntry.getKind() == FileKind.FILE) {
|
||||
// This one is up-to-date and does not need to be recalculated
|
||||
totalSize.addAndGet(fileEntry.getSize());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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()) {
|
||||
var sourceFile = e.getKey();
|
||||
var targetFile = FileNames.join(target.getPath(), e.getValue());
|
||||
|
@ -252,11 +297,127 @@ public class FileSystemHelper {
|
|||
if (sourceFile.getKind() == FileKind.DIRECTORY) {
|
||||
target.getFileSystem().mkdirs(targetFile);
|
||||
} else if (sourceFile.getKind() == FileKind.FILE) {
|
||||
try (var in = sourceFile.getFileSystem().openInput(sourceFile.getPath());
|
||||
var out = target.getFileSystem().openOutput(targetFile)) {
|
||||
in.transferTo(out);
|
||||
if (checkConflicts && !handleChoice(
|
||||
lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) {
|
||||
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.DataStoreEntryRef;
|
||||
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.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.util.FailableConsumer;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -27,20 +30,21 @@ import java.util.stream.Stream;
|
|||
public final class OpenFileSystemModel {
|
||||
|
||||
private final DataStoreEntryRef<? extends FileSystemStore> entry;
|
||||
private FileSystem fileSystem;
|
||||
private final Property<String> filter = new SimpleStringProperty();
|
||||
private final BrowserFileListModel fileList;
|
||||
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
||||
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
private final BrowserModel browserModel;
|
||||
private OpenFileSystemSavedState savedState;
|
||||
private OpenFileSystemCache cache;
|
||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||
private final String name;
|
||||
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;
|
||||
|
||||
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
||||
|
@ -56,6 +60,24 @@ public final class OpenFileSystemModel {
|
|||
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) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (fileSystem == null) {
|
||||
|
@ -131,8 +153,13 @@ public final class OpenFileSystemModel {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
// 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
|
||||
var adjustedPath = FileSystemHelper.adjustPath(this, path);
|
||||
|
@ -158,26 +185,19 @@ public final class OpenFileSystemModel {
|
|||
var directory = currentPath.get();
|
||||
var name = adjustedPath + " - " + entry.get().getName();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
||||
TerminalHelper.open(
|
||||
if (ShellDialects.getStartableDialects().stream()
|
||||
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) {
|
||||
TerminalLauncher.open(
|
||||
entry.getEntry(),
|
||||
name,
|
||||
fileSystem
|
||||
.getShell()
|
||||
.get()
|
||||
.subShell(processControl -> adjustedPath, (sc) -> adjustedPath)
|
||||
.withInitSnippet(new SimpleScriptSnippet(
|
||||
fileSystem
|
||||
.getShell()
|
||||
.get()
|
||||
.getShellDialect()
|
||||
.getCdCommand(currentPath.get()),
|
||||
ScriptSnippet.ExecutionType.BOTH)));
|
||||
directory,
|
||||
fileSystem.getShell().get().singularSubShell(ShellOpenFunction.of(adjustedPath)));
|
||||
} else {
|
||||
TerminalHelper.open(
|
||||
TerminalLauncher.open(
|
||||
entry.getEntry(),
|
||||
name,
|
||||
fileSystem.getShell().get().command(adjustedPath).withWorkingDirectory(directory));
|
||||
directory,
|
||||
fileSystem.getShell().get().command(adjustedPath));
|
||||
}
|
||||
});
|
||||
return Optional.ofNullable(currentPath.get());
|
||||
|
@ -227,6 +247,7 @@ public final class OpenFileSystemModel {
|
|||
private boolean loadFilesSync(String dir) {
|
||||
try {
|
||||
if (dir != null) {
|
||||
startIfNeeded();
|
||||
var stream = getFileSystem().listFiles(dir);
|
||||
fileList.setAll(stream);
|
||||
} else {
|
||||
|
@ -247,7 +268,8 @@ public final class OpenFileSystemModel {
|
|||
return;
|
||||
}
|
||||
|
||||
FileSystemHelper.dropLocalFilesInto(entry, files);
|
||||
startIfNeeded();
|
||||
FileSystemHelper.dropLocalFilesInto(entry, files, progress::setValue, true);
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
|
@ -266,14 +288,10 @@ public final class OpenFileSystemModel {
|
|||
return;
|
||||
}
|
||||
|
||||
var same = files.get(0).getFileSystem().equals(target.getFileSystem());
|
||||
if (same && !explicitCopy) {
|
||||
if (!BrowserAlerts.showMoveAlert(files, target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemHelper.dropFilesInto(target, files, explicitCopy);
|
||||
startIfNeeded();
|
||||
FileSystemHelper.dropFilesInto(target, files, explicitCopy, true, browserTransferProgress -> {
|
||||
progress.setValue(browserTransferProgress);
|
||||
});
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
|
@ -294,9 +312,11 @@ public final class OpenFileSystemModel {
|
|||
return;
|
||||
}
|
||||
|
||||
startIfNeeded();
|
||||
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
|
||||
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);
|
||||
|
@ -320,6 +340,7 @@ public final class OpenFileSystemModel {
|
|||
return;
|
||||
}
|
||||
|
||||
startIfNeeded();
|
||||
var abs = FileNames.join(getCurrentDirectory().getPath(), linkName);
|
||||
fileSystem.symbolicLink(abs, targetFile);
|
||||
refreshSync();
|
||||
|
@ -370,14 +391,12 @@ public final class OpenFileSystemModel {
|
|||
BooleanScope.execute(busy, () -> {
|
||||
var fs = entry.getStore().createFileSystem();
|
||||
if (fs.getShell().isPresent()) {
|
||||
this.customScriptsStartIndex = fs.getShell().get().getInitCommands().size();
|
||||
this.customScriptsStartIndex =
|
||||
fs.getShell().get().getInitCommands().size();
|
||||
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
||||
}
|
||||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
this.local = fs.getShell()
|
||||
.map(shellControl -> shellControl.hasLocalSystemAccess())
|
||||
.orElse(false);
|
||||
|
||||
this.cache = new OpenFileSystemCache(this);
|
||||
for (BrowserAction b : BrowserAction.ALL) {
|
||||
|
@ -408,21 +427,12 @@ public final class OpenFileSystemModel {
|
|||
BooleanScope.execute(busy, () -> {
|
||||
if (fileSystem.getShell().isPresent()) {
|
||||
var connection = fileSystem.getShell().get();
|
||||
var snippet = directory != null ? new SimpleScriptSnippet(connection.getShellDialect().getCdCommand(directory),
|
||||
ScriptSnippet.ExecutionType.BOTH) : null;
|
||||
if (snippet != null) {
|
||||
connection.getInitCommands().add(customScriptsStartIndex,snippet);
|
||||
}
|
||||
|
||||
try {
|
||||
var name = (directory != null ? directory + " - " : "") + entry.get().getName();
|
||||
TerminalHelper.open(entry.getEntry(), name, connection);
|
||||
var name = (directory != null ? directory + " - " : "")
|
||||
+ entry.get().getName();
|
||||
TerminalLauncher.open(entry.getEntry(), name, directory, connection);
|
||||
|
||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||
connection.start();
|
||||
} finally {
|
||||
connection.getInitCommands().remove(snippet);
|
||||
}
|
||||
startIfNeeded();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,6 +33,86 @@ import java.util.stream.Collectors;
|
|||
@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class)
|
||||
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> {
|
||||
|
||||
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
|
||||
@Jacksonized
|
||||
@Builder
|
||||
|
@ -95,76 +167,4 @@ public class OpenFileSystemSavedState {
|
|||
String directory;
|
||||
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(() -> {
|
||||
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
|
||||
var comp = new BrowserComp(model)
|
||||
|
@ -47,7 +48,7 @@ public class StandaloneFileBrowser {
|
|||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.accept(fileStores.size() > 0 ? fileStores.get(0) : null);
|
||||
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
|
@ -63,7 +64,7 @@ public class StandaloneFileBrowser {
|
|||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.setValue(fileStores.size() > 0 ? fileStores.get(0) : null);
|
||||
file.setValue(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
|
|
|
@ -13,14 +13,6 @@ import java.util.ServiceLoader;
|
|||
|
||||
public interface BrowserAction {
|
||||
|
||||
enum Category {
|
||||
CUSTOM,
|
||||
OPEN,
|
||||
NATIVE,
|
||||
COPY_PASTE,
|
||||
MUTATION
|
||||
}
|
||||
|
||||
List<BrowserAction> ALL = new ArrayList<>();
|
||||
|
||||
static List<LeafAction> getFlattened(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
|
@ -39,7 +31,7 @@ public interface BrowserAction {
|
|||
.orElseThrow();
|
||||
}
|
||||
|
||||
default void init(OpenFileSystemModel model) throws Exception {}
|
||||
default void init(OpenFileSystemModel model) {}
|
||||
|
||||
default String getProFeatureId() {
|
||||
return null;
|
||||
|
@ -75,6 +67,14 @@ public interface BrowserAction {
|
|||
return true;
|
||||
}
|
||||
|
||||
enum Category {
|
||||
CUSTOM,
|
||||
OPEN,
|
||||
NATIVE,
|
||||
COPY_PASTE,
|
||||
MUTATION
|
||||
}
|
||||
|
||||
class Loader implements ModuleLayerLoader {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.List;
|
|||
public class BrowserActionFormatter {
|
||||
|
||||
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) {
|
||||
|
|
|
@ -52,7 +52,8 @@ public interface LeafAction extends BrowserAction {
|
|||
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.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
|
||||
}
|
||||
|
@ -83,7 +84,8 @@ public interface LeafAction extends BrowserAction {
|
|||
mi.setMnemonicParsing(false);
|
||||
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.setText(mi.getText() + " (Pro)");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.xpipe.app.browser.BrowserEntry;
|
|||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
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 org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
@ -24,25 +24,30 @@ public abstract class MultiExecuteAction implements BranchAction {
|
|||
model.withShell(
|
||||
pc -> {
|
||||
for (BrowserEntry entry : entries) {
|
||||
TerminalHelper.open(model.getEntry().getEntry(), FilenameUtils.getBaseName(
|
||||
entry.getRawFileEntry().getPath()), pc.command(createCommand(pc, model, entry))
|
||||
.withWorkingDirectory(model.getCurrentDirectory()
|
||||
.getPath()));
|
||||
TerminalLauncher.open(
|
||||
model.getEntry().getEntry(),
|
||||
FilenameUtils.getBaseName(
|
||||
entry.getRawFileEntry().getPath()),
|
||||
model.getCurrentDirectory() != null
|
||||
? model.getCurrentDirectory()
|
||||
.getPath()
|
||||
: null,
|
||||
pc.command(createCommand(pc, model, entry)));
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return AppPrefs.get().terminalType().getValue() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
var t = AppPrefs.get().terminalType().getValue();
|
||||
return "in " + (t != null ? t.toTranslatedString() : "?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return AppPrefs.get().terminalType().getValue() != null;
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
|
||||
|
@ -51,7 +56,8 @@ public abstract class MultiExecuteAction implements BranchAction {
|
|||
model.withShell(
|
||||
pc -> {
|
||||
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)
|
||||
.withWorkingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
|
|
|
@ -9,7 +9,10 @@ import java.io.BufferedReader;
|
|||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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;
|
||||
|
||||
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 {
|
||||
|
||||
@Getter
|
||||
|
@ -101,10 +110,4 @@ public interface DirectoryType {
|
|||
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
|
||||
class Simple implements FileType {
|
||||
|
||||
|
@ -72,7 +78,9 @@ public interface FileType {
|
|||
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
|
||||
|
@ -80,10 +88,4 @@ public interface FileType {
|
|||
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.util.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
@ -31,13 +32,14 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
|||
model.getSelected())))));
|
||||
|
||||
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.setRight(sidebar.createRegion());
|
||||
pane.getStyleClass().add("background");
|
||||
model.getSelected().addListener((c, o, n) -> {
|
||||
if (o != null && o.equals(model.getEntries().get(2))) {
|
||||
AppPrefs.get().save();
|
||||
DataStorage.get().saveAsync();
|
||||
}
|
||||
});
|
||||
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);
|
||||
return c;
|
||||
}),
|
||||
paths -> value.setValue(Files.readString(paths.get(0))));
|
||||
paths -> value.setValue(Files.readString(paths.getFirst())));
|
||||
return fileDrop.createRegion();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
|
@ -28,7 +27,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
|
|||
@Override
|
||||
public LazyTextFieldComp.Structure createBase() {
|
||||
var sp = new StackPane();
|
||||
var r = new JFXTextField();
|
||||
var r = new TextField();
|
||||
|
||||
r.setOnKeyPressed(ke -> {
|
||||
if (ke.getCode().equals(KeyCode.ESCAPE)) {
|
||||
|
@ -69,8 +68,7 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
|
|||
SimpleChangeListener.apply(currentValue, n -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if (Objects.equals(r.getText(), n)
|
||||
|| (n == null && r.getText().isEmpty())) {
|
||||
if (Objects.equals(r.getText(), n) || (n == null && r.getText().isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
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 = () -> {
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
|
|
|
@ -65,7 +65,8 @@ public class ListSelectorComp<T> extends SimpleComp {
|
|||
|
||||
if (showAllSelector) {
|
||||
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) -> {
|
||||
cbs.forEach(checkBox -> {
|
||||
if (checkBox.isDisabled()) {
|
||||
|
|
|
@ -16,10 +16,8 @@ import javafx.scene.layout.StackPane;
|
|||
|
||||
public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||
|
||||
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
|
||||
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
|
||||
}
|
||||
|
||||
private static final double FPS = 30.0;
|
||||
private static final double cycleDurationSeconds = 4.0;
|
||||
private final Comp<?> comp;
|
||||
private final ObservableValue<Boolean> showLoading;
|
||||
private final ObservableValue<Number> progress;
|
||||
|
@ -30,6 +28,10 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||
this.progress = PlatformThread.sync(progress);
|
||||
}
|
||||
|
||||
public static LoadingOverlayComp noProgress(Comp<?> comp, ObservableValue<Boolean> loading) {
|
||||
return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<StackPane> createBase() {
|
||||
var compStruc = comp.createStructure();
|
||||
|
@ -39,6 +41,11 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||
loading.progressProperty().bind(progress);
|
||||
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);
|
||||
loadingOverlay.getStyleClass().add("loading-comp");
|
||||
loadingOverlay.setVisible(showLoading.getValue());
|
||||
|
|
|
@ -46,11 +46,14 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||
@SneakyThrows
|
||||
private WebView createWebView() {
|
||||
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);
|
||||
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark() ? "web/github-markdown-dark.css" : "web/github-markdown-light.css";
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme)
|
||||
.orElseThrow();
|
||||
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark()
|
||||
? "web/github-markdown-dark.css"
|
||||
: "web/github-markdown-light.css";
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
|
||||
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
||||
|
||||
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.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
@ -23,23 +20,14 @@ import lombok.Value;
|
|||
|
||||
public class ModalOverlayComp extends SimpleComp {
|
||||
|
||||
private final Comp<?> background;
|
||||
private final Property<OverlayContent> overlayContent;
|
||||
|
||||
public ModalOverlayComp(Comp<?> background, Property<OverlayContent> overlayContent) {
|
||||
this.background = background;
|
||||
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
|
||||
protected Region createSimple() {
|
||||
var bgRegion = background.createRegion();
|
||||
|
@ -62,7 +50,7 @@ public class ModalOverlayComp extends SimpleComp {
|
|||
|
||||
if (newValue.finishKey != null) {
|
||||
var finishButton = new Button(AppI18n.get(newValue.finishKey));
|
||||
Shortcuts.addShortcut(finishButton, new KeyCodeCombination(KeyCode.ENTER));
|
||||
finishButton.setDefaultButton(true);
|
||||
Styles.toggleStyleClass(finishButton, Styles.FLAT);
|
||||
finishButton.setOnAction(event -> {
|
||||
newValue.onFinish.run();
|
||||
|
@ -96,4 +84,13 @@ public class ModalOverlayComp extends SimpleComp {
|
|||
});
|
||||
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 {
|
||||
|
||||
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 ObservableValue<SystemStateComp.State> state;
|
||||
|
||||
|
@ -47,7 +50,8 @@ public class OsLogoComp extends SimpleComp {
|
|||
|
||||
return getImage(ons.getOsName());
|
||||
},
|
||||
wrapper.getPersistentState(), state));
|
||||
wrapper.getPersistentState(),
|
||||
state));
|
||||
var hide = BindingsHelper.map(img, s -> s != null);
|
||||
return new StackComp(List.of(
|
||||
new SystemStateComp(state).hide(hide),
|
||||
|
@ -55,9 +59,6 @@ public class OsLogoComp extends SimpleComp {
|
|||
.createRegion();
|
||||
}
|
||||
|
||||
private static final Map<String, String> ICONS = new HashMap<>();
|
||||
private static final String LINUX_DEFAULT = "linux-24.png";
|
||||
|
||||
private String getImage(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
|
@ -66,8 +67,10 @@ public class OsLogoComp extends SimpleComp {
|
|||
if (ICONS.isEmpty()) {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
|
||||
try (var list = Files.list(file)) {
|
||||
list.filter(path -> path.toString().endsWith(".svg") && !path.toString().endsWith(LINUX_DEFAULT))
|
||||
.map(path -> FileNames.getFileName(path.toString())).forEach(path -> {
|
||||
list.filter(path -> path.toString().endsWith(".svg")
|
||||
&& !path.toString().endsWith(LINUX_DEFAULT_SVG))
|
||||
.map(path -> FileNames.getFileName(path.toString()))
|
||||
.forEach(path -> {
|
||||
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
|
||||
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.CompStructure;
|
||||
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.IconButtonComp;
|
||||
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.XPipeDistributionType;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -39,6 +40,32 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
var vbox = new VBox();
|
||||
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");
|
||||
entries.forEach(e -> {
|
||||
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()
|
||||
.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());
|
||||
vbox.getChildren().add(b.createRegion());
|
||||
});
|
||||
|
||||
{
|
||||
var b = new IconButtonComp(
|
||||
"mdal-bug_report",
|
||||
Augment<CompStructure<Button>> simpleBorders = struc -> {
|
||||
struc.get()
|
||||
.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");
|
||||
if (AppLogs.get().isWriteToFile()) {
|
||||
event.attachment(AppLogs.get().getSessionLogsDirectory());
|
||||
}
|
||||
UserReportComp.show(event.build());
|
||||
})
|
||||
.apply(new FancyTooltipAugment<>("reportIssue")).accessibleTextKey("reportIssue");
|
||||
.apply(new FancyTooltipAugment<>("reportIssue"))
|
||||
.apply(simpleBorders)
|
||||
.accessibleTextKey("reportIssue");
|
||||
b.apply(struc -> {
|
||||
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))
|
||||
.apply(new FancyTooltipAugment<>("visitGithubRepository")).accessibleTextKey("visitGithubRepository");
|
||||
.apply(new FancyTooltipAugment<>("visitGithubRepository"))
|
||||
.apply(simpleBorders)
|
||||
.accessibleTextKey("visitGithubRepository");
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 2);
|
||||
});
|
||||
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))
|
||||
.apply(new FancyTooltipAugment<>("discord")).accessibleTextKey("discord");
|
||||
.apply(new FancyTooltipAugment<>("discord"))
|
||||
.apply(simpleBorders)
|
||||
.accessibleTextKey("discord");
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 2);
|
||||
});
|
||||
|
@ -102,7 +160,8 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
{
|
||||
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
||||
.apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip");
|
||||
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"))
|
||||
.accessibleTextKey("updateAvailableTooltip");
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 2);
|
||||
});
|
||||
|
@ -123,7 +182,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
filler.setMaxHeight(3000);
|
||||
vbox.getChildren().add(filler);
|
||||
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");
|
||||
return new SimpleCompStructure<>(vbox);
|
||||
|
|
|
@ -15,6 +15,7 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
|||
private final Comp<?> center;
|
||||
private Double initialWidth;
|
||||
private Consumer<Double> onDividerChange;
|
||||
|
||||
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||
this.left = left;
|
||||
this.center = center;
|
||||
|
@ -36,13 +37,13 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
|||
}
|
||||
|
||||
if (!setInitial.get() && initialWidth != null) {
|
||||
r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue());
|
||||
r.getDividers().getFirst().setPosition(initialWidth / newValue.doubleValue());
|
||||
setInitial.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -52,7 +53,7 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
|||
}
|
||||
});
|
||||
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) {
|
||||
|
|
|
@ -39,7 +39,7 @@ public class StoreToggleComp extends SimpleComp {
|
|||
},
|
||||
section.getWrapper().getValidity(),
|
||||
section.getShowDetails()));
|
||||
var t = new NamedToggleComp(value, AppI18n.observable(nameKey))
|
||||
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
|
||||
.visible(visible)
|
||||
.disable(disable);
|
||||
value.addListener((observable, oldValue, newValue) -> {
|
||||
|
|
|
@ -18,29 +18,12 @@ import org.kordamp.ikonli.javafx.StackedFontIcon;
|
|||
@Getter
|
||||
public class SystemStateComp extends SimpleComp {
|
||||
|
||||
private final ObservableValue<State> state;
|
||||
|
||||
public SystemStateComp(ObservableValue<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
|
||||
protected Region createSimple() {
|
||||
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
||||
|
@ -58,15 +41,19 @@ public class SystemStateComp extends SimpleComp {
|
|||
border.getStyleClass().add("outer-icon");
|
||||
border.setOpacity(0.5);
|
||||
|
||||
var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-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 success = Styles.toDataURI(
|
||||
".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-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();
|
||||
pane.getChildren().addAll(fi, border);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
|
||||
var dataClass1 = """
|
||||
var dataClass1 =
|
||||
"""
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 22px;
|
||||
}
|
||||
|
@ -78,9 +65,31 @@ public class SystemStateComp extends SimpleComp {
|
|||
|
||||
SimpleChangeListener.apply(PlatformThread.sync(state), val -> {
|
||||
pane.getStylesheets().removeAll(success, failure, other);
|
||||
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other);
|
||||
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure : other);
|
||||
});
|
||||
|
||||
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
|
||||
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
|
||||
public Structure createBase() {
|
||||
var bt = new Button();
|
||||
|
@ -68,7 +80,13 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||
fi.setIconSize((int) (size * 0.55));
|
||||
});
|
||||
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
|
||||
|
@ -85,16 +103,4 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||
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.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
|
@ -32,16 +31,26 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||
: Comp.empty();
|
||||
information.setGraphic(state.createRegion());
|
||||
|
||||
var summary = wrapper.getSummary();
|
||||
var info = wrapper.getEntry().getProvider().informationString(wrapper);
|
||||
SimpleChangeListener.apply(grid.hoverProperty(), val -> {
|
||||
if (val && summary.getValue() != null && wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
||||
information.textProperty().bind(PlatformThread.sync(summary));
|
||||
var summary = wrapper.getSummary();
|
||||
if (wrapper.getEntry().getProvider() != null) {
|
||||
information
|
||||
.textProperty()
|
||||
.bind(PlatformThread.sync(Bindings.createStringBinding(
|
||||
() -> {
|
||||
var val = summary.getValue();
|
||||
if (val != null
|
||||
&& grid.isHover()
|
||||
&& wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
||||
return val;
|
||||
} else {
|
||||
information.textProperty().bind(PlatformThread.sync(info));
|
||||
|
||||
return info.getValue();
|
||||
}
|
||||
},
|
||||
grid.hoverProperty(),
|
||||
info,
|
||||
summary)));
|
||||
}
|
||||
});
|
||||
|
||||
return information;
|
||||
}
|
||||
|
@ -51,9 +60,12 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||
grid.setHgap(8);
|
||||
|
||||
var name = createName().createRegion();
|
||||
name.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
name.maxWidthProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return grid.getWidth() / 2.5;
|
||||
}, grid.widthProperty()));
|
||||
},
|
||||
grid.widthProperty()));
|
||||
|
||||
if (showIcon) {
|
||||
var storeIcon = createIcon(30, 24);
|
||||
|
|
|
@ -12,7 +12,6 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||
super(entry, content);
|
||||
}
|
||||
|
||||
|
||||
protected Region createContent() {
|
||||
var name = createName().createRegion();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
@ -60,12 +60,12 @@ public class StoreCategoryWrapper {
|
|||
return StoreViewState.get().getCategories().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
||||
.findAny().orElse(null);
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public boolean contains(DataStoreEntry entry) {
|
||||
return entry.getCategoryUuid().equals(category.getUuid())
|
||||
|| children.stream().anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry));
|
||||
public boolean contains(StoreEntryWrapper entry) {
|
||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid()) || containedEntries.contains(entry);
|
||||
}
|
||||
|
||||
public void select() {
|
||||
|
@ -87,6 +87,10 @@ public class StoreCategoryWrapper {
|
|||
update();
|
||||
}));
|
||||
|
||||
AppPrefs.get().showChildCategoriesInParentCategory().addListener((observable, oldValue, newValue) -> {
|
||||
update();
|
||||
});
|
||||
|
||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||
category.setSortMode(newValue);
|
||||
});
|
||||
|
@ -117,15 +121,21 @@ public class StoreCategoryWrapper {
|
|||
share.setValue(category.isShare());
|
||||
|
||||
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());
|
||||
children.setAll(StoreViewState.get().getCategories().stream()
|
||||
.filter(storeCategoryWrapper -> getCategory()
|
||||
.getUuid()
|
||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||
.toList());
|
||||
Optional.ofNullable(getParent())
|
||||
.ifPresent(storeCategoryWrapper -> {
|
||||
Optional.ofNullable(getParent()).ifPresent(storeCategoryWrapper -> {
|
||||
storeCategoryWrapper.update();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
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.MultiStepComp;
|
||||
import io.xpipe.app.comp.base.PopupMenuButtonComp;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
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.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
|
@ -25,12 +27,12 @@ import javafx.beans.binding.Bindings;
|
|||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
|
@ -41,9 +43,10 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Predicate;
|
||||
|
||||
@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<DataStore> store;
|
||||
Predicate<DataStoreProvider> filter;
|
||||
|
@ -53,19 +56,22 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
BooleanProperty finished = new SimpleBooleanProperty();
|
||||
ObservableValue<DataStoreEntry> entry;
|
||||
BooleanProperty changedSinceError = new SimpleBooleanProperty();
|
||||
BooleanProperty skippable = new SimpleBooleanProperty();
|
||||
StringProperty name;
|
||||
DataStoreEntry existingEntry;
|
||||
boolean staticDisplay;
|
||||
|
||||
public StoreCreationComp(
|
||||
MultiStepComp parent,
|
||||
Stage window,
|
||||
Consumer<DataStoreEntry> consumer,
|
||||
Property<DataStoreProvider> provider,
|
||||
Property<DataStore> store,
|
||||
Predicate<DataStoreProvider> filter,
|
||||
String initialName,
|
||||
DataStoreEntry existingEntry,
|
||||
boolean staticDisplay) {
|
||||
this.parent = parent;
|
||||
this.window = window;
|
||||
this.consumer = consumer;
|
||||
this.provider = provider;
|
||||
this.store = store;
|
||||
this.filter = filter;
|
||||
|
@ -97,7 +103,8 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
newValue.validate();
|
||||
});
|
||||
});
|
||||
this.entry = Bindings.createObjectBinding(() -> {
|
||||
this.entry = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (name.getValue() == null || store.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -111,22 +118,27 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
|
||||
var targetCategory = p != null
|
||||
? p.getCategoryUuid()
|
||||
: DataStorage.get()
|
||||
.getSelectedCategory()
|
||||
.getUuid();
|
||||
var rootCategory = DataStorage.get().getRootCategory(DataStorage.get().getStoreCategoryIfPresent(targetCategory).orElseThrow());
|
||||
: DataStorage.get().getSelectedCategory().getUuid();
|
||||
var rootCategory = DataStorage.get()
|
||||
.getRootCategory(DataStorage.get()
|
||||
.getStoreCategoryIfPresent(targetCategory)
|
||||
.orElseThrow());
|
||||
// Don't put connections in the scripts category ever
|
||||
if ((provider.getValue().getCreationCategory() == null || !provider.getValue().getCreationCategory().equals(DataStoreProvider.CreationCategory.SCRIPT)) &&
|
||||
rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
|
||||
targetCategory = DataStorage.get().getDefaultCategory().getUuid();
|
||||
if ((provider.getValue().getCreationCategory() == null
|
||||
|| !provider.getValue()
|
||||
.getCreationCategory()
|
||||
.equals(DataStoreProvider.CreationCategory.SCRIPT))
|
||||
&& rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
|
||||
targetCategory = DataStorage.get()
|
||||
.getDefaultConnectionsCategory()
|
||||
.getUuid();
|
||||
}
|
||||
|
||||
return DataStoreEntry.createNew(
|
||||
UUID.randomUUID(),
|
||||
targetCategory,
|
||||
name.getValue(),
|
||||
store.getValue());
|
||||
}, name, store);
|
||||
UUID.randomUUID(), targetCategory, name.getValue(), store.getValue());
|
||||
},
|
||||
name,
|
||||
store);
|
||||
}
|
||||
|
||||
public static void showEdit(DataStoreEntry e) {
|
||||
|
@ -148,16 +160,23 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
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(
|
||||
null,
|
||||
selected,
|
||||
selected != null ? selected.defaultStore() : null,
|
||||
filter,
|
||||
base != null ? DataStoreProviders.byStore(base) : null,
|
||||
base,
|
||||
dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()),
|
||||
e -> {
|
||||
try {
|
||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||
if (e.getProvider().shouldHaveChildren()) {
|
||||
if (e.getProvider().shouldHaveChildren()
|
||||
&& AppPrefs.get()
|
||||
.openConnectionSearchWindowOnConnectionCreation()
|
||||
.get()) {
|
||||
ScanAlert.showAsync(e);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
@ -178,38 +197,117 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
DataStoreEntry existingEntry) {
|
||||
var prop = new SimpleObjectProperty<>(provider);
|
||||
var store = new SimpleObjectProperty<>(s);
|
||||
var loading = new SimpleBooleanProperty();
|
||||
var name = "addConnection";
|
||||
Platform.runLater(() -> {
|
||||
var stage = AppWindowHelper.sideWindow(
|
||||
AppI18n.get(name),
|
||||
window -> {
|
||||
return new MultiStepComp() {
|
||||
DialogComp.showWindow(
|
||||
"addConnection",
|
||||
stage -> new StoreCreationComp(
|
||||
stage, con, prop, store, filter, initialName, existingEntry, staticDisplay));
|
||||
}
|
||||
|
||||
private final StoreCreationComp creator = new StoreCreationComp(
|
||||
this, prop, store, filter, initialName, existingEntry, staticDisplay);
|
||||
private static boolean showInvalidConfirmAlert() {
|
||||
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
|
||||
protected List<Entry> setup() {
|
||||
loading.bind(creator.busy);
|
||||
return List.of(new Entry(AppI18n.observable("a"), creator));
|
||||
protected List<Comp<?>> customButtons() {
|
||||
return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> {
|
||||
if (showInvalidConfirmAlert()) {
|
||||
commit();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.visible(skippable));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObservableValue<Boolean> busy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish() {
|
||||
window.close();
|
||||
if (creator.entry.getValue() != null) {
|
||||
con.accept(creator.entry.getValue());
|
||||
if (finished.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
public Comp<?> bottom() {
|
||||
var disable = Bindings.createBooleanBinding(
|
||||
|
@ -219,7 +317,9 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
|| !store.getValue().isComplete()
|
||||
// When switching providers, both observables change one after another.
|
||||
// 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,
|
||||
|
@ -238,17 +338,6 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
.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) {
|
||||
return new OptionsBuilder()
|
||||
.addComp(comp, store)
|
||||
|
@ -259,18 +348,26 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<? extends Region> createBase() {
|
||||
var back = Comp.of(this::createLayout);
|
||||
var message = new ErrorOverlayComp(back, messageProp);
|
||||
return message.createStructure();
|
||||
private void commit() {
|
||||
if (finished.get()) {
|
||||
return;
|
||||
}
|
||||
finished.setValue(true);
|
||||
|
||||
if (entry.getValue() != null) {
|
||||
consumer.accept(entry.getValue());
|
||||
}
|
||||
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
|
||||
private Region createLayout() {
|
||||
var layout = new BorderPane();
|
||||
layout.getStyleClass().add("store-creator");
|
||||
layout.setPadding(new Insets(20));
|
||||
var providerChoice = new DataStoreProviderChoiceComp(filter, provider, staticDisplay);
|
||||
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
|
||||
if (staticDisplay) {
|
||||
providerChoice.apply(struc -> struc.get().setDisable(true));
|
||||
}
|
||||
|
@ -297,93 +394,9 @@ public class StoreCreationComp extends MultiStepComp.Step<CompStructure<?>> {
|
|||
|
||||
var sep = new Separator();
|
||||
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");
|
||||
layout.setTop(top);
|
||||
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(new SeparatorMenuItem());
|
||||
|
||||
menu.getItems().add(category("addHost", "mdi2h-home-plus",
|
||||
DataStoreProvider.CreationCategory.HOST, "ssh"));
|
||||
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh"));
|
||||
|
||||
menu.getItems().add(category("addShell", "mdi2t-text-box-multiple",
|
||||
DataStoreProvider.CreationCategory.SHELL, null));
|
||||
menu.getItems()
|
||||
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
|
||||
|
||||
menu.getItems().add(category("addScript", "mdi2s-script-text-outline",
|
||||
DataStoreProvider.CreationCategory.SCRIPT, "script"));
|
||||
menu.getItems()
|
||||
.add(category(
|
||||
"addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script"));
|
||||
|
||||
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than",
|
||||
DataStoreProvider.CreationCategory.COMMAND, "cmd"));
|
||||
menu.getItems()
|
||||
.add(category(
|
||||
"addCommand", "mdi2c-code-greater-than", DataStoreProvider.CreationCategory.COMMAND, "cmd"));
|
||||
|
||||
menu.getItems().add(category("addTunnel", "mdi2v-vector-polyline-plus",
|
||||
DataStoreProvider.CreationCategory.TUNNEL, null));
|
||||
menu.getItems()
|
||||
.add(category(
|
||||
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreProvider.CreationCategory.TUNNEL, null));
|
||||
|
||||
menu.getItems().add(category("addCluster", "mdi2d-domain-plus",
|
||||
DataStoreProvider.CreationCategory.CLUSTER, null));
|
||||
|
||||
menu.getItems().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) {
|
||||
var sub = DataStoreProviders.getAll().stream().filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())).toList();
|
||||
private static MenuItem category(
|
||||
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) {
|
||||
var item = new MenuItem();
|
||||
item.setGraphic(new FontIcon(graphic));
|
||||
item.textProperty().bind(AppI18n.observable(name));
|
||||
item.setOnAction(event -> {
|
||||
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null,
|
||||
v -> category.equals(v.getCreationCategory()));
|
||||
StoreCreationComp.showCreation(
|
||||
defaultProvider != null
|
||||
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||
: null,
|
||||
category);
|
||||
event.consume();
|
||||
});
|
||||
return item;
|
||||
|
@ -68,16 +73,19 @@ public class StoreCreationMenu {
|
|||
return;
|
||||
}
|
||||
|
||||
StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null,
|
||||
v -> category.equals(v.getCreationCategory()));
|
||||
StoreCreationComp.showCreation(
|
||||
defaultProvider != null
|
||||
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||
: null,
|
||||
category);
|
||||
event.consume();
|
||||
});
|
||||
sub.forEach(dataStoreProvider -> {
|
||||
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 -> {
|
||||
StoreCreationComp.showCreation(dataStoreProvider,
|
||||
v -> category.equals(v.getCreationCategory()));
|
||||
StoreCreationComp.showCreation(dataStoreProvider, category);
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(item);
|
||||
|
|
|
@ -40,24 +40,6 @@ import java.util.Arrays;
|
|||
|
||||
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 INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
||||
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
|
||||
|
@ -72,6 +54,29 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
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
|
||||
protected final Region createSimple() {
|
||||
var r = createContent();
|
||||
|
@ -83,8 +88,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
button.setPadding(Insets.EMPTY);
|
||||
button.setMaxWidth(5000);
|
||||
button.setFocusTraversable(true);
|
||||
button.accessibleTextProperty()
|
||||
.bind(wrapper.nameProperty());
|
||||
button.accessibleTextProperty().bind(wrapper.nameProperty());
|
||||
button.setOnAction(event -> {
|
||||
event.consume();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
|
@ -105,8 +109,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
protected Label createInformation() {
|
||||
var information = new Label();
|
||||
information.setGraphicTextGap(7);
|
||||
information.textProperty().bind(wrapper.getEntry().getProvider() != null ?
|
||||
PlatformThread.sync(wrapper.getEntry().getProvider().informationString(wrapper)) : new SimpleStringProperty());
|
||||
information
|
||||
.textProperty()
|
||||
.bind(
|
||||
wrapper.getEntry().getProvider() != null
|
||||
? PlatformThread.sync(
|
||||
wrapper.getEntry().getProvider().informationString(wrapper))
|
||||
: new SimpleStringProperty());
|
||||
information.getStyleClass().add("information");
|
||||
AppFont.header(information);
|
||||
|
||||
|
@ -191,15 +200,16 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
continue;
|
||||
}
|
||||
|
||||
var button = new IconButtonComp(
|
||||
actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
|
||||
var button =
|
||||
new IconButtonComp(actionProvider.getIcon(wrapper.getEntry().ref()), () -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var action = actionProvider.createAction(
|
||||
wrapper.getEntry().ref());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
||||
button.accessibleText(
|
||||
actionProvider.getName(wrapper.getEntry().ref()).getValue());
|
||||
button.apply(new FancyTooltipAugment<>(
|
||||
actionProvider.getName(wrapper.getEntry().ref())));
|
||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
||||
|
@ -213,11 +223,11 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
var settingsButton = createSettingsButton();
|
||||
list.add(settingsButton);
|
||||
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++) {
|
||||
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 -> {
|
||||
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
|
||||
|
@ -264,11 +274,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
? new Menu(null, new FontIcon(icon))
|
||||
: new MenuItem(null, new FontIcon(icon));
|
||||
|
||||
var proRequired = p.getKey().getProFeatureId() != null &&
|
||||
!LicenseProvider.get().getFeature(p.getKey().getProFeatureId()).isSupported();
|
||||
var proRequired = p.getKey().getProFeatureId() != null
|
||||
&& !LicenseProvider.get()
|
||||
.getFeature(p.getKey().getProFeatureId())
|
||||
.isSupported();
|
||||
if (proRequired) {
|
||||
item.setDisable(true);
|
||||
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)",name));
|
||||
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
|
||||
} else {
|
||||
item.textProperty().bind(name);
|
||||
}
|
||||
|
@ -285,8 +297,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
|
||||
contextMenu.hide();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var action = actionProvider.createAction(
|
||||
wrapper.getEntry().ref());
|
||||
var action = actionProvider.createAction(wrapper.getEntry().ref());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
|
@ -302,20 +313,27 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
run.textProperty().bind(AppI18n.observable("base.execute"));
|
||||
run.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
p.getKey().getDataStoreCallSite().createAction(wrapper.getEntry().ref()).execute();
|
||||
p.getKey()
|
||||
.getDataStoreCallSite()
|
||||
.createAction(wrapper.getEntry().ref())
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
menu.getItems().add(run);
|
||||
|
||||
|
||||
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
|
||||
var url = "xpipe://action/" + p.getKey().getId() + "/"
|
||||
+ wrapper.getEntry().getUuid();
|
||||
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
|
||||
sc.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
DesktopShortcuts.create(url,
|
||||
wrapper.nameProperty().getValue() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().ref()).getValue() + ")");
|
||||
DesktopShortcuts.create(
|
||||
url,
|
||||
wrapper.nameProperty().getValue() + " ("
|
||||
+ p.getKey()
|
||||
.getDataStoreCallSite()
|
||||
.getName(wrapper.getEntry().ref())
|
||||
.getValue() + ")");
|
||||
});
|
||||
});
|
||||
menu.getItems().add(sc);
|
||||
|
@ -345,9 +363,12 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
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"));
|
||||
StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue().getRoot()).forEach(storeCategoryWrapper -> {
|
||||
StoreViewState.get()
|
||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||
.forEach(storeCategoryWrapper -> {
|
||||
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
||||
m.setOnAction(event -> {
|
||||
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"));
|
||||
del.disableProperty().bind(Bindings.createBooleanBinding(() -> {
|
||||
return !wrapper.getDeletable().get() && !AppPrefs.get().developerDisableGuiRestrictions().get();
|
||||
}, wrapper.getDeletable(), AppPrefs.get().developerDisableGuiRestrictions()));
|
||||
del.disableProperty()
|
||||
.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return !wrapper.getDeletable().get()
|
||||
&& !AppPrefs.get()
|
||||
.developerDisableGuiRestrictions()
|
||||
.get();
|
||||
},
|
||||
wrapper.getDeletable(),
|
||||
AppPrefs.get().developerDisableGuiRestrictions()));
|
||||
del.setOnAction(event -> wrapper.delete());
|
||||
contextMenu.getItems().add(del);
|
||||
|
||||
|
|
|
@ -35,10 +35,18 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
var showIntro = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
var all = StoreViewState.get().getAllConnectionsCategory();
|
||||
var connections = StoreViewState.get().getAllEntries().stream().filter(wrapper -> all.contains(wrapper.getEntry())).toList();
|
||||
return initialCount == connections.size() && StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory());
|
||||
var connections = StoreViewState.get().getAllEntries().stream()
|
||||
.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>>();
|
||||
map.put(
|
||||
createList(),
|
||||
|
|
|
@ -37,23 +37,43 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
public StoreEntryListStatusComp() {
|
||||
this.sortMode = new SimpleObjectProperty<>();
|
||||
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
|
||||
sortMode.unbind();
|
||||
sortMode.bindBidirectional(val.getSortMode());
|
||||
sortMode.setValue(val.getSortMode().getValue());
|
||||
});
|
||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||
var cat = StoreViewState.get().getActiveCategory().getValue();
|
||||
if (cat == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cat.getSortMode().setValue(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
private Region createGroupListHeader() {
|
||||
var label = new Label();
|
||||
label.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "Connections" : "Scripts";
|
||||
}, StoreViewState.get().getActiveCategory()));
|
||||
label.textProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return StoreViewState.get()
|
||||
.getActiveCategory()
|
||||
.getValue()
|
||||
.getRoot()
|
||||
.equals(StoreViewState.get().getAllConnectionsCategory())
|
||||
? "Connections"
|
||||
: "Scripts";
|
||||
},
|
||||
StoreViewState.get().getActiveCategory()));
|
||||
label.getStyleClass().add("name");
|
||||
|
||||
var all = BindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
storeEntryWrapper -> {
|
||||
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());
|
||||
var shownList = BindingsHelper.filteredContentBinding(
|
||||
|
@ -66,7 +86,13 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
var count = new CountComp<>(shownList, all);
|
||||
|
||||
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(c, 3);
|
||||
topBar.setAlignment(Pos.CENTER);
|
||||
|
@ -104,7 +130,6 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
f.setPadding(new Insets(-3, 0, -3, 0));
|
||||
}
|
||||
|
||||
|
||||
AppFont.medium(hbox);
|
||||
return hbox;
|
||||
}
|
||||
|
|
|
@ -121,7 +121,10 @@ public class StoreEntryWrapper {
|
|||
deletable.setValue(entry.getConfiguration().isDeletable()
|
||||
|| 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()) {
|
||||
summary.setValue(null);
|
||||
|
@ -155,8 +158,7 @@ public class StoreEntryWrapper {
|
|||
&& e.getDefaultDataStoreCallSite()
|
||||
.getApplicableClass()
|
||||
.isAssignableFrom(entry.getStore().getClass())
|
||||
&& e.getDefaultDataStoreCallSite()
|
||||
.isApplicable(entry.ref()))
|
||||
&& e.getDefaultDataStoreCallSite().isApplicable(entry.ref()))
|
||||
.findFirst()
|
||||
.map(ActionProvider::getDefaultDataStoreCallSite)
|
||||
.orElse(null);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
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.util.ScanAlert;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
@ -23,23 +22,23 @@ public class StoreIntroComp extends SimpleComp {
|
|||
@Override
|
||||
public Region createSimple() {
|
||||
var title = new Label(AppI18n.get("storeIntroTitle"));
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
AppFont.setSize(title, 7);
|
||||
|
||||
var introDesc = new Label(AppI18n.get("storeIntroDescription"));
|
||||
|
||||
var mfi = new FontIcon("mdi2p-playlist-plus");
|
||||
var machine = new Label(AppI18n.get("storeMachineDescription"));
|
||||
machine.heightProperty().addListener((c, o, n) -> {
|
||||
mfi.iconSizeProperty().set(n.intValue());
|
||||
});
|
||||
introDesc.setWrapText(true);
|
||||
introDesc.setMaxWidth(470);
|
||||
|
||||
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
||||
scanButton.setDefaultButton(true);
|
||||
var scanPane = new StackPane(scanButton);
|
||||
scanPane.setAlignment(Pos.CENTER);
|
||||
|
||||
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion();
|
||||
var text = new VBox(title, introDesc, new Separator(Orientation.HORIZONTAL), machine);
|
||||
var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150)
|
||||
.createRegion();
|
||||
var text = new VBox(title, introDesc);
|
||||
text.setSpacing(5);
|
||||
text.setAlignment(Pos.CENTER_LEFT);
|
||||
var hbox = new HBox(img, text);
|
||||
hbox.setSpacing(35);
|
||||
|
|
|
@ -19,10 +19,12 @@ public class StoreLayoutComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
|
||||
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
|
||||
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
|
||||
.withOnDividerChange(aDouble -> {
|
||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||
}).createStructure();
|
||||
})
|
||||
.createStructure();
|
||||
struc.getLeft().setMinWidth(260);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
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
|
||||
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;
|
||||
ObservableList<StoreSection> allChildren;
|
||||
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(
|
||||
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
if (category == null) {
|
||||
|
@ -63,14 +63,16 @@ public class StoreSection {
|
|||
|
||||
var c = Comparator.<StoreSection>comparingInt(
|
||||
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(
|
||||
list,
|
||||
(o1, o2) -> {
|
||||
var current = mappedSortMode.getValue();
|
||||
if (current != null) {
|
||||
return c.thenComparing(current.comparator())
|
||||
.compare(o1, o2);
|
||||
.compare(current.representative(o1), current.representative(o2));
|
||||
} else {
|
||||
return c.compare(o1, o2);
|
||||
}
|
||||
|
@ -97,7 +99,9 @@ public class StoreSection {
|
|||
section -> {
|
||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||
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;
|
||||
},
|
||||
category,
|
||||
|
@ -117,11 +121,11 @@ public class StoreSection {
|
|||
}
|
||||
|
||||
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
||||
// Legacy implementation that does not use caches. Use for testing
|
||||
// if (true) return DataStorage.get()
|
||||
// .getDisplayParent(other.getEntry())
|
||||
// .map(found -> found.equals(e.getEntry()))
|
||||
// .orElse(false);
|
||||
// Legacy implementation that does not use children caches. Use for testing
|
||||
// if (true) return DataStorage.get()
|
||||
// .getDisplayParent(other.getEntry())
|
||||
// .map(found -> found.equals(e.getEntry()))
|
||||
// .orElse(false);
|
||||
|
||||
// This check is fast as the children are cached in the storage
|
||||
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||
|
@ -134,9 +138,13 @@ public class StoreSection {
|
|||
section -> {
|
||||
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
|
||||
// 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());
|
||||
var sameCategory = category == null
|
||||
|| category.getValue() == null
|
||||
|| 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;
|
||||
},
|
||||
category,
|
||||
|
|
|
@ -22,12 +22,11 @@ import java.util.List;
|
|||
|
||||
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 SUB = PseudoClass.getPseudoClass("sub");
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
|
||||
private final StoreSection section;
|
||||
private final boolean topLevel;
|
||||
|
||||
|
@ -38,7 +37,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var root = StandardStoreEntryComp.customSection(section, topLevel)
|
||||
var root = StoreEntryComp.customSection(section, topLevel)
|
||||
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
|
@ -54,9 +53,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
.apply(struc -> struc.get().setMinWidth(30))
|
||||
.apply(struc -> struc.get().setPrefWidth(30))
|
||||
.focusTraversable()
|
||||
.accessibleText(Bindings.createStringBinding(() -> {
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
}, section.getWrapper().getName()))
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(BindingsHelper.persist(
|
||||
Bindings.size(section.getShownChildren()).isEqualTo(0)))
|
||||
.grow(false, true)
|
||||
|
|
|
@ -28,19 +28,18 @@ import java.util.function.BiConsumer;
|
|||
@Builder
|
||||
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
public static Comp<?> createList(StoreSection top, BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment) {
|
||||
return new StoreSectionMiniComp(top, augment);
|
||||
}
|
||||
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
|
||||
private final StoreSection section;
|
||||
|
||||
@Builder.Default
|
||||
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
|
||||
public CompStructure<VBox> createBase() {
|
||||
var list = new ArrayList<Comp<?>>();
|
||||
|
@ -48,14 +47,14 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
if (section.getWrapper() != null) {
|
||||
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
||||
.apply(struc -> {
|
||||
var provider = section.getWrapper()
|
||||
.getEntry()
|
||||
.getProvider();
|
||||
var provider = section.getWrapper().getEntry().getProvider();
|
||||
struc.get()
|
||||
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(provider != null ? provider
|
||||
.getDisplayIconFileName(section.getWrapper()
|
||||
.setGraphic(PrettyImageHelper.ofFixedSmallSquare(
|
||||
provider != null
|
||||
? provider.getDisplayIconFileName(section.getWrapper()
|
||||
.getEntry()
|
||||
.getStore()) : null)
|
||||
.getStore())
|
||||
: null)
|
||||
.createRegion());
|
||||
})
|
||||
.apply(struc -> {
|
||||
|
@ -79,31 +78,40 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
.apply(struc -> struc.get().setMinWidth(20))
|
||||
.apply(struc -> struc.get().setPrefWidth(20))
|
||||
.focusTraversable()
|
||||
.accessibleText(Bindings.createStringBinding(() -> {
|
||||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
}, section.getWrapper().getName()))
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Expand "
|
||||
+ section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(BindingsHelper.persist(
|
||||
Bindings.size(section.getAllChildren()).isEqualTo(0)))
|
||||
.grow(false, true)
|
||||
.styleClass("expand-button");
|
||||
List<Comp<?>> topEntryList = List.of(button, root);
|
||||
list.add(new HorizontalComp(topEntryList)
|
||||
.apply(struc -> struc.get().setFillHeight(true)));
|
||||
list.add(new HorizontalComp(topEntryList).apply(struc -> struc.get().setFillHeight(true)));
|
||||
} else {
|
||||
expanded = new SimpleBooleanProperty(true);
|
||||
}
|
||||
|
||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = section.getWrapper() != null ? BindingsHelper.filteredContentBinding(
|
||||
var listSections = section.getWrapper() != null
|
||||
? BindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20
|
||||
|| expanded.get(),
|
||||
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||
expanded,
|
||||
section.getAllChildren()) : section.getShownChildren();
|
||||
section.getAllChildren())
|
||||
: section.getShownChildren();
|
||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||
return StoreSectionMiniComp.builder().section(e).augment(this.augment).build();
|
||||
}).withLimit(100).minHeight(0).hgrow();
|
||||
return StoreSectionMiniComp.builder()
|
||||
.section(e)
|
||||
.augment(this.augment)
|
||||
.build();
|
||||
})
|
||||
.withLimit(100)
|
||||
.minHeight(0)
|
||||
.hgrow();
|
||||
|
||||
list.add(new HorizontalComp(List.of(content))
|
||||
.styleClass("content")
|
||||
|
@ -130,8 +138,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
return;
|
||||
}
|
||||
|
||||
struc.get().getStyleClass().removeIf(
|
||||
s -> Arrays.stream(DataStoreColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
|
||||
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values())
|
||||
.anyMatch(dataStoreColor ->
|
||||
dataStoreColor.getId().equals(s)));
|
||||
struc.get().getStyleClass().remove("none");
|
||||
struc.get().getStyleClass().add("color-box");
|
||||
if (val != null) {
|
||||
|
|
|
@ -14,9 +14,18 @@ public class StoreSidebarComp extends SimpleComp {
|
|||
protected Region createSimple() {
|
||||
var sideBar = new VerticalComp(List.of(
|
||||
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").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()));
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory())
|
||||
.styleClass("color-box")
|
||||
.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.styleClass("sidebar");
|
||||
sideBar.prefWidth(240);
|
||||
|
|
|
@ -12,6 +12,11 @@ import java.util.stream.Stream;
|
|||
public interface StoreSortMode {
|
||||
|
||||
StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
|
||||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "alphabetical-desc";
|
||||
|
@ -23,8 +28,12 @@ public interface StoreSortMode {
|
|||
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
};
|
||||
|
||||
StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
|
||||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "alphabetical-asc";
|
||||
|
@ -37,8 +46,21 @@ public interface StoreSortMode {
|
|||
.reversed();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
public String getId() {
|
||||
return "date-desc";
|
||||
|
@ -54,8 +76,21 @@ public interface 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
|
||||
public String getId() {
|
||||
return "date-asc";
|
||||
|
@ -68,9 +103,11 @@ public interface StoreSortMode {
|
|||
.map(entry -> entry.getLastAccess())
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElseThrow();
|
||||
}).reversed();
|
||||
})
|
||||
.reversed();
|
||||
}
|
||||
};
|
||||
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
|
||||
|
||||
static Stream<DataStoreEntry> flatten(StoreSection section) {
|
||||
return Stream.concat(
|
||||
|
@ -78,14 +115,14 @@ public interface StoreSortMode {
|
|||
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) {
|
||||
return ALL.stream()
|
||||
.filter(storeSortMode -> storeSortMode.getId().equals(id))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
StoreSection representative(StoreSection s);
|
||||
|
||||
String getId();
|
||||
|
||||
Comparator<StoreSection> comparator();
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.comp.store;
|
|||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
|
@ -23,6 +24,26 @@ import java.util.stream.Collectors;
|
|||
public class StoreViewState {
|
||||
|
||||
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() {
|
||||
if (INSTANCE != null) {
|
||||
|
@ -52,27 +73,6 @@ public class StoreViewState {
|
|||
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() {
|
||||
categories.forEach(c -> c.update());
|
||||
allEntries.forEach(e -> e.update());
|
||||
|
@ -112,12 +112,27 @@ public class StoreViewState {
|
|||
.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!
|
||||
DataStorage.get().addListener(new StorageListener() {
|
||||
@Override
|
||||
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(() -> {
|
||||
// Don't update anything if we have already reset
|
||||
if (INSTANCE == null) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.Main;
|
||||
import io.xpipe.app.comp.AppLayoutComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
|
@ -12,8 +11,8 @@ import javafx.application.Application;
|
|||
import javafx.beans.binding.Bindings;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
|
||||
@Getter
|
||||
|
@ -27,26 +26,13 @@ public class App extends Application {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void start(Stage primaryStage) {
|
||||
TrackEvent.info("Application launched");
|
||||
APP = this;
|
||||
stage = primaryStage;
|
||||
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)) {
|
||||
Desktop.getDesktop().setPreferencesHandler(e -> {
|
||||
AppLayoutModel.get().selectSettings();
|
||||
|
@ -56,7 +42,8 @@ public class App extends Application {
|
|||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||
try {
|
||||
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.set(xToolkit, "XPipe");
|
||||
} catch (Exception e) {
|
||||
|
@ -103,10 +90,7 @@ public class App extends Application {
|
|||
|
||||
public void focus() {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.setAlwaysOnTop(false);
|
||||
stage.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ public class AppBundledFonts {
|
|||
return;
|
||||
}
|
||||
|
||||
System.setProperty("prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
|
||||
System.setProperty(
|
||||
"prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
|
||||
System.setProperty("prism.embeddedfonts", "true");
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ public class AppDebugModeNotice {
|
|||
}
|
||||
|
||||
var out = AppLogs.get().getOriginalSysOut();
|
||||
var msg = """
|
||||
var msg =
|
||||
"""
|
||||
|
||||
****************************************
|
||||
* 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.ext.ExtensionException;
|
||||
import io.xpipe.app.ext.ModuleInstall;
|
||||
import io.xpipe.app.ext.XPipeServiceProviders;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
@ -29,6 +28,7 @@ public class AppExtensionManager {
|
|||
private final List<ModuleLayer> leafModuleLayers = new ArrayList<>();
|
||||
private final List<Path> extensionBaseDirectories = new ArrayList<>();
|
||||
private ModuleLayer baseLayer = ModuleLayer.boot();
|
||||
|
||||
@Getter
|
||||
private ModuleLayer extendedLayer;
|
||||
|
||||
|
@ -52,11 +52,20 @@ public class AppExtensionManager {
|
|||
XPipeServiceProviders.load(INSTANCE.extendedLayer);
|
||||
MessageExchangeImpls.loadAll();
|
||||
} 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() {
|
||||
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
|
||||
if (baseModule.isEmpty()) {
|
||||
|
@ -95,14 +104,6 @@ public class AppExtensionManager {
|
|||
extensionBaseDirectories.add(productionRoot);
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
INSTANCE = null;
|
||||
}
|
||||
|
||||
public static AppExtensionManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public Set<Module> getContentModules() {
|
||||
return Stream.concat(
|
||||
Stream.of(ModuleLayer.boot().findModule("io.xpipe.app").orElseThrow()),
|
||||
|
@ -110,87 +111,28 @@ public class AppExtensionManager {
|
|||
.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() {
|
||||
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
||||
loadExtensionRootDirectory(extensionBaseDirectory);
|
||||
for (var ext : List.of("jdbc", "proc", "uacc")) {
|
||||
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 cfs = leafModuleLayers.stream().map(ModuleLayer::configuration).toList();
|
||||
var finder = ModuleFinder.ofSystem();
|
||||
var cf = Configuration.resolve(finder, cfs, finder, List.of());
|
||||
extendedLayer = ModuleLayer.defineModulesWithOneLoader(cf, leafModuleLayers, scl)
|
||||
.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) {
|
||||
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) {
|
||||
var found = parseExtensionDirectory(extensionBaseDirectory.resolve(name), parent);
|
||||
if (found.isPresent()) {
|
||||
|
@ -206,7 +148,7 @@ public class AppExtensionManager {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (loadedExtensions.stream().anyMatch(extension -> extension.dir.equals(dir))
|
||||
if (loadedExtensions.stream().anyMatch(extension -> dir.equals(extension.dir))
|
||||
|| loadedExtensions.stream()
|
||||
.anyMatch(extension ->
|
||||
extension.id.equals(dir.getFileName().toString()))) {
|
||||
|
|
|
@ -107,6 +107,7 @@ public class AppFileWatcher {
|
|||
|
||||
private class WatchedDirectory {
|
||||
private final BiConsumer<Path, WatchEvent.Kind<Path>> listener;
|
||||
|
||||
@Getter
|
||||
private final Path baseDir;
|
||||
|
||||
|
@ -114,9 +115,7 @@ public class AppFileWatcher {
|
|||
this.baseDir = dir;
|
||||
this.listener = listener;
|
||||
createRecursiveWatchers(dir);
|
||||
TrackEvent.withTrace("watcher", "Added watched directory")
|
||||
.tag("location", dir)
|
||||
.handle();
|
||||
TrackEvent.withTrace("Added watched directory").tag("location", dir).handle();
|
||||
}
|
||||
|
||||
private void createRecursiveWatchers(Path dir) {
|
||||
|
@ -177,13 +176,12 @@ public class AppFileWatcher {
|
|||
}
|
||||
|
||||
// Handle event
|
||||
TrackEvent.withTrace("watcher", "Watch event")
|
||||
TrackEvent.withTrace("Watch event")
|
||||
.tag("baseDir", baseDir)
|
||||
.tag("file", baseDir.relativize(file))
|
||||
.tag("kind", event.kind().name())
|
||||
.handle();
|
||||
listener.accept(file, ev.kind());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,8 @@ public class AppFont {
|
|||
try (var in = Files.newInputStream(file)) {
|
||||
Font.loadFont(in, OsType.getLocal() == OsType.LINUX ? 11 : 12);
|
||||
} 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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
|
@ -52,7 +51,7 @@ public class AppGreetings {
|
|||
|
||||
public static void showIfNeeded() {
|
||||
boolean set = AppCache.get("legalAccepted", Boolean.class, () -> false);
|
||||
if (set || !AppState.get().isInitialLaunch()) {
|
||||
if (set || AppProperties.get().isDevelopmentEnvironment()) {
|
||||
return;
|
||||
}
|
||||
var read = new SimpleBooleanProperty();
|
||||
|
@ -72,20 +71,21 @@ public class AppGreetings {
|
|||
});
|
||||
|
||||
var acceptanceBox = Comp.of(() -> {
|
||||
var cb = new JFXCheckBox();
|
||||
var cb = new CheckBox();
|
||||
cb.selectedProperty().bindBidirectional(accepted);
|
||||
|
||||
var label = new Label(AppI18n.get("legalAccept"));
|
||||
label.setGraphic(cb);
|
||||
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.setGraphicTextGap(10);
|
||||
return label;
|
||||
})
|
||||
.createRegion();
|
||||
|
||||
var layout = new BorderPane();
|
||||
layout.getStyleClass().add("window-content");
|
||||
layout.setPadding(new Insets(20));
|
||||
layout.setCenter(accordion);
|
||||
layout.setBottom(acceptanceBox);
|
||||
layout.setPrefWidth(700);
|
||||
|
|
|
@ -37,10 +37,10 @@ import java.util.regex.Pattern;
|
|||
public class AppI18n {
|
||||
|
||||
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> markdownDocumentations;
|
||||
private PrettyTime prettyTime;
|
||||
private static final AppI18n INSTANCE = new AppI18n();
|
||||
|
||||
public static void init() {
|
||||
var i = INSTANCE;
|
||||
|
@ -51,7 +51,7 @@ public class AppI18n {
|
|||
i.load();
|
||||
|
||||
if (AppPrefs.get() != null) {
|
||||
AppPrefs.get().language.addListener((c, o, n) -> {
|
||||
AppPrefs.get().language().addListener((c, o, n) -> {
|
||||
i.clear();
|
||||
i.load();
|
||||
});
|
||||
|
@ -98,8 +98,11 @@ public class AppI18n {
|
|||
return "null";
|
||||
}
|
||||
|
||||
return getInstance().prettyTime.formatDuration(
|
||||
getInstance().prettyTime.approximateDuration(Instant.now().plus(duration.getValue())));
|
||||
return getInstance()
|
||||
.prettyTime
|
||||
.formatDuration(getInstance()
|
||||
.prettyTime
|
||||
.approximateDuration(Instant.now().plus(duration.getValue())));
|
||||
},
|
||||
duration);
|
||||
}
|
||||
|
@ -136,20 +139,6 @@ public class AppI18n {
|
|||
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
|
||||
private static String getCallerModuleName() {
|
||||
var callers = CallingClass.INSTANCE.getCallingClasses();
|
||||
|
@ -161,6 +150,7 @@ public class AppI18n {
|
|||
|| caller.equals(FancyTooltipAugment.class)
|
||||
|| caller.equals(PrefsChoiceValue.class)
|
||||
|| caller.equals(Translatable.class)
|
||||
|| caller.equals(AppWindowHelper.class)
|
||||
|| caller.equals(OptionsBuilder.class)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -170,6 +160,11 @@ public class AppI18n {
|
|||
return "";
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
translations.clear();
|
||||
prettyTime = null;
|
||||
}
|
||||
|
||||
public String getKey(String s) {
|
||||
var key = s;
|
||||
if (!s.contains(".")) {
|
||||
|
@ -210,7 +205,7 @@ public class AppI18n {
|
|||
|
||||
private boolean matchesLocale(Path f) {
|
||||
var l = AppPrefs.get() != null
|
||||
? AppPrefs.get().language.getValue().getLocale()
|
||||
? AppPrefs.get().language().getValue().getLocale()
|
||||
: SupportedLocale.ENGLISH.getLocale();
|
||||
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
||||
var ending = "_" + l.toLanguageTag();
|
||||
|
@ -219,7 +214,8 @@ public class AppI18n {
|
|||
|
||||
public String getMarkdownDocumentation(String 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, "");
|
||||
|
@ -311,7 +307,16 @@ public class AppI18n {
|
|||
|
||||
this.prettyTime = new PrettyTime(
|
||||
AppPrefs.get() != null
|
||||
? AppPrefs.get().language.getValue().getLocale()
|
||||
? AppPrefs.get().language().getValue().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.store.StoreLayoutComp;
|
||||
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 javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -18,20 +19,26 @@ import lombok.extern.jackson.Jacksonized;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AppLayoutModel {
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Jacksonized
|
||||
public static class SavedState {
|
||||
|
||||
double sidebarWidth;
|
||||
double browserConnectionsWidth;
|
||||
}
|
||||
|
||||
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() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
@ -46,19 +53,16 @@ public class AppLayoutModel {
|
|||
INSTANCE = null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final SavedState savedState;
|
||||
private final List<Entry> entries;
|
||||
private final Property<Entry> selected;
|
||||
public Property<Entry> getSelectedInternal() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public AppLayoutModel(SavedState savedState) {
|
||||
this.savedState = savedState;
|
||||
this.entries = createEntryList();
|
||||
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
||||
public ObservableValue<Entry> getSelected() {
|
||||
return selectedWrapper;
|
||||
}
|
||||
|
||||
public void selectBrowser() {
|
||||
selected.setValue(entries.get(0));
|
||||
selected.setValue(entries.getFirst());
|
||||
}
|
||||
|
||||
public void selectSettings() {
|
||||
|
@ -75,17 +79,14 @@ public class AppLayoutModel {
|
|||
|
||||
private List<Entry> createEntryList() {
|
||||
var l = new ArrayList<>(List.of(
|
||||
new Entry(
|
||||
AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
|
||||
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
|
||||
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
||||
new Entry(
|
||||
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this))));
|
||||
new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp())));
|
||||
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
||||
// StorageLayoutComp()),
|
||||
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp())
|
||||
if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) {
|
||||
l.add(new Entry(
|
||||
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
||||
l.add(new Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
||||
}
|
||||
|
||||
l.add(new Entry(
|
||||
|
@ -96,5 +97,14 @@ public class AppLayoutModel {
|
|||
return l;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Jacksonized
|
||||
public static class SavedState {
|
||||
|
||||
double sidebarWidth;
|
||||
double browserConnectionsWidth;
|
||||
}
|
||||
|
||||
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.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -32,23 +31,26 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
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_LOGS_PROP = "io.xpipe.app.writeLogs";
|
||||
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 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());
|
||||
private static final DateTimeFormatter MESSAGE_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(ZoneId.systemDefault());
|
||||
|
||||
private static AppLogs INSTANCE;
|
||||
|
||||
@Getter
|
||||
private final PrintStream originalSysOut;
|
||||
|
||||
@Getter
|
||||
private final PrintStream originalSysErr;
|
||||
|
||||
private final Path logDir;
|
||||
|
||||
@Getter
|
||||
|
@ -60,16 +62,15 @@ public class AppLogs {
|
|||
@Getter
|
||||
private final String logLevel;
|
||||
|
||||
private final PrintStream outStream;
|
||||
private final Map<String, PrintStream> categoryWriters;
|
||||
private final PrintStream outFileStream;
|
||||
|
||||
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.writeToSysout = writeToSysout;
|
||||
this.writeToFile = writeToFile;
|
||||
this.logLevel = logLevel;
|
||||
this.outStream = System.out;
|
||||
this.categoryWriters = new HashMap<>();
|
||||
this.outFileStream = outFileStream;
|
||||
|
||||
this.originalSysOut = System.out;
|
||||
this.originalSysErr = System.err;
|
||||
|
@ -96,20 +97,34 @@ public class AppLogs {
|
|||
}
|
||||
|
||||
public static void init() {
|
||||
if (INSTANCE != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var logDir = AppProperties.get().getDataDir().resolve("logs");
|
||||
|
||||
// Regularly clean logs dir
|
||||
if (XPipeSession.get().isNewBuildSession() && Files.exists(logDir)) {
|
||||
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) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
|
||||
var shouldLogToFile = shouldWriteLogs();
|
||||
|
||||
var now = Instant.now();
|
||||
var name = FORMATTER.format(now);
|
||||
var name = NAME_FORMATTER.format(now);
|
||||
Path usedLogsDir = logDir.resolve(name);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
PrintStream outFileStream = null;
|
||||
var shouldLogToFile = shouldWriteLogs();
|
||||
if (shouldLogToFile) {
|
||||
try {
|
||||
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) {
|
||||
ErrorEvent.fromThrowable(ex).build().handle();
|
||||
shouldLogToFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
var shouldLogToSysout = shouldWriteSysout();
|
||||
|
||||
if (shouldLogToFile && outFileStream == null) {
|
||||
TrackEvent.info("Log file initialization failed. Writing to standard out");
|
||||
shouldLogToSysout = true;
|
||||
shouldLogToFile = false;
|
||||
}
|
||||
|
||||
if (shouldLogToFile && !shouldLogToSysout) {
|
||||
TrackEvent.info("Writing log output to " + usedLogsDir + " from now on");
|
||||
}
|
||||
|
||||
var level = determineLogLevel();
|
||||
INSTANCE = new AppLogs(usedLogsDir, shouldLogToSysout, shouldLogToFile, level);
|
||||
INSTANCE = new AppLogs(usedLogsDir, shouldLogToSysout, shouldLogToFile, level, outFileStream);
|
||||
}
|
||||
|
||||
public static void teardown() {
|
||||
|
@ -149,45 +175,19 @@ public class AppLogs {
|
|||
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() {
|
||||
outStream.close();
|
||||
categoryWriters.forEach((k, s) -> {
|
||||
s.close();
|
||||
});
|
||||
if (outFileStream != null) {
|
||||
outFileStream.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() {
|
||||
|
@ -210,12 +210,7 @@ public class AppLogs {
|
|||
return;
|
||||
}
|
||||
|
||||
TrackEvent.builder()
|
||||
.type("info")
|
||||
.category("sysout")
|
||||
.message(line)
|
||||
.build()
|
||||
.handle();
|
||||
TrackEvent.builder().type("info").message(line).build().handle();
|
||||
baos.reset();
|
||||
} else {
|
||||
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) {
|
||||
var deob = Deobfuscator.deobfuscateToString(e);
|
||||
var event = TrackEvent.builder()
|
||||
|
@ -264,9 +250,9 @@ public class AppLogs {
|
|||
}
|
||||
|
||||
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 current = DEFAULT_LEVELS.indexOf(event.getType());
|
||||
int current = LOG_LEVELS.indexOf(event.getType());
|
||||
if (current <= i) {
|
||||
if (writeToSysout) {
|
||||
logSysOut(event);
|
||||
|
@ -281,12 +267,9 @@ public class AppLogs {
|
|||
var time = MESSAGE_FORMATTER.format(event.getInstant());
|
||||
var string =
|
||||
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
||||
if (event.getCategory() != null) {
|
||||
string.append("[").append(event.getCategory()).append("] ");
|
||||
}
|
||||
string.append(event);
|
||||
var toLog = string.toString();
|
||||
outStream.println(toLog);
|
||||
this.originalSysOut.println(toLog);
|
||||
}
|
||||
|
||||
private void logToFile(TrackEvent event) {
|
||||
|
@ -295,8 +278,7 @@ public class AppLogs {
|
|||
new StringBuilder(time).append(" - ").append(event.getType()).append(": ");
|
||||
string.append(event);
|
||||
var toLog = string.toString();
|
||||
getLogStream(event).println(toLog);
|
||||
getCatchAllLogStream().println(toLog);
|
||||
outFileStream.println(toLog);
|
||||
}
|
||||
|
||||
private void setLogLevels() {
|
||||
|
@ -312,10 +294,6 @@ public class AppLogs {
|
|||
}
|
||||
}
|
||||
|
||||
public Path getLogsDirectory() {
|
||||
return logDir.getParent();
|
||||
}
|
||||
|
||||
public Path getSessionLogsDirectory() {
|
||||
return logDir;
|
||||
}
|
||||
|
@ -339,7 +317,7 @@ public class AppLogs {
|
|||
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 {
|
||||
|
||||
private final String name;
|
||||
|
||||
public Slf4jLogger(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFullyQualifiedCallerName() {
|
||||
return "logger";
|
||||
|
@ -390,7 +362,6 @@ public class AppLogs {
|
|||
}
|
||||
}
|
||||
TrackEvent.builder()
|
||||
.category(name)
|
||||
.type(level.toString().toLowerCase())
|
||||
.message(msg)
|
||||
.build()
|
||||
|
@ -399,62 +370,62 @@ public class AppLogs {
|
|||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
return DEFAULT_LEVELS.indexOf("trace")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("trace")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled(Marker marker) {
|
||||
return DEFAULT_LEVELS.indexOf("trace")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("trace")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return DEFAULT_LEVELS.indexOf("debug")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("debug")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled(Marker marker) {
|
||||
return DEFAULT_LEVELS.indexOf("debug")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("debug")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled() {
|
||||
return DEFAULT_LEVELS.indexOf("info")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("info")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled(Marker marker) {
|
||||
return DEFAULT_LEVELS.indexOf("info")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("info")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled() {
|
||||
return DEFAULT_LEVELS.indexOf("warn")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("warn")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled(Marker marker) {
|
||||
return DEFAULT_LEVELS.indexOf("warn")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("warn")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled() {
|
||||
return DEFAULT_LEVELS.indexOf("error")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("error")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled(Marker marker) {
|
||||
return DEFAULT_LEVELS.indexOf("error")
|
||||
<= DEFAULT_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
return LOG_LEVELS.indexOf("error")
|
||||
<= LOG_LEVELS.indexOf(AppLogs.get().getLogLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,6 @@ public class AppMainWindow {
|
|||
|
||||
private void logChange() {
|
||||
TrackEvent.withDebug("Window resize")
|
||||
.windowCategory()
|
||||
.tag("x", stage.getX())
|
||||
.tag("y", stage.getY())
|
||||
.tag("width", stage.getWidth())
|
||||
|
@ -98,7 +97,6 @@ public class AppMainWindow {
|
|||
applyState(state);
|
||||
|
||||
TrackEvent.withDebug("Window initialized")
|
||||
.windowCategory()
|
||||
.tag("x", stage.getX())
|
||||
.tag("y", stage.getY())
|
||||
.tag("width", stage.getWidth())
|
||||
|
|
|
@ -19,15 +19,20 @@ public class AppProperties {
|
|||
private static final String EXTENSION_PATHS_PROP = "io.xpipe.app.extensions";
|
||||
private static AppProperties INSTANCE;
|
||||
boolean fullVersion;
|
||||
|
||||
@Getter
|
||||
String version;
|
||||
|
||||
@Getter
|
||||
String build;
|
||||
|
||||
UUID buildUuid;
|
||||
String sentryUrl;
|
||||
String arch;
|
||||
|
||||
@Getter
|
||||
boolean image;
|
||||
|
||||
boolean staging;
|
||||
boolean useVirtualThreads;
|
||||
boolean debugThreads;
|
||||
|
@ -101,6 +106,10 @@ public class AppProperties {
|
|||
return INSTANCE;
|
||||
}
|
||||
|
||||
public boolean isDevelopmentEnvironment() {
|
||||
return !AppProperties.get().isImage() && AppProperties.get().isDeveloperMode();
|
||||
}
|
||||
|
||||
public boolean isDeveloperMode() {
|
||||
if (AppPrefs.get() == null) {
|
||||
return false;
|
||||
|
@ -108,5 +117,4 @@ public class AppProperties {
|
|||
|
||||
return AppPrefs.get().developerMode().getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,7 +57,10 @@ public class AppSocketServer {
|
|||
.handle();
|
||||
} catch (Exception ex) {
|
||||
// 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 {
|
||||
if (clientSocket.isClosed()) {
|
||||
TrackEvent.trace("beacon", "Socket closed");
|
||||
TrackEvent.trace("Socket closed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -121,14 +124,14 @@ public class AppSocketServer {
|
|||
node = JacksonMapper.getDefault().readTree(blockIn);
|
||||
}
|
||||
if (node.isMissingNode()) {
|
||||
TrackEvent.trace("beacon", "Received EOF");
|
||||
TrackEvent.trace("Received EOF");
|
||||
return false;
|
||||
}
|
||||
|
||||
TrackEvent.trace("beacon", "Received raw request: \n" + node.toPrettyString());
|
||||
TrackEvent.trace("Received raw request: \n" + node.toPrettyString());
|
||||
|
||||
var req = parseRequest(node);
|
||||
TrackEvent.trace("beacon", "Parsed request: \n" + req.toString());
|
||||
TrackEvent.trace("Parsed request: \n" + req.toString());
|
||||
|
||||
var prov = MessageExchangeImpls.byRequest(req);
|
||||
if (prov.isEmpty()) {
|
||||
|
@ -145,19 +148,19 @@ public class AppSocketServer {
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
},
|
||||
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);
|
||||
|
||||
try {
|
||||
|
@ -170,7 +173,6 @@ public class AppSocketServer {
|
|||
}
|
||||
|
||||
TrackEvent.builder()
|
||||
.category("beacon")
|
||||
.type("trace")
|
||||
.message("Socket connection #" + id + " performed exchange "
|
||||
+ req.getClass().getSimpleName())
|
||||
|
@ -187,7 +189,7 @@ public class AppSocketServer {
|
|||
informationNode = JacksonMapper.getDefault().readTree(blockIn);
|
||||
}
|
||||
if (informationNode.isMissingNode()) {
|
||||
TrackEvent.trace("beacon", "Received EOF");
|
||||
TrackEvent.trace("Received EOF");
|
||||
return;
|
||||
}
|
||||
var information =
|
||||
|
@ -197,7 +199,6 @@ public class AppSocketServer {
|
|||
}
|
||||
|
||||
TrackEvent.builder()
|
||||
.category("beacon")
|
||||
.type("trace")
|
||||
.message("Created new socket connection #" + id)
|
||||
.tag("client", information != null ? information.toDisplayString() : "Unknown")
|
||||
|
@ -211,29 +212,29 @@ public class AppSocketServer {
|
|||
}
|
||||
}
|
||||
TrackEvent.builder()
|
||||
.category("beacon")
|
||||
.type("trace")
|
||||
.message("Socket connection #" + id + " finished successfully")
|
||||
.build()
|
||||
.handle();
|
||||
|
||||
} 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());
|
||||
} catch (ServerException se) {
|
||||
TrackEvent.trace("beacon", "Sending server error to #" + id + ": " + se.getMessage());
|
||||
ErrorEvent.fromThrowable(se).build().handle();
|
||||
TrackEvent.trace("Sending server error to #" + id + ": " + se.getMessage());
|
||||
Deobfuscator.deobfuscate(se);
|
||||
sendServerErrorResponse(clientSocket, se);
|
||||
var toReport = se.getCause() != null ? se.getCause() : se;
|
||||
ErrorEvent.fromThrowable(toReport).build().handle();
|
||||
} catch (SocketException ex) {
|
||||
// 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
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
} catch (Throwable ex) {
|
||||
TrackEvent.trace("beacon", "Sending internal server error to #" + id + ": " + ex.getMessage());
|
||||
ErrorEvent.fromThrowable(ex).build().handle();
|
||||
TrackEvent.trace("Sending internal server error to #" + id + ": " + ex.getMessage());
|
||||
Deobfuscator.deobfuscate(ex);
|
||||
sendServerErrorResponse(clientSocket, ex);
|
||||
ErrorEvent.fromThrowable(ex).build().handle();
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// Omit it, as this might happen often
|
||||
|
@ -243,16 +244,13 @@ public class AppSocketServer {
|
|||
} finally {
|
||||
try {
|
||||
clientSocket.close();
|
||||
TrackEvent.trace("beacon", "Closed socket #" + id);
|
||||
TrackEvent.trace("Closed socket #" + id);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).build().handle();
|
||||
}
|
||||
}
|
||||
|
||||
TrackEvent.builder()
|
||||
.category("beacon")
|
||||
.type("trace")
|
||||
.message("Socket connection #" + id + " finished unsuccessfully");
|
||||
TrackEvent.builder().type("trace").message("Socket connection #" + id + " finished unsuccessfully");
|
||||
}
|
||||
|
||||
private void performExchangesAsync(Socket clientSocket) {
|
||||
|
@ -296,7 +294,7 @@ public class AppSocketServer {
|
|||
}
|
||||
|
||||
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())) {
|
||||
blockOut.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
@ -336,7 +334,7 @@ public class AppSocketServer {
|
|||
|
||||
private <T extends RequestMessage> T parseRequest(JsonNode header) throws Exception {
|
||||
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 phase = content.required("messagePhase").textValue();
|
||||
|
|
|
@ -17,6 +17,7 @@ public class AppState {
|
|||
@NonFinal
|
||||
@Setter
|
||||
String userName;
|
||||
|
||||
@NonFinal
|
||||
@Setter
|
||||
String userEmail;
|
||||
|
|
|
@ -28,7 +28,7 @@ public class AppStyle {
|
|||
loadStylesheets();
|
||||
|
||||
if (AppPrefs.get() != null) {
|
||||
AppPrefs.get().useSystemFont.addListener((c, o, n) -> {
|
||||
AppPrefs.get().useSystemFont().addListener((c, o, n) -> {
|
||||
changeFontUsage(n);
|
||||
});
|
||||
}
|
||||
|
@ -48,17 +48,19 @@ public class AppStyle {
|
|||
return;
|
||||
}
|
||||
|
||||
TrackEvent.trace("core", "Loading styles for module " + module.getName());
|
||||
TrackEvent.trace("Loading styles for module " + module.getName());
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
try {
|
||||
var bytes = Files.readAllBytes(file);
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
|
@ -93,7 +95,7 @@ public class AppStyle {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import atlantafx.base.theme.*;
|
||||
import com.jthemedetecor.OsThemeDetector;
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
|
@ -14,7 +13,10 @@ import javafx.animation.KeyFrame;
|
|||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.ColorScheme;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
@ -71,30 +73,23 @@ public class AppTheme {
|
|||
}
|
||||
|
||||
try {
|
||||
OsThemeDetector detector = OsThemeDetector.getDetector();
|
||||
if (AppPrefs.get().theme.getValue() == null) {
|
||||
try {
|
||||
setDefault(detector.isDark());
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
setDefault(false);
|
||||
}
|
||||
setDefault(Platform.getPreferences().getColorScheme());
|
||||
}
|
||||
|
||||
// The gnome detector sometimes runs into issues, also it's not that important
|
||||
if (!OsType.getLocal().equals(OsType.LINUX)) {
|
||||
detector.registerListener(dark -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (dark && !AppPrefs.get().theme.getValue().isDark()) {
|
||||
Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> {
|
||||
Platform.runLater(() -> {
|
||||
if (t1 == ColorScheme.DARK
|
||||
&& !AppPrefs.get().theme.getValue().isDark()) {
|
||||
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());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
ErrorEvent.fromThrowable(t).omit().handle();
|
||||
}
|
||||
|
@ -110,8 +105,8 @@ public class AppTheme {
|
|||
init = true;
|
||||
}
|
||||
|
||||
private static void setDefault(boolean dark) {
|
||||
if (dark) {
|
||||
private static void setDefault(ColorScheme colorScheme) {
|
||||
if (colorScheme == ColorScheme.DARK) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||
} else {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||
|
@ -189,8 +184,8 @@ public class AppTheme {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String toTranslatedString() {
|
||||
return name;
|
||||
public ObservableValue<String> toTranslatedString() {
|
||||
return new SimpleStringProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,6 +206,12 @@ public class AppTheme {
|
|||
// Also include your custom theme here
|
||||
public static final List<Theme> ALL =
|
||||
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() {
|
||||
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() {
|
||||
return theme.isDarkMode();
|
||||
}
|
||||
|
@ -244,8 +238,8 @@ public class AppTheme {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String toTranslatedString() {
|
||||
return theme.getName();
|
||||
public ObservableValue<String> toTranslatedString() {
|
||||
return new SimpleStringProperty(theme.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,6 +14,7 @@ public class AppTray {
|
|||
|
||||
private static AppTray INSTANCE;
|
||||
private final AppTrayIcon icon;
|
||||
|
||||
@Getter
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
|
|
|
@ -14,21 +14,23 @@ public class AppTrayIcon {
|
|||
|
||||
private final SystemTray tray;
|
||||
private final TrayIcon trayIcon;
|
||||
private final PopupMenu popupMenu = new PopupMenu();
|
||||
|
||||
public AppTrayIcon() {
|
||||
ensureSystemTraySupported();
|
||||
|
||||
tray = SystemTray.getSystemTray();
|
||||
|
||||
var image = switch (OsType.getLocal()) {
|
||||
var image =
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> "img/logo/logo_16x16.png";
|
||||
case OsType.Linux linux -> "img/logo/logo_24x24.png";
|
||||
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
|
||||
};
|
||||
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.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() {
|
||||
return trayIcon;
|
||||
}
|
||||
|
@ -65,17 +80,7 @@ public class AppTrayIcon {
|
|||
private void ensureSystemTraySupported() {
|
||||
if (!SystemTray.isSupported()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"SystemTray icons are not "
|
||||
+ "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);
|
||||
"SystemTray icons are not " + "supported by the current desktop environment.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,11 +134,9 @@ public class AppTrayIcon {
|
|||
|
||||
public void showInfoMessage(String title, String message) {
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
showMacAlert(title, message,"Information");
|
||||
showMacAlert(title, message, "Information");
|
||||
} else {
|
||||
EventQueue.invokeLater(() ->
|
||||
this.trayIcon.displayMessage(
|
||||
title, message, TrayIcon.MessageType.INFO));
|
||||
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.INFO));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,11 +146,9 @@ public class AppTrayIcon {
|
|||
|
||||
public void showWarningMessage(String title, String message) {
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
showMacAlert(title, message,"Warning");
|
||||
showMacAlert(title, message, "Warning");
|
||||
} else {
|
||||
EventQueue.invokeLater(() ->
|
||||
this.trayIcon.displayMessage(
|
||||
title, message, TrayIcon.MessageType.WARNING));
|
||||
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.WARNING));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,11 +158,9 @@ public class AppTrayIcon {
|
|||
|
||||
public void showErrorMessage(String title, String message) {
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
showMacAlert(title, message,"Error");
|
||||
showMacAlert(title, message, "Error");
|
||||
} else {
|
||||
EventQueue.invokeLater(() ->
|
||||
this.trayIcon.displayMessage(
|
||||
title, message, TrayIcon.MessageType.ERROR));
|
||||
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,11 +170,9 @@ public class AppTrayIcon {
|
|||
|
||||
public void showMessage(String title, String message) {
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
showMacAlert(title, message,"Message");
|
||||
showMacAlert(title, message, "Message");
|
||||
} else {
|
||||
EventQueue.invokeLater(() ->
|
||||
this.trayIcon.displayMessage(
|
||||
title, message, TrayIcon.MessageType.NONE));
|
||||
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.NONE));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,26 +180,14 @@ public class AppTrayIcon {
|
|||
this.showMessage(null, message);
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return Desktop.isDesktopSupported() && SystemTray.isSupported();
|
||||
}
|
||||
|
||||
private void showMacAlert(String subTitle, String message, String title) {
|
||||
String execute = String.format(
|
||||
"display notification \"%s\""
|
||||
+ " with title \"%s\""
|
||||
+ " subtitle \"%s\"",
|
||||
message != null ? message : "",
|
||||
title != null ? title : "",
|
||||
subTitle != null ? subTitle : ""
|
||||
);
|
||||
"display notification \"%s\"" + " with title \"%s\"" + " subtitle \"%s\"",
|
||||
message != null ? message : "", title != null ? title : "", subTitle != null ? subTitle : "");
|
||||
try {
|
||||
Runtime.getRuntime()
|
||||
.exec(new String[] { "osascript", "-e", execute });
|
||||
Runtime.getRuntime().exec(new String[] {"osascript", "-e", execute});
|
||||
} catch (IOException e) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot run osascript with given parameters.");
|
||||
throw new UnsupportedOperationException("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.fxcomps.Comp;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.application.Platform;
|
||||
|
@ -19,6 +20,7 @@ import javafx.scene.layout.Pane;
|
|||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
@ -63,6 +65,9 @@ public class AppWindowHelper {
|
|||
public static Stage sideWindow(
|
||||
String title, Function<Stage, Comp<?>> contentFunc, boolean bindSize, ObservableValue<Boolean> loading) {
|
||||
var stage = new Stage();
|
||||
if (AppMainWindow.getInstance() != null) {
|
||||
stage.initOwner(AppMainWindow.getInstance().getStage());
|
||||
}
|
||||
stage.setTitle(title);
|
||||
if (AppMainWindow.getInstance() != null) {
|
||||
stage.initOwner(AppMainWindow.getInstance().getStage());
|
||||
|
@ -72,6 +77,10 @@ public class AppWindowHelper {
|
|||
setupContent(stage, contentFunc, bindSize, loading);
|
||||
setupStylesheets(stage.getScene());
|
||||
|
||||
if (AppPrefs.get() != null && AppPrefs.get().enforceWindowModality().get()) {
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
}
|
||||
|
||||
stage.setOnShown(e -> {
|
||||
// If we set the theme pseudo classes earlier when the window is not shown
|
||||
// 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);
|
||||
}
|
||||
|
||||
public static void showAlert(
|
||||
Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
|
||||
public static void showAlert(Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var r = showBlockingAlert(c);
|
||||
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) {
|
||||
Supplier<Alert> supplier = () -> {
|
||||
Alert a = AppWindowHelper.createEmptyAlert();
|
||||
|
@ -224,7 +262,6 @@ public class AppWindowHelper {
|
|||
if (event.getCode().equals(KeyCode.W) && event.isShortcutDown()) {
|
||||
stage.close();
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -236,7 +273,11 @@ public class AppWindowHelper {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -287,41 +328,44 @@ public class AppWindowHelper {
|
|||
|
||||
private static List<Screen> getWindowScreens(Stage stage) {
|
||||
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) {
|
||||
double minX = Double.POSITIVE_INFINITY ;
|
||||
double minY = Double.POSITIVE_INFINITY ;
|
||||
double maxX = Double.NEGATIVE_INFINITY ;
|
||||
double maxY = Double.NEGATIVE_INFINITY ;
|
||||
double minX = Double.POSITIVE_INFINITY;
|
||||
double minY = Double.POSITIVE_INFINITY;
|
||||
double maxX = Double.NEGATIVE_INFINITY;
|
||||
double maxY = Double.NEGATIVE_INFINITY;
|
||||
for (Screen screen : getWindowScreens(stage)) {
|
||||
Rectangle2D screenBounds = screen.getBounds();
|
||||
if (screenBounds.getMinX() < minX) {
|
||||
minX = screenBounds.getMinX();
|
||||
}
|
||||
if (screenBounds.getMinY() < minY) {
|
||||
minY = screenBounds.getMinY() ;
|
||||
minY = screenBounds.getMinY();
|
||||
}
|
||||
if (screenBounds.getMaxX() > maxX) {
|
||||
maxX = screenBounds.getMaxX();
|
||||
}
|
||||
if (screenBounds.getMaxY() > maxY) {
|
||||
maxY = screenBounds.getMaxY() ;
|
||||
maxY = screenBounds.getMaxY();
|
||||
}
|
||||
}
|
||||
// Taskbar adjustment
|
||||
maxY -= 50;
|
||||
|
||||
var w = maxX-minX;
|
||||
var h = maxY-minY;
|
||||
var w = maxX - minX;
|
||||
var h = maxY - minY;
|
||||
|
||||
// This should not happen but on weird Linux systems nothing is impossible
|
||||
if (w < 0 || h < 0) {
|
||||
return new Rectangle2D(0,0,800, 600);
|
||||
return new Rectangle2D(0, 0, 800, 600);
|
||||
}
|
||||
|
||||
return new Rectangle2D(minX, minY, w, h);
|
||||
|
|
|
@ -17,54 +17,6 @@ import java.util.Optional;
|
|||
|
||||
public class AppAvCheck {
|
||||
|
||||
@Getter
|
||||
public static enum AvType {
|
||||
|
||||
BITDEFENDER("Bitdefender") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Bitdefender", "InstallDir");
|
||||
}
|
||||
},
|
||||
MALWAREBYTES("Malwarebytes") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Malwarebytes", "id");
|
||||
}
|
||||
},
|
||||
MCAFEE("McAfee") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\McAfee", "mi");
|
||||
}
|
||||
};
|
||||
|
||||
private final String name;
|
||||
|
||||
AvType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract String getDescription();
|
||||
|
||||
public abstract boolean isActive();
|
||||
}
|
||||
|
||||
private static Optional<AvType> detect() {
|
||||
for (AvType value : AvType.values()) {
|
||||
if (value.isActive()) {
|
||||
|
@ -93,14 +45,20 @@ public class AppAvCheck {
|
|||
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
||||
alert.setAlertType(Alert.AlertType.NONE);
|
||||
|
||||
AppResources.with(
|
||||
AppResources.XPIPE_MODULE,
|
||||
"misc/antivirus.md",
|
||||
file -> {
|
||||
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();
|
||||
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));
|
||||
});
|
||||
|
@ -110,4 +68,52 @@ public class AppAvCheck {
|
|||
a.filter(b -> b.getButtonData().isDefaultButton())
|
||||
.ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1));
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum AvType {
|
||||
BITDEFENDER("Bitdefender") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(
|
||||
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir");
|
||||
}
|
||||
},
|
||||
MALWAREBYTES("Malwarebytes") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Malwarebytes", "id");
|
||||
}
|
||||
},
|
||||
MCAFEE("McAfee") {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\McAfee", "mi");
|
||||
}
|
||||
};
|
||||
|
||||
private final String name;
|
||||
|
||||
AvType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract String getDescription();
|
||||
|
||||
public abstract boolean isActive();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.util.LocalShell;
|
||||
import io.xpipe.core.process.ProcessControlProvider;
|
||||
import io.xpipe.core.process.ProcessOutputException;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -12,7 +12,8 @@ public class AppShellCheck {
|
|||
public static void check() {
|
||||
var err = selfTestErrorCheck();
|
||||
if (err.isPresent()) {
|
||||
var msg = """
|
||||
var msg =
|
||||
"""
|
||||
Shell self-test failed for %s:
|
||||
%s
|
||||
|
||||
|
@ -24,7 +25,12 @@ public class AppShellCheck {
|
|||
- 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.
|
||||
""".formatted(ShellDialects.getPlatformDefault().getDisplayName(), err.get());
|
||||
"""
|
||||
.formatted(
|
||||
ProcessControlProvider.get()
|
||||
.getEffectiveLocalDialect()
|
||||
.getDisplayName(),
|
||||
err.get());
|
||||
ErrorEvent.fromThrowable(new IllegalStateException(msg)).handle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ public class AppTempCheck {
|
|||
}
|
||||
|
||||
if (dir == null || !Files.exists(dir) || !Files.isDirectory(dir)) {
|
||||
ErrorEvent.fromThrowable(
|
||||
new IOException("Specified temporary directory " + tmpdir + ", set via the environment variable %TEMP% is invalid."))
|
||||
ErrorEvent.fromThrowable(new IOException("Specified temporary directory " + tmpdir
|
||||
+ ", set via the environment variable %TEMP% is invalid."))
|
||||
.term()
|
||||
.handle();
|
||||
}
|
||||
|
|
|
@ -4,16 +4,18 @@ import io.xpipe.app.browser.BrowserModel;
|
|||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.*;
|
||||
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.ext.ActionProvider;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.GitStorageHandler;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.FileBridge;
|
||||
import io.xpipe.app.util.LicenseProvider;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.LockedSecretValue;
|
||||
import io.xpipe.app.util.UnlockAlert;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
||||
public class BaseMode extends OperationMode {
|
||||
|
@ -39,29 +41,29 @@ public class BaseMode extends OperationMode {
|
|||
// For debugging
|
||||
// if (true) throw new IllegalStateException();
|
||||
|
||||
TrackEvent.info("mode", "Initializing base mode components ...");
|
||||
TrackEvent.info("Initializing base mode components ...");
|
||||
AppExtensionManager.init(true);
|
||||
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();
|
||||
LicenseProvider.get().init();
|
||||
AppPrefs.initLocal();
|
||||
AppCertutilCheck.check();
|
||||
AppAvCheck.check();
|
||||
LocalShell.init();
|
||||
AppShellCheck.check();
|
||||
XPipeDistributionType.init();
|
||||
AppPrefs.init();
|
||||
AppCharsets.init();
|
||||
AppCharsetter.init();
|
||||
AppShellCheck.check();
|
||||
AppPrefs.setDefaults();
|
||||
// Initialize socket server as we should be prepared for git askpass commands
|
||||
AppSocketServer.init();
|
||||
GitStorageHandler.getInstance().init();
|
||||
GitStorageHandler.getInstance().setupRepositoryAndPull();
|
||||
AppPrefs.initSharedRemote();
|
||||
UnlockAlert.showIfNeeded();
|
||||
DataStorage.init();
|
||||
AppFileWatcher.init();
|
||||
FileBridge.init();
|
||||
ActionProvider.initProviders();
|
||||
TrackEvent.info("mode", "Finished base components initialization");
|
||||
TrackEvent.info("Finished base components initialization");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
@ -70,7 +72,7 @@ public class BaseMode extends OperationMode {
|
|||
|
||||
@Override
|
||||
public void finalTeardown() {
|
||||
TrackEvent.info("mode", "Background mode shutdown started");
|
||||
TrackEvent.info("Background mode shutdown started");
|
||||
BrowserModel.DEFAULT.reset();
|
||||
StoreViewState.reset();
|
||||
DataStorage.reset();
|
||||
|
@ -80,6 +82,6 @@ public class BaseMode extends OperationMode {
|
|||
AppDataLock.unlock();
|
||||
// Shut down socket server last to keep a non-daemon thread running
|
||||
AppSocketServer.reset();
|
||||
TrackEvent.info("mode", "Background mode shutdown finished");
|
||||
TrackEvent.info("Background mode shutdown finished");
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue