mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-29 07:20:35 +00:00
Squash merge branch 14-release into master
This commit is contained in:
+2
-1
@@ -187,7 +187,8 @@ APPENDIX: How to apply the Apache License to your work.
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2024 Christopher Schnick
|
Copyright 2023 Christopher Schnick
|
||||||
|
Copyright 2023 XPipe UG (haftungsbeschränkt)
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ are not able to resolve and install any dependency packages.
|
|||||||
|
|
||||||
### RHEL-based distros
|
### RHEL-based distros
|
||||||
|
|
||||||
|
The rpm releases are signed with the GPG key https://xpipe.io/signatures/crschnick.asc.
|
||||||
|
You can import it via `rpm --import https://xpipe.io/signatures/crschnick.asc` to allow your rpm-based package manager to verify the release signature.
|
||||||
|
|
||||||
The following rpm installers are available:
|
The following rpm installers are available:
|
||||||
|
|
||||||
- [Linux .rpm Installer (x86-64)](https://github.com/xpipe-io/xpipe/releases/latest/download/xpipe-installer-linux-x86_64.rpm)
|
- [Linux .rpm Installer (x86-64)](https://github.com/xpipe-io/xpipe/releases/latest/download/xpipe-installer-linux-x86_64.rpm)
|
||||||
|
|||||||
+11
-5
@@ -23,8 +23,8 @@ dependencies {
|
|||||||
api project(':beacon')
|
api project(':beacon')
|
||||||
|
|
||||||
compileOnly 'org.hamcrest:hamcrest:3.0'
|
compileOnly 'org.hamcrest:hamcrest:3.0'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.3'
|
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.4'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.3'
|
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.4'
|
||||||
|
|
||||||
api 'com.vladsch.flexmark:flexmark:0.64.8'
|
api 'com.vladsch.flexmark:flexmark:0.64.8'
|
||||||
api 'com.vladsch.flexmark:flexmark-util:0.64.8'
|
api 'com.vladsch.flexmark:flexmark-util:0.64.8'
|
||||||
@@ -56,10 +56,10 @@ dependencies {
|
|||||||
exclude group: 'org.apache.commons', module: 'commons-lang3'
|
exclude group: 'org.apache.commons', module: 'commons-lang3'
|
||||||
}
|
}
|
||||||
api 'org.apache.commons:commons-lang3:3.17.0'
|
api 'org.apache.commons:commons-lang3:3.17.0'
|
||||||
api 'io.sentry:sentry:7.18.0'
|
api 'io.sentry:sentry:7.20.0'
|
||||||
api 'commons-io:commons-io:2.18.0'
|
api 'commons-io:commons-io:2.18.0'
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.18.1"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.18.2"
|
||||||
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.18.1"
|
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.18.2"
|
||||||
api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
|
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-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-javafx', version: "12.2.0"
|
||||||
@@ -105,6 +105,9 @@ run {
|
|||||||
|
|
||||||
workingDir = rootDir
|
workingDir = rootDir
|
||||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
||||||
|
|
||||||
|
def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList());
|
||||||
|
classpath += exts
|
||||||
}
|
}
|
||||||
|
|
||||||
task runAttachedDebugger(type: JavaExec) {
|
task runAttachedDebugger(type: JavaExec) {
|
||||||
@@ -120,6 +123,9 @@ task runAttachedDebugger(type: JavaExec) {
|
|||||||
)
|
)
|
||||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
||||||
systemProperties run.systemProperties
|
systemProperties run.systemProperties
|
||||||
|
|
||||||
|
def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList());
|
||||||
|
classpath += exts
|
||||||
}
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public class Main {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length == 1 && args[0].equals("version")) {
|
if (args.length == 1 && args[0].equals("version")) {
|
||||||
AppProperties.init();
|
AppProperties.init(args);
|
||||||
System.out.println(AppProperties.get().getVersion());
|
System.out.println(AppProperties.get().getVersion());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,15 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = beaconInterface.handle(exchange, object);
|
|
||||||
|
var sync = beaconInterface.getSynchronizationObject();
|
||||||
|
if (sync != null) {
|
||||||
|
synchronized (sync) {
|
||||||
|
response = beaconInterface.handle(exchange, object);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response = beaconInterface.handle(exchange, object);
|
||||||
|
}
|
||||||
} catch (BeaconClientException clientException) {
|
} catch (BeaconClientException clientException) {
|
||||||
ErrorEvent.fromThrowable(clientException).omit().expected().handle();
|
ErrorEvent.fromThrowable(clientException).omit().expected().handle();
|
||||||
writeError(exchange, new BeaconClientErrorResponse(clientException.getMessage()), 400);
|
writeError(exchange, new BeaconClientErrorResponse(clientException.getMessage()), 400);
|
||||||
@@ -193,7 +201,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||||||
&& method.getParameters()[0].getType().equals(byte[].class))
|
&& method.getParameters()[0].getType().equals(byte[].class))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
setMethod.invoke(b, s);
|
setMethod.invoke(b, (Object) s);
|
||||||
|
|
||||||
var m = b.getClass().getDeclaredMethod("build");
|
var m = b.getClass().getDeclaredMethod("build");
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||||
import io.xpipe.app.terminal.TerminalView;
|
import io.xpipe.app.terminal.TerminalView;
|
||||||
import io.xpipe.app.util.AskpassAlert;
|
import io.xpipe.app.util.AskpassAlert;
|
||||||
import io.xpipe.app.util.SecretManager;
|
import io.xpipe.app.util.SecretManager;
|
||||||
import io.xpipe.app.util.SecretQueryState;
|
import io.xpipe.app.util.SecretQueryState;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.api.AskpassExchange;
|
import io.xpipe.beacon.api.AskpassExchange;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
@@ -50,17 +53,24 @@ public class AskpassExchangeImpl extends AskpassExchange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var term = TerminalView.get().getTerminalInstances().stream()
|
var term = TerminalView.get().getTerminalInstances().stream()
|
||||||
.filter(instance ->
|
.filter(instance -> instance.equals(found.get().getTerminal()))
|
||||||
instance.getTerminalProcess().equals(found.get().getTerminal()))
|
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (term.isEmpty()) {
|
if (term.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var control = term.get().controllable();
|
var control = term.get().controllable();
|
||||||
control.ifPresent(controllableTerminalSession -> {
|
if (control.isPresent()) {
|
||||||
controllableTerminalSession.focus();
|
control.get().focus();
|
||||||
});
|
} else {
|
||||||
|
if (OsType.getLocal() == OsType.MACOS) {
|
||||||
|
// Just focus the app, this is correct most of the time
|
||||||
|
var terminalType = AppPrefs.get().terminalType().getValue();
|
||||||
|
if (terminalType instanceof ExternalApplicationType.MacApplication m) {
|
||||||
|
m.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
|
import io.xpipe.beacon.api.CategoryAddExchange;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
|
public class CategoryAddExchangeImpl extends CategoryAddExchange {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(HttpExchange exchange, Request msg) throws Throwable {
|
||||||
|
if (DataStorage.get().getStoreCategoryIfPresent(msg.getParent()).isEmpty()) {
|
||||||
|
throw new BeaconClientException("Parent category with id " + msg.getParent() + " does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DataStorage.get().getStoreCategories().stream()
|
||||||
|
.anyMatch(dataStoreCategory -> msg.getParent().equals(dataStoreCategory.getParentCategory())
|
||||||
|
&& msg.getName().equals(dataStoreCategory.getName()))) {
|
||||||
|
throw new BeaconClientException(
|
||||||
|
"Category with name " + msg.getName() + " already exists in parent category");
|
||||||
|
}
|
||||||
|
|
||||||
|
var cat = DataStoreCategory.createNew(msg.getParent(), msg.getName());
|
||||||
|
DataStorage.get().addStoreCategory(cat);
|
||||||
|
return Response.builder().category(cat.getUuid()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package io.xpipe.app.beacon.impl;
|
|||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.api.ConnectionAddExchange;
|
import io.xpipe.beacon.api.ConnectionAddExchange;
|
||||||
import io.xpipe.core.util.ValidationException;
|
import io.xpipe.core.util.ValidationException;
|
||||||
|
|
||||||
@@ -17,7 +18,17 @@ public class ConnectionAddExchangeImpl extends ConnectionAddExchange {
|
|||||||
return Response.builder().connection(found.get().getUuid()).build();
|
return Response.builder().connection(found.get().getUuid()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.getCategory() != null
|
||||||
|
&& DataStorage.get()
|
||||||
|
.getStoreCategoryIfPresent(msg.getCategory())
|
||||||
|
.isEmpty()) {
|
||||||
|
throw new BeaconClientException("Category with id " + msg.getCategory() + " does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
var entry = DataStoreEntry.createNew(msg.getName(), msg.getData());
|
var entry = DataStoreEntry.createNew(msg.getName(), msg.getData());
|
||||||
|
if (msg.getCategory() != null) {
|
||||||
|
entry.setCategoryUuid(msg.getCategory());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
DataStorage.get().addStoreEntryInProgress(entry);
|
DataStorage.get().addStoreEntryInProgress(entry);
|
||||||
if (msg.getValidate()) {
|
if (msg.getValidate()) {
|
||||||
@@ -35,6 +46,22 @@ public class ConnectionAddExchangeImpl extends ConnectionAddExchange {
|
|||||||
DataStorage.get().removeStoreEntryInProgress(entry);
|
DataStorage.get().removeStoreEntryInProgress(entry);
|
||||||
}
|
}
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(entry);
|
DataStorage.get().addStoreEntryIfNotPresent(entry);
|
||||||
|
|
||||||
|
// Explicitly assign category
|
||||||
|
if (msg.getCategory() != null) {
|
||||||
|
DataStorage.get()
|
||||||
|
.updateCategory(
|
||||||
|
entry,
|
||||||
|
DataStorage.get()
|
||||||
|
.getStoreCategoryIfPresent(msg.getCategory())
|
||||||
|
.orElseThrow());
|
||||||
|
}
|
||||||
|
|
||||||
return Response.builder().connection(entry.getUuid()).build();
|
return Response.builder().connection(entry.getUuid()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,9 @@ public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange {
|
|||||||
AppLayoutModel.get().selectBrowser();
|
AppLayoutModel.get().selectBrowser();
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,19 +56,8 @@ public class ConnectionInfoExchangeImpl extends ConnectionInfoExchange {
|
|||||||
return Response.builder().infos(list).build();
|
return Response.builder().infos(list).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> toWrapper(Class<?> clazz) {
|
@Override
|
||||||
if (!clazz.isPrimitive()) return clazz;
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
if (clazz == Integer.TYPE) return Integer.class;
|
|
||||||
if (clazz == Long.TYPE) return Long.class;
|
|
||||||
if (clazz == Boolean.TYPE) return Boolean.class;
|
|
||||||
if (clazz == Byte.TYPE) return Byte.class;
|
|
||||||
if (clazz == Character.TYPE) return Character.class;
|
|
||||||
if (clazz == Float.TYPE) return Float.class;
|
|
||||||
if (clazz == Double.TYPE) return Double.class;
|
|
||||||
if (clazz == Short.TYPE) return Short.class;
|
|
||||||
if (clazz == Void.TYPE) return Void.class;
|
|
||||||
|
|
||||||
return clazz;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
|
|
||||||
private String toRegex(String pattern) {
|
private String toRegex(String pattern) {
|
||||||
// https://stackoverflow.com/a/17369948/6477761
|
// https://stackoverflow.com/a/17369948/6477761
|
||||||
StringBuilder sb = new StringBuilder(pattern.length());
|
StringBuilder sb = new StringBuilder(pattern.length());
|
||||||
|
|||||||
@@ -21,4 +21,9 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange {
|
|||||||
}
|
}
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,9 @@ public class ConnectionRemoveExchangeImpl extends ConnectionRemoveExchange {
|
|||||||
DataStorage.get().deleteWithChildren(entries.toArray(DataStoreEntry[]::new));
|
DataStorage.get().deleteWithChildren(entries.toArray(DataStoreEntry[]::new));
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,9 @@ public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange {
|
|||||||
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,9 @@ public class ConnectionToggleExchangeImpl extends ConnectionToggleExchange {
|
|||||||
}
|
}
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSynchronizationObject() {
|
||||||
|
return DataStorage.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
import io.xpipe.app.core.launcher.LauncherInput;
|
import io.xpipe.app.core.AppOpenArguments;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.util.PlatformState;
|
import io.xpipe.app.util.PlatformInit;
|
||||||
import io.xpipe.beacon.BeaconServerException;
|
import io.xpipe.beacon.BeaconServerException;
|
||||||
import io.xpipe.beacon.api.DaemonOpenExchange;
|
import io.xpipe.beacon.api.DaemonOpenExchange;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
public class DaemonOpenExchangeImpl extends DaemonOpenExchange {
|
public class DaemonOpenExchangeImpl extends DaemonOpenExchange {
|
||||||
|
|
||||||
|
private int openCounter = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconServerException {
|
public Object handle(HttpExchange exchange, Request msg) throws BeaconServerException {
|
||||||
if (msg.getArguments().isEmpty()) {
|
if (msg.getArguments().isEmpty()) {
|
||||||
if (!OperationMode.switchToSyncIfPossible(OperationMode.GUI)) {
|
try {
|
||||||
throw new BeaconServerException(PlatformState.getLastError());
|
// At this point we are already loading this on another thread
|
||||||
|
// so this call will only perform the waiting
|
||||||
|
PlatformInit.init(true);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new BeaconServerException(t);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LauncherInput.handle(msg.getArguments());
|
// The open command is used as a default opener on Linux
|
||||||
|
// We don't want to overwrite the default startup mode
|
||||||
|
if (OsType.getLocal() == OsType.LINUX && openCounter++ == 0) {
|
||||||
|
return Response.builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationMode.switchToAsync(OperationMode.GUI);
|
||||||
|
} else {
|
||||||
|
AppOpenArguments.handle(msg.getArguments());
|
||||||
|
}
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,4 +41,9 @@ public class DaemonOpenExchangeImpl extends DaemonOpenExchange {
|
|||||||
public boolean requiresEnabledApi() {
|
public boolean requiresEnabledApi() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresCompletedStartup() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ public class FsScriptExchangeImpl extends FsScriptExchange {
|
|||||||
try (var in = BlobManager.get().getBlob(msg.getBlob())) {
|
try (var in = BlobManager.get().getBlob(msg.getBlob())) {
|
||||||
data = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
data = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
data = shell.getControl().getShellDialect().prepareScriptContent(data);
|
||||||
var file = ScriptHelper.getExecScriptFile(shell.getControl());
|
var file = ScriptHelper.getExecScriptFile(shell.getControl());
|
||||||
shell.getControl()
|
shell.getControl().view().writeScriptFile(file, data);
|
||||||
.getShellDialect()
|
file = ScriptHelper.fixScriptPermissions(shell.getControl(), file);
|
||||||
.createScriptTextFileWriteCommand(shell.getControl(), data, file.toString())
|
|
||||||
.execute();
|
|
||||||
return Response.builder().path(file).build();
|
return Response.builder().path(file).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import javafx.scene.layout.Priority;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -50,8 +52,14 @@ public class BrowserFileChooserSessionComp extends DialogComp {
|
|||||||
public static void openSingleFile(
|
public static void openSingleFile(
|
||||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
var lastWindow = Window.getWindows().stream()
|
||||||
|
.filter(window -> window.isFocused())
|
||||||
|
.findFirst();
|
||||||
var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE);
|
var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE);
|
||||||
DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> {
|
DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> {
|
||||||
|
stage.addEventFilter(WindowEvent.WINDOW_HIDDEN, event -> {
|
||||||
|
lastWindow.ifPresent(window -> window.requestFocus());
|
||||||
|
});
|
||||||
var comp = new BrowserFileChooserSessionComp(stage, model);
|
var comp = new BrowserFileChooserSessionComp(stage, model);
|
||||||
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
|
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||||
.apply(struc -> AppFont.normal(struc.get()))
|
.apply(struc -> AppFont.normal(struc.get()))
|
||||||
@@ -116,7 +124,8 @@ public class BrowserFileChooserSessionComp extends DialogComp {
|
|||||||
|
|
||||||
var bookmarkTopBar = new BrowserConnectionListFilterComp();
|
var bookmarkTopBar = new BrowserConnectionListFilterComp();
|
||||||
var bookmarksList = new BrowserConnectionListComp(
|
var bookmarksList = new BrowserConnectionListComp(
|
||||||
BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()),
|
BindingsHelper.map(
|
||||||
|
model.getSelectedEntry(), v -> v != null ? v.getEntry().get() : null),
|
||||||
applicable,
|
applicable,
|
||||||
action,
|
action,
|
||||||
bookmarkTopBar.getCategory(),
|
bookmarkTopBar.getCategory(),
|
||||||
|
|||||||
@@ -34,20 +34,14 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
|
|||||||
|
|
||||||
public static final BrowserFullSessionModel DEFAULT = new BrowserFullSessionModel();
|
public static final BrowserFullSessionModel DEFAULT = new BrowserFullSessionModel();
|
||||||
|
|
||||||
static {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private static void init() {
|
public static void init() {
|
||||||
DEFAULT.openSync(new BrowserHistoryTabModel(DEFAULT), null);
|
DEFAULT.openSync(new BrowserHistoryTabModel(DEFAULT), null);
|
||||||
if (AppPrefs.get().pinLocalMachineOnStartup().get()) {
|
if (AppPrefs.get().pinLocalMachineOnStartup().get()) {
|
||||||
var tab = new BrowserFileSystemTabModel(
|
var tab = new BrowserFileSystemTabModel(
|
||||||
DEFAULT, DataStorage.get().local().ref(), BrowserFileSystemTabModel.SelectionMode.ALL);
|
DEFAULT, DataStorage.get().local().ref(), BrowserFileSystemTabModel.SelectionMode.ALL);
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
DEFAULT.openSync(tab, null);
|
||||||
DEFAULT.openSync(tab, null);
|
DEFAULT.pinTab(tab);
|
||||||
DEFAULT.pinTab(tab);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +56,10 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
|
|||||||
return Bindings.createObjectBinding(
|
return Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var current = selectedEntry.getValue();
|
var current = selectedEntry.getValue();
|
||||||
|
if (current == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!current.isCloseable()) {
|
if (!current.isCloseable()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -176,6 +174,10 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
|
|||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
synchronized (BrowserFullSessionModel.this) {
|
synchronized (BrowserFullSessionModel.this) {
|
||||||
|
if (globalPinnedTab.getValue() != null) {
|
||||||
|
globalPinnedTab.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
var all = new ArrayList<>(sessionEntries);
|
var all = new ArrayList<>(sessionEntries);
|
||||||
for (var o : all) {
|
for (var o : all) {
|
||||||
// Don't close busy connections gracefully
|
// Don't close busy connections gracefully
|
||||||
@@ -242,7 +244,7 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
|
|||||||
if (split != null) {
|
if (split != null) {
|
||||||
split.close();
|
split.close();
|
||||||
}
|
}
|
||||||
|
previousTabs.remove(e);
|
||||||
super.closeSync(e);
|
super.closeSync(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import javafx.beans.property.BooleanProperty;
|
|||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -15,12 +16,10 @@ public abstract class BrowserSessionTab {
|
|||||||
|
|
||||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||||
protected final String name;
|
|
||||||
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
|
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name) {
|
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel) {
|
||||||
this.browserModel = browserModel;
|
this.browserModel = browserModel;
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Comp<?> comp();
|
public abstract Comp<?> comp();
|
||||||
@@ -31,6 +30,8 @@ public abstract class BrowserSessionTab {
|
|||||||
|
|
||||||
public abstract void close();
|
public abstract void close();
|
||||||
|
|
||||||
|
public abstract ObservableValue<String> getName();
|
||||||
|
|
||||||
public abstract String getIcon();
|
public abstract String getIcon();
|
||||||
|
|
||||||
public abstract DataColor getColor();
|
public abstract DataColor getColor();
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
var cm = ContextMenuHelper.create();
|
var cm = ContextMenuHelper.create();
|
||||||
|
|
||||||
if (tabModel.isCloseable()) {
|
if (tabModel.isCloseable()) {
|
||||||
var unpin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("unpinTab"));
|
var unpin = ContextMenuHelper.item(LabelGraphic.none(), "unpinTab");
|
||||||
unpin.visibleProperty()
|
unpin.visibleProperty()
|
||||||
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -290,7 +290,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
cm.getItems().add(unpin);
|
cm.getItems().add(unpin);
|
||||||
|
|
||||||
var pin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("pinTab"));
|
var pin = ContextMenuHelper.item(LabelGraphic.none(), "pinTab");
|
||||||
pin.visibleProperty()
|
pin.visibleProperty()
|
||||||
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -304,7 +304,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
cm.getItems().add(pin);
|
cm.getItems().add(pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab"));
|
var select = ContextMenuHelper.item(LabelGraphic.none(), "selectTab");
|
||||||
select.acceleratorProperty()
|
select.acceleratorProperty()
|
||||||
.bind(Bindings.createObjectBinding(
|
.bind(Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -325,7 +325,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
|
|
||||||
cm.getItems().add(new SeparatorMenuItem());
|
cm.getItems().add(new SeparatorMenuItem());
|
||||||
|
|
||||||
var close = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeTab"));
|
var close = ContextMenuHelper.item(LabelGraphic.none(), "closeTab");
|
||||||
close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
|
close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
|
||||||
close.setOnAction(event -> {
|
close.setOnAction(event -> {
|
||||||
if (tab.isClosable()) {
|
if (tab.isClosable()) {
|
||||||
@@ -335,7 +335,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
cm.getItems().add(close);
|
cm.getItems().add(close);
|
||||||
|
|
||||||
var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeOtherTabs"));
|
var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), "closeOtherTabs");
|
||||||
closeOthers.setOnAction(event -> {
|
closeOthers.setOnAction(event -> {
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
.removeAll(tabs.getTabs().stream()
|
.removeAll(tabs.getTabs().stream()
|
||||||
@@ -345,7 +345,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
cm.getItems().add(closeOthers);
|
cm.getItems().add(closeOthers);
|
||||||
|
|
||||||
var closeLeft = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeLeftTabs"));
|
var closeLeft = ContextMenuHelper.item(LabelGraphic.none(), "closeLeftTabs");
|
||||||
closeLeft.setOnAction(event -> {
|
closeLeft.setOnAction(event -> {
|
||||||
var index = tabs.getTabs().indexOf(tab);
|
var index = tabs.getTabs().indexOf(tab);
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
@@ -356,7 +356,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
cm.getItems().add(closeLeft);
|
cm.getItems().add(closeLeft);
|
||||||
|
|
||||||
var closeRight = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeRightTabs"));
|
var closeRight = ContextMenuHelper.item(LabelGraphic.none(), "closeRightTabs");
|
||||||
closeRight.setOnAction(event -> {
|
closeRight.setOnAction(event -> {
|
||||||
var index = tabs.getTabs().indexOf(tab);
|
var index = tabs.getTabs().indexOf(tab);
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
@@ -367,7 +367,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
cm.getItems().add(closeRight);
|
cm.getItems().add(closeRight);
|
||||||
|
|
||||||
var closeAll = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeAllTabs"));
|
var closeAll = ContextMenuHelper.item(LabelGraphic.none(), "closeAllTabs");
|
||||||
closeAll.setAccelerator(
|
closeAll.setAccelerator(
|
||||||
new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
|
new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
|
||||||
closeAll.setOnAction(event -> {
|
closeAll.setOnAction(event -> {
|
||||||
@@ -425,13 +425,16 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||||||
tab.textProperty()
|
tab.textProperty()
|
||||||
.bind(Bindings.createStringBinding(
|
.bind(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return tabModel.getName()
|
var n = tabModel.getName().getValue();
|
||||||
|
return (AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n)
|
||||||
+ (global.getValue() == tabModel ? " (" + AppI18n.get("pinned") + ")" : "");
|
+ (global.getValue() == tabModel ? " (" + AppI18n.get("pinned") + ")" : "");
|
||||||
},
|
},
|
||||||
|
tabModel.getName(),
|
||||||
global,
|
global,
|
||||||
AppPrefs.get().language()));
|
AppPrefs.get().language(),
|
||||||
|
AppPrefs.get().censorMode()));
|
||||||
} else {
|
} else {
|
||||||
tab.setText(tabModel.getName());
|
tab.textProperty().bind(tabModel.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
Comp<?> comp = tabModel.comp();
|
Comp<?> comp = tabModel.comp();
|
||||||
|
|||||||
@@ -6,16 +6,26 @@ import io.xpipe.app.storage.DataStorage;
|
|||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class BrowserStoreSessionTab<T extends DataStore> extends BrowserSessionTab {
|
public abstract class BrowserStoreSessionTab<T extends DataStore> extends BrowserSessionTab {
|
||||||
|
|
||||||
protected final DataStoreEntryRef<? extends T> entry;
|
protected final DataStoreEntryRef<? extends T> entry;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
public BrowserStoreSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
|
public BrowserStoreSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
|
||||||
super(browserModel, DataStorage.get().getStoreEntryDisplayName(entry.get()));
|
super(browserModel);
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
|
this.name = DataStorage.get().getStoreEntryDisplayName(entry.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableValue<String> getName() {
|
||||||
|
return new SimpleStringProperty(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Comp<?> comp();
|
public abstract Comp<?> comp();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import io.xpipe.app.browser.action.BrowserAction;
|
|||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.util.*;
|
import io.xpipe.app.util.*;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileInfo;
|
import io.xpipe.core.store.FileInfo;
|
||||||
@@ -29,10 +28,7 @@ import atlantafx.base.theme.Styles;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
|
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
|
||||||
@@ -283,12 +279,21 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (var ignored = updateFromModel) {
|
try (var ignored = updateFromModel) {
|
||||||
fileList.getSelection().setAll(c.getList());
|
// Attempt to preserve ordering. Works at least when selecting single entries
|
||||||
|
var existing = new HashSet<>(fileList.getSelection());
|
||||||
|
c.getList().forEach(browserEntry -> {
|
||||||
|
if (!existing.contains(browserEntry)) {
|
||||||
|
fileList.getSelection().add(browserEntry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileList.getSelection().removeIf(browserEntry -> !c.getList().contains(browserEntry));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
if (c.getList().equals(table.getSelectionModel().getSelectedItems())) {
|
var existing = new HashSet<>(fileList.getSelection());
|
||||||
|
var toApply = new HashSet<>(c.getList());
|
||||||
|
if (existing.equals(toApply)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.xpipe.app.browser.file;
|
package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
|
import io.xpipe.app.util.PasswdFile;
|
||||||
import io.xpipe.app.util.ShellControlCache;
|
import io.xpipe.app.util.ShellControlCache;
|
||||||
import io.xpipe.core.process.CommandBuilder;
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
@@ -16,7 +17,7 @@ public class BrowserFileSystemCache extends ShellControlCache {
|
|||||||
|
|
||||||
private final BrowserFileSystemTabModel model;
|
private final BrowserFileSystemTabModel model;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final Map<Integer, String> users = new LinkedHashMap<>();
|
private final PasswdFile passwdFile;
|
||||||
private final Map<Integer, String> groups = new LinkedHashMap<>();
|
private final Map<Integer, String> groups = new LinkedHashMap<>();
|
||||||
|
|
||||||
public BrowserFileSystemCache(BrowserFileSystemTabModel model) throws Exception {
|
public BrowserFileSystemCache(BrowserFileSystemTabModel model) throws Exception {
|
||||||
@@ -27,16 +28,16 @@ public class BrowserFileSystemCache extends ShellControlCache {
|
|||||||
ShellDialect d = sc.getShellDialect();
|
ShellDialect d = sc.getShellDialect();
|
||||||
// If there is no id command, we should still be fine with just assuming root
|
// If there is no id command, we should still be fine with just assuming root
|
||||||
username = d.printUsernameCommand(sc).readStdoutIfPossible().orElse("root");
|
username = d.printUsernameCommand(sc).readStdoutIfPossible().orElse("root");
|
||||||
loadUsers();
|
passwdFile = PasswdFile.parse(sc);
|
||||||
loadGroups();
|
loadGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Integer, String> getUsers() {
|
||||||
|
return passwdFile.getUsers();
|
||||||
|
}
|
||||||
|
|
||||||
public int getUidForUser(String name) {
|
public int getUidForUser(String name) {
|
||||||
return users.entrySet().stream()
|
return passwdFile.getUidForUser(name);
|
||||||
.filter(e -> e.getValue().equals(name))
|
|
||||||
.findFirst()
|
|
||||||
.map(e -> e.getKey())
|
|
||||||
.orElse(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGidForGroup(String name) {
|
public int getGidForGroup(String name) {
|
||||||
@@ -47,28 +48,6 @@ public class BrowserFileSystemCache extends ShellControlCache {
|
|||||||
.orElse(0);
|
.orElse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadUsers() throws Exception {
|
|
||||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
|
||||||
if (sc.getOsType() == OsType.WINDOWS || sc.getOsType() == OsType.MACOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = sc.command(CommandBuilder.of().add("cat").addFile("/etc/passwd"))
|
|
||||||
.readStdoutIfPossible()
|
|
||||||
.orElse("");
|
|
||||||
lines.lines().forEach(s -> {
|
|
||||||
var split = s.split(":");
|
|
||||||
try {
|
|
||||||
users.putIfAbsent(Integer.parseInt(split[2]), split[0]);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
|
||||||
users.put(0, "root");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadGroups() throws Exception {
|
private void loadGroups() throws Exception {
|
||||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||||
if (sc.getOsType() == OsType.WINDOWS || sc.getOsType() == OsType.MACOS) {
|
if (sc.getOsType() == OsType.WINDOWS || sc.getOsType() == OsType.MACOS) {
|
||||||
|
|||||||
@@ -51,15 +51,16 @@ public class BrowserFileSystemHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shell = model.getFileSystem().getShell();
|
var shell = model.getFileSystem().getShell();
|
||||||
if (shell.isEmpty() || !shell.get().isRunning()) {
|
if (shell.isEmpty() || !shell.get().isRunning(true)) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return shell.get()
|
var r = shell.get()
|
||||||
.getShellDialect()
|
.getShellDialect()
|
||||||
.evaluateExpression(shell.get(), path)
|
.evaluateExpression(shell.get(), path)
|
||||||
.readStdoutOrThrow();
|
.readStdoutOrThrow();
|
||||||
|
return !r.isBlank() ? r : null;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class BrowserFileSystemSavedState {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() {
|
public synchronized void save() {
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ public class BrowserFileSystemSavedState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRecent(String dir) {
|
private synchronized void updateRecent(String dir) {
|
||||||
var without = FileNames.removeTrailingSlash(dir);
|
var without = FileNames.removeTrailingSlash(dir);
|
||||||
var with = FileNames.toDirectory(dir);
|
var with = FileNames.toDirectory(dir);
|
||||||
recentDirectories.removeIf(recentEntry ->
|
recentDirectories.removeIf(recentEntry ->
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ public class BrowserFileSystemTabComp extends SimpleComp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var alertOverlay = new ModalOverlayComp(Comp.of(() -> createContent()), model.getOverlay());
|
return createContent();
|
||||||
return alertOverlay.createRegion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region createContent() {
|
private Region createContent() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import io.xpipe.app.browser.BrowserFullSessionModel;
|
|||||||
import io.xpipe.app.browser.BrowserStoreSessionTab;
|
import io.xpipe.app.browser.BrowserStoreSessionTab;
|
||||||
import io.xpipe.app.browser.action.BrowserAction;
|
import io.xpipe.app.browser.action.BrowserAction;
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
|
||||||
import io.xpipe.app.core.window.AppMainWindow;
|
import io.xpipe.app.core.window.AppMainWindow;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.ext.ShellStore;
|
import io.xpipe.app.ext.ShellStore;
|
||||||
@@ -44,10 +43,10 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
private final BrowserFileListModel fileList;
|
private final BrowserFileListModel fileList;
|
||||||
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
||||||
private final BrowserFileSystemHistory history = new BrowserFileSystemHistory();
|
private final BrowserFileSystemHistory history = new BrowserFileSystemHistory();
|
||||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
|
||||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||||
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>();
|
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>();
|
||||||
private final ObservableList<UUID> terminalRequests = FXCollections.observableArrayList();
|
private final ObservableList<UUID> terminalRequests = FXCollections.observableArrayList();
|
||||||
|
private final BooleanProperty transferCancelled = new SimpleBooleanProperty();
|
||||||
private FileSystem fileSystem;
|
private FileSystem fileSystem;
|
||||||
private BrowserFileSystemSavedState savedState;
|
private BrowserFileSystemSavedState savedState;
|
||||||
private BrowserFileSystemCache cache;
|
private BrowserFileSystemCache cache;
|
||||||
@@ -65,6 +64,14 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
fileList = new BrowserFileListModel(selectionMode, this);
|
fileList = new BrowserFileListModel(selectionMode, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<FileEntry> findFile(String path) {
|
||||||
|
return getFileList().getAll().getValue().stream()
|
||||||
|
.filter(browserEntry -> browserEntry.getFileName().equals(path)
|
||||||
|
|| browserEntry.getRawFileEntry().getPath().equals(path))
|
||||||
|
.findFirst()
|
||||||
|
.map(browserEntry -> browserEntry.getRawFileEntry());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Comp<?> comp() {
|
public Comp<?> comp() {
|
||||||
return new BrowserFileSystemTabComp(this, true);
|
return new BrowserFileSystemTabComp(this, true);
|
||||||
@@ -142,6 +149,14 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void killTransfer() {
|
||||||
|
if (fileSystem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transferCancelled.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
|
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
@@ -384,7 +399,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
|
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
var op = BrowserFileTransferOperation.ofLocal(
|
var op = BrowserFileTransferOperation.ofLocal(
|
||||||
entry, files, BrowserFileTransferMode.COPY, true, progress::setValue);
|
entry, files, BrowserFileTransferMode.COPY, true, progress::setValue, transferCancelled);
|
||||||
op.execute();
|
op.execute();
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
@@ -404,7 +419,8 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
}
|
}
|
||||||
|
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
var op = new BrowserFileTransferOperation(target, files, mode, true, progress::setValue);
|
var op = new BrowserFileTransferOperation(
|
||||||
|
target, files, mode, true, progress::setValue, transferCancelled);
|
||||||
op.execute();
|
op.execute();
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
@@ -463,10 +479,6 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void runCommandAsync(CommandBuilder command, boolean refresh) {
|
public void runCommandAsync(CommandBuilder command, boolean refresh) {
|
||||||
if (name == null || name.isBlank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.executeExclusive(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
@@ -491,10 +503,6 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void runAsync(FailableRunnable<Exception> r, boolean refresh) {
|
public void runAsync(FailableRunnable<Exception> r, boolean refresh) {
|
||||||
if (name == null || name.isBlank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.executeExclusive(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
@@ -566,7 +574,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
fullSessionModel.splitTab(
|
fullSessionModel.splitTab(
|
||||||
this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests));
|
this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests));
|
||||||
}
|
}
|
||||||
TerminalLauncher.open(entry.getEntry(), name, directory, processControl, uuid, !dock);
|
TerminalLauncher.open(entry.get(), name, directory, processControl, uuid, !dock);
|
||||||
|
|
||||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
@@ -575,11 +583,11 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void backSync(int i) throws Exception {
|
public void backSync(int i) {
|
||||||
cdSync(history.back(i));
|
cdSync(history.back(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forthSync(int i) throws Exception {
|
public void forthSync(int i) {
|
||||||
cdSync(history.forth(i));
|
cdSync(history.forth(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package io.xpipe.app.browser.file;
|
|||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -20,6 +22,7 @@ public class BrowserFileTransferOperation {
|
|||||||
private final BrowserFileTransferMode transferMode;
|
private final BrowserFileTransferMode transferMode;
|
||||||
private final boolean checkConflicts;
|
private final boolean checkConflicts;
|
||||||
private final Consumer<BrowserTransferProgress> progress;
|
private final Consumer<BrowserTransferProgress> progress;
|
||||||
|
private final BooleanProperty cancelled;
|
||||||
|
|
||||||
BrowserAlerts.FileConflictChoice lastConflictChoice;
|
BrowserAlerts.FileConflictChoice lastConflictChoice;
|
||||||
|
|
||||||
@@ -28,12 +31,14 @@ public class BrowserFileTransferOperation {
|
|||||||
List<FileEntry> files,
|
List<FileEntry> files,
|
||||||
BrowserFileTransferMode transferMode,
|
BrowserFileTransferMode transferMode,
|
||||||
boolean checkConflicts,
|
boolean checkConflicts,
|
||||||
Consumer<BrowserTransferProgress> progress) {
|
Consumer<BrowserTransferProgress> progress,
|
||||||
|
BooleanProperty cancelled) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
this.transferMode = transferMode;
|
this.transferMode = transferMode;
|
||||||
this.checkConflicts = checkConflicts;
|
this.checkConflicts = checkConflicts;
|
||||||
this.progress = progress;
|
this.progress = progress;
|
||||||
|
this.cancelled = cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BrowserFileTransferOperation ofLocal(
|
public static BrowserFileTransferOperation ofLocal(
|
||||||
@@ -41,7 +46,8 @@ public class BrowserFileTransferOperation {
|
|||||||
List<Path> files,
|
List<Path> files,
|
||||||
BrowserFileTransferMode transferMode,
|
BrowserFileTransferMode transferMode,
|
||||||
boolean checkConflicts,
|
boolean checkConflicts,
|
||||||
Consumer<BrowserTransferProgress> progress) {
|
Consumer<BrowserTransferProgress> progress,
|
||||||
|
BooleanProperty cancelled) {
|
||||||
var entries = files.stream()
|
var entries = files.stream()
|
||||||
.map(path -> {
|
.map(path -> {
|
||||||
if (!Files.exists(path)) {
|
if (!Files.exists(path)) {
|
||||||
@@ -56,7 +62,7 @@ public class BrowserFileTransferOperation {
|
|||||||
})
|
})
|
||||||
.filter(entry -> entry != null)
|
.filter(entry -> entry != null)
|
||||||
.toList();
|
.toList();
|
||||||
return new BrowserFileTransferOperation(target, entries, transferMode, checkConflicts, progress);
|
return new BrowserFileTransferOperation(target, entries, transferMode, checkConflicts, progress, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(BrowserTransferProgress progress) {
|
private void updateProgress(BrowserTransferProgress progress) {
|
||||||
@@ -112,12 +118,18 @@ public class BrowserFileTransferOperation {
|
|||||||
return BrowserAlerts.FileConflictChoice.REPLACE;
|
return BrowserAlerts.FileConflictChoice.REPLACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean cancelled() {
|
||||||
|
return cancelled.get();
|
||||||
|
}
|
||||||
|
|
||||||
public void execute() throws Exception {
|
public void execute() throws Exception {
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
updateProgress(null);
|
updateProgress(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelled.set(false);
|
||||||
|
|
||||||
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
|
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
|
||||||
var doesMove = transferMode == BrowserFileTransferMode.MOVE
|
var doesMove = transferMode == BrowserFileTransferMode.MOVE
|
||||||
|| (same && transferMode == BrowserFileTransferMode.NORMAL);
|
|| (same && transferMode == BrowserFileTransferMode.NORMAL);
|
||||||
@@ -129,6 +141,10 @@ public class BrowserFileTransferOperation {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for (var file : files) {
|
for (var file : files) {
|
||||||
|
if (cancelled()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (same) {
|
if (same) {
|
||||||
handleSingleOnSameFileSystem(file);
|
handleSingleOnSameFileSystem(file);
|
||||||
} else {
|
} else {
|
||||||
@@ -138,6 +154,10 @@ public class BrowserFileTransferOperation {
|
|||||||
|
|
||||||
if (!same && doesMove) {
|
if (!same && doesMove) {
|
||||||
for (var file : files) {
|
for (var file : files) {
|
||||||
|
if (cancelled()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
deleteSingle(file);
|
deleteSingle(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +227,7 @@ public class BrowserFileTransferOperation {
|
|||||||
var newFile =
|
var newFile =
|
||||||
targetFile.getParent().join(matcher.group(1) + " (" + (number + 1) + ")." + matcher.group(3));
|
targetFile.getParent().join(matcher.group(1) + " (" + (number + 1) + ")." + matcher.group(3));
|
||||||
return newFile.toString();
|
return newFile.toString();
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +262,10 @@ public class BrowserFileTransferOperation {
|
|||||||
var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath()));
|
var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath()));
|
||||||
List<FileEntry> list = source.getFileSystem().listFilesRecursively(source.getPath());
|
List<FileEntry> list = source.getFileSystem().listFilesRecursively(source.getPath());
|
||||||
for (FileEntry fileEntry : list) {
|
for (FileEntry fileEntry : list) {
|
||||||
|
if (cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var rel = FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath()));
|
var rel = FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath()));
|
||||||
flatFiles.put(fileEntry, rel);
|
flatFiles.put(fileEntry, rel);
|
||||||
if (fileEntry.getKind() == FileKind.FILE) {
|
if (fileEntry.getKind() == FileKind.FILE) {
|
||||||
@@ -264,6 +288,10 @@ public class BrowserFileTransferOperation {
|
|||||||
var start = Instant.now();
|
var start = Instant.now();
|
||||||
AtomicLong transferred = new AtomicLong();
|
AtomicLong transferred = new AtomicLong();
|
||||||
for (var e : flatFiles.entrySet()) {
|
for (var e : flatFiles.entrySet()) {
|
||||||
|
if (cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var sourceFile = e.getKey();
|
var sourceFile = e.getKey();
|
||||||
var fixedRelPath = new FilePath(e.getValue())
|
var fixedRelPath = new FilePath(e.getValue())
|
||||||
.fileSystemCompatible(
|
.fileSystemCompatible(
|
||||||
@@ -298,6 +326,10 @@ public class BrowserFileTransferOperation {
|
|||||||
private void transfer(
|
private void transfer(
|
||||||
FileEntry sourceFile, String targetFile, AtomicLong transferred, AtomicLong totalSize, Instant start)
|
FileEntry sourceFile, String targetFile, AtomicLong transferred, AtomicLong totalSize, Instant start)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
if (cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
OutputStream outputStream = null;
|
OutputStream outputStream = null;
|
||||||
try {
|
try {
|
||||||
@@ -377,7 +409,7 @@ public class BrowserFileTransferOperation {
|
|||||||
AtomicLong transferred,
|
AtomicLong transferred,
|
||||||
AtomicLong total,
|
AtomicLong total,
|
||||||
Instant start)
|
Instant start)
|
||||||
throws IOException {
|
throws Exception {
|
||||||
// Initialize progress immediately prior to reading anything
|
// Initialize progress immediately prior to reading anything
|
||||||
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
||||||
|
|
||||||
@@ -385,9 +417,48 @@ public class BrowserFileTransferOperation {
|
|||||||
byte[] buffer = new byte[bs];
|
byte[] buffer = new byte[bs];
|
||||||
int read;
|
int read;
|
||||||
while ((read = inputStream.read(buffer, 0, bs)) > 0) {
|
while ((read = inputStream.read(buffer, 0, bs)) > 0) {
|
||||||
|
if (cancelled()) {
|
||||||
|
killStreams();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkTransferValidity()) {
|
||||||
|
killStreams();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(buffer, 0, read);
|
outputStream.write(buffer, 0, read);
|
||||||
transferred.addAndGet(read);
|
transferred.addAndGet(read);
|
||||||
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkTransferValidity() {
|
||||||
|
var sourceFs = files.getFirst().getFileSystem();
|
||||||
|
var targetFs = target.getFileSystem();
|
||||||
|
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
|
||||||
|
if (!same) {
|
||||||
|
var sourceShell = sourceFs.getShell().orElseThrow();
|
||||||
|
var targetShell = targetFs.getShell().orElseThrow();
|
||||||
|
return !sourceShell.getStdout().isClosed()
|
||||||
|
&& !targetShell.getStdin().isClosed();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void killStreams() throws Exception {
|
||||||
|
var sourceFs = files.getFirst().getFileSystem();
|
||||||
|
var targetFs = target.getFileSystem();
|
||||||
|
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
|
||||||
|
if (!same) {
|
||||||
|
var sourceShell = sourceFs.getShell().orElseThrow();
|
||||||
|
var targetShell = targetFs.getShell().orElseThrow();
|
||||||
|
try {
|
||||||
|
sourceShell.closeStdout();
|
||||||
|
} finally {
|
||||||
|
targetShell.closeStdin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import io.xpipe.app.comp.base.HorizontalComp;
|
|||||||
import io.xpipe.app.comp.base.LabelComp;
|
import io.xpipe.app.comp.base.LabelComp;
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||||
import io.xpipe.app.comp.base.PrettySvgComp;
|
|
||||||
import io.xpipe.app.comp.base.TileButtonComp;
|
import io.xpipe.app.comp.base.TileButtonComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.BindingsHelper;
|
import io.xpipe.app.util.BindingsHelper;
|
||||||
import io.xpipe.app.util.DerivedObservableList;
|
import io.xpipe.app.util.DerivedObservableList;
|
||||||
@@ -20,7 +20,6 @@ import io.xpipe.app.util.ThreadHelper;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -52,7 +51,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||||||
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
||||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
var img = new PrettySvgComp(new SimpleStringProperty("graphics/Hips.svg"), 50, 75)
|
var img = PrettyImageHelper.ofSpecificFixedSize("graphics/Hips.svg", 50, 61)
|
||||||
.padding(new Insets(5, 0, 0, 0))
|
.padding(new Insets(5, 0, 0, 0))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
@@ -148,17 +147,20 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
var graphic = entry.get().getEffectiveIconFile();
|
var graphic = entry.get().getEffectiveIconFile();
|
||||||
var view = PrettyImageHelper.ofFixedSize(graphic, 22, 16);
|
var view = PrettyImageHelper.ofFixedSize(graphic, 22, 16);
|
||||||
return new ButtonComp(
|
var name = Bindings.createStringBinding(
|
||||||
new SimpleStringProperty(DataStorage.get().getStoreEntryDisplayName(entry.get())),
|
() -> {
|
||||||
view.createRegion(),
|
var n = DataStorage.get().getStoreEntryDisplayName(entry.get());
|
||||||
() -> {
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
ThreadHelper.runAsync(() -> {
|
},
|
||||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
AppPrefs.get().censorMode());
|
||||||
if (storageEntry.isPresent()) {
|
return new ButtonComp(name, view.createRegion(), () -> {
|
||||||
model.openFileSystemAsync(storageEntry.get().ref(), null, disable);
|
ThreadHelper.runAsync(() -> {
|
||||||
}
|
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
});
|
if (storageEntry.isPresent()) {
|
||||||
})
|
model.openFileSystemAsync(storageEntry.get().ref(), null, disable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
.minWidth(300)
|
.minWidth(300)
|
||||||
.accessibleText(DataStorage.get().getStoreEntryDisplayName(entry.get()))
|
.accessibleText(DataStorage.get().getStoreEntryDisplayName(entry.get()))
|
||||||
.disable(disable)
|
.disable(disable)
|
||||||
@@ -168,7 +170,13 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Comp<?> dirButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) {
|
private Comp<?> dirButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) {
|
||||||
return new ButtonComp(new SimpleStringProperty(e.getPath()), null, () -> {
|
var name = Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var n = e.getPath();
|
||||||
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
|
},
|
||||||
|
AppPrefs.get().censorMode());
|
||||||
|
return new ButtonComp(name, () -> {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
model.restoreStateAsync(e, disable);
|
model.restoreStateAsync(e, disable);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import io.xpipe.app.comp.Comp;
|
|||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
|
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
public final class BrowserHistoryTabModel extends BrowserSessionTab {
|
public final class BrowserHistoryTabModel extends BrowserSessionTab {
|
||||||
|
|
||||||
public BrowserHistoryTabModel(BrowserAbstractSessionModel<?> browserModel) {
|
public BrowserHistoryTabModel(BrowserAbstractSessionModel<?> browserModel) {
|
||||||
super(browserModel, " " + AppI18n.get("history") + " ");
|
super(browserModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -24,11 +26,16 @@ public final class BrowserHistoryTabModel extends BrowserSessionTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {}
|
public void init() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {}
|
public void close() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableValue<String> getName() {
|
||||||
|
return AppI18n.observable("history").map(s -> " " + s + " ");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIcon() {
|
public String getIcon() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -118,8 +118,10 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
|||||||
path.addListener((observable, oldValue, newValue) -> {
|
path.addListener((observable, oldValue, newValue) -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||||
var changed = model.cdSyncOrRetry(newValue, true);
|
var changed = model.cdSyncOrRetry(newValue != null && !newValue.isBlank() ? newValue : null, true);
|
||||||
changed.ifPresent(s -> Platform.runLater(() -> path.set(s)));
|
changed.ifPresent(s -> {
|
||||||
|
Platform.runLater(() -> path.set(!s.isBlank() ? s : null));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -148,8 +150,6 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
|||||||
INVISIBLE, !val && !struc.get().isFocused());
|
INVISIBLE, !val && !struc.get().isFocused());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
struc.get().setPromptText("Overview of " + model.getName());
|
|
||||||
})
|
})
|
||||||
.accessibleText("Current path");
|
.accessibleText("Current path");
|
||||||
return pathBar;
|
return pathBar;
|
||||||
|
|||||||
@@ -60,20 +60,20 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
var commonOverview = new BrowserFileOverviewComp(model, commonPlatform, false);
|
var commonOverview = new BrowserFileOverviewComp(model, commonPlatform, false);
|
||||||
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview)
|
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview, false)
|
||||||
.apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));
|
.apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));
|
||||||
|
|
||||||
var roots = model.getFileSystem().listRoots().stream()
|
var roots = model.getFileSystem().listRoots().stream()
|
||||||
.map(s -> FileEntry.ofDirectory(model.getFileSystem(), s))
|
.map(s -> FileEntry.ofDirectory(model.getFileSystem(), s))
|
||||||
.toList();
|
.toList();
|
||||||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview, false);
|
||||||
|
|
||||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true)
|
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true)
|
||||||
.mapped(s -> FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
|
.mapped(s -> FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
|
||||||
.getList();
|
.getList();
|
||||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview, false);
|
||||||
|
|
||||||
var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview");
|
var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview");
|
||||||
var r = vbox.createRegion();
|
var r = vbox.createRegion();
|
||||||
|
|||||||
@@ -312,7 +312,10 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
|||||||
browserActionMenu.show(menu.getStyleableNode(), Side.RIGHT, 0, 0);
|
browserActionMenu.show(menu.getStyleableNode(), Side.RIGHT, 0, 0);
|
||||||
shownBrowserActionsMenu = browserActionMenu;
|
shownBrowserActionsMenu = browserActionMenu;
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
browserActionMenu.getItems().getFirst().getStyleableNode().requestFocus();
|
var items = browserActionMenu.getItems();
|
||||||
|
if (items.size() > 0) {
|
||||||
|
items.getFirst().getStyleableNode().requestFocus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import io.xpipe.app.comp.SimpleComp;
|
|||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.comp.base.HorizontalComp;
|
import io.xpipe.app.comp.base.HorizontalComp;
|
||||||
|
import io.xpipe.app.comp.base.IconButtonComp;
|
||||||
import io.xpipe.app.comp.base.LabelComp;
|
import io.xpipe.app.comp.base.LabelComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.util.BindingsHelper;
|
import io.xpipe.app.util.BindingsHelper;
|
||||||
import io.xpipe.app.util.HumanReadableFormat;
|
import io.xpipe.app.util.HumanReadableFormat;
|
||||||
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -36,7 +39,8 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||||||
createProgressEstimateStatus(),
|
createProgressEstimateStatus(),
|
||||||
Comp.hspacer(),
|
Comp.hspacer(),
|
||||||
createClipboardStatus(),
|
createClipboardStatus(),
|
||||||
createSelectionStatus()));
|
createSelectionStatus(),
|
||||||
|
createKillButton()));
|
||||||
bar.spacing(15);
|
bar.spacing(15);
|
||||||
bar.styleClass("status-bar");
|
bar.styleClass("status-bar");
|
||||||
|
|
||||||
@@ -50,6 +54,33 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comp<?> createKillButton() {
|
||||||
|
var button = new IconButtonComp("mdi2s-stop", () -> {
|
||||||
|
ThreadHelper.runAsync(() -> {
|
||||||
|
model.killTransfer();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
button.accessibleText("Kill").tooltipKey("killTransfer");
|
||||||
|
var cancel = PlatformThread.sync(model.getTransferCancelled());
|
||||||
|
var hide = Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
if (model.getProgress().getValue() == null
|
||||||
|
|| model.getProgress().getValue().done()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel.getValue()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
cancel,
|
||||||
|
model.getProgress());
|
||||||
|
button.hide(hide);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
private Comp<?> createProgressEstimateStatus() {
|
private Comp<?> createProgressEstimateStatus() {
|
||||||
var text = BindingsHelper.map(model.getProgress(), p -> {
|
var text = BindingsHelper.map(model.getProgress(), p -> {
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
@@ -97,7 +128,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||||||
var progressComp = new LabelComp(text)
|
var progressComp = new LabelComp(text)
|
||||||
.styleClass("progress")
|
.styleClass("progress")
|
||||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
||||||
.prefWidth(180);
|
.hgrow();
|
||||||
return progressComp;
|
return progressComp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.xpipe.app.util.ThreadHelper;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -35,7 +36,7 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
|||||||
BrowserAbstractSessionModel<?> browserModel,
|
BrowserAbstractSessionModel<?> browserModel,
|
||||||
BrowserSessionTab origin,
|
BrowserSessionTab origin,
|
||||||
ObservableList<UUID> terminalRequests) {
|
ObservableList<UUID> terminalRequests) {
|
||||||
super(browserModel, AppI18n.get("terminal"));
|
super(browserModel);
|
||||||
this.origin = origin;
|
this.origin = origin;
|
||||||
this.terminalRequests = terminalRequests;
|
this.terminalRequests = terminalRequests;
|
||||||
}
|
}
|
||||||
@@ -154,6 +155,11 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
|||||||
dockModel.onClose();
|
dockModel.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableValue<String> getName() {
|
||||||
|
return AppI18n.observable("terminal");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIcon() {
|
public String getIcon() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import javafx.beans.property.SimpleStringProperty;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.input.ClipboardContent;
|
import javafx.scene.input.ClipboardContent;
|
||||||
import javafx.scene.input.DragEvent;
|
import javafx.scene.input.DragEvent;
|
||||||
import javafx.scene.input.Dragboard;
|
import javafx.scene.input.Dragboard;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.text.TextAlignment;
|
||||||
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
@@ -41,6 +43,8 @@ public class BrowserTransferComp extends SimpleComp {
|
|||||||
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
var background = new LabelComp(AppI18n.observable("transferDescription"))
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
||||||
.apply(struc -> struc.get().setWrapText(true))
|
.apply(struc -> struc.get().setWrapText(true))
|
||||||
|
.apply(struc -> struc.get().setTextAlignment(TextAlignment.CENTER))
|
||||||
|
.apply(struc -> struc.get().setContentDisplay(ContentDisplay.TOP))
|
||||||
.visible(model.getEmpty());
|
.visible(model.getEmpty());
|
||||||
var backgroundStack = new StackComp(List.of(background))
|
var backgroundStack = new StackComp(List.of(background))
|
||||||
.grow(true, true)
|
.grow(true, true)
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ public class BrowserTransferModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getOpenFileSystemModel() != null
|
var itemModel = item.getOpenFileSystemModel();
|
||||||
&& item.getOpenFileSystemModel().isClosed()) {
|
if (itemModel == null || itemModel.isClosed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +134,16 @@ public class BrowserTransferModel {
|
|||||||
progress -> {
|
progress -> {
|
||||||
// Don't update item progress to keep it as finished
|
// Don't update item progress to keep it as finished
|
||||||
if (progress == null) {
|
if (progress == null) {
|
||||||
item.getOpenFileSystemModel().getProgress().setValue(null);
|
itemModel.getProgress().setValue(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (item.getProgress()) {
|
synchronized (item.getProgress()) {
|
||||||
item.getProgress().setValue(progress);
|
item.getProgress().setValue(progress);
|
||||||
}
|
}
|
||||||
item.getOpenFileSystemModel().getProgress().setValue(progress);
|
itemModel.getProgress().setValue(progress);
|
||||||
});
|
},
|
||||||
|
itemModel.getTransferCancelled());
|
||||||
op.execute();
|
op.execute();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ErrorEvent.fromThrowable(t).handle();
|
ErrorEvent.fromThrowable(t).handle();
|
||||||
|
|||||||
@@ -93,6 +93,18 @@ public abstract class Comp<S extends CompStructure<?>> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void focusOnShow() {
|
||||||
|
onSceneAssign(struc -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
struc.get().requestFocus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Comp<S> minWidth(double width) {
|
public Comp<S> minWidth(double width) {
|
||||||
return apply(struc -> struc.get().setMinWidth(width));
|
return apply(struc -> struc.get().setMinWidth(width));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,35 @@ package io.xpipe.app.comp.base;
|
|||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.CompStructure;
|
import io.xpipe.app.comp.CompStructure;
|
||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
|
||||||
import io.xpipe.app.comp.store.StoreViewState;
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.control.ButtonBase;
|
import javafx.scene.control.ButtonBase;
|
||||||
import javafx.scene.input.KeyCodeCombination;
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
|
||||||
|
|
||||||
private final AppLayoutModel model = AppLayoutModel.get();
|
private final AppLayoutModel model = AppLayoutModel.get();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<Pane> createBase() {
|
public Structure createBase() {
|
||||||
Map<Comp<?>, ObservableValue<Boolean>> map = model.getEntries().stream()
|
Map<Comp<?>, ObservableValue<Boolean>> map = model.getEntries().stream()
|
||||||
.filter(entry -> entry.comp() != null)
|
.filter(entry -> entry.comp() != null)
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
@@ -67,6 +70,28 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
|||||||
});
|
});
|
||||||
AppFont.normal(pane);
|
AppFont.normal(pane);
|
||||||
pane.getStyleClass().add("layout");
|
pane.getStyleClass().add("layout");
|
||||||
return new SimpleCompStructure<>(pane);
|
return new Structure(pane, multiR, sidebarR, new ArrayList<>(multiR.getChildren()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Structure(BorderPane pane, StackPane stack, Region sidebar, List<Node> children)
|
||||||
|
implements CompStructure<BorderPane> {
|
||||||
|
|
||||||
|
public void prepareAddition() {
|
||||||
|
stack.getChildren().clear();
|
||||||
|
sidebar.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
for (var child : children) {
|
||||||
|
stack.getChildren().add(child);
|
||||||
|
PlatformThread.runNestedLoopIteration();
|
||||||
|
}
|
||||||
|
sidebar.setDisable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BorderPane get() {
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppProperties;
|
||||||
|
import io.xpipe.app.core.window.AppDialog;
|
||||||
|
import io.xpipe.app.core.window.AppMainWindow;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import atlantafx.base.util.Animations;
|
||||||
|
|
||||||
|
public class AppMainWindowContentComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final Stage stage;
|
||||||
|
|
||||||
|
public AppMainWindowContentComp(Stage stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var overlay = AppDialog.getModalOverlay();
|
||||||
|
var loaded = AppMainWindow.getLoadedContent();
|
||||||
|
var bg = Comp.of(() -> {
|
||||||
|
var loadingIcon = new ImageView();
|
||||||
|
loadingIcon.setFitWidth(64);
|
||||||
|
loadingIcon.setFitHeight(64);
|
||||||
|
|
||||||
|
var anim = Animations.pulse(loadingIcon, 1.1);
|
||||||
|
if (OsType.getLocal() != OsType.LINUX) {
|
||||||
|
anim.setRate(0.85);
|
||||||
|
anim.setCycleCount(Animation.INDEFINITE);
|
||||||
|
anim.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This allows for assigning logos even if AppImages has not been initialized yet
|
||||||
|
var dir = "img/logo/";
|
||||||
|
AppResources.with(AppResources.XPIPE_MODULE, dir, path -> {
|
||||||
|
loadingIcon.setImage(AppImages.loadImage(path.resolve("loading.png")));
|
||||||
|
});
|
||||||
|
|
||||||
|
var version = new LabelComp((AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " "
|
||||||
|
+ AppProperties.get().getVersion());
|
||||||
|
version.apply(struc -> {
|
||||||
|
AppFont.setSize(struc.get(), 1);
|
||||||
|
struc.get().setOpacity(0.6);
|
||||||
|
});
|
||||||
|
|
||||||
|
var text = new LabelComp(AppMainWindow.getLoadingText());
|
||||||
|
text.apply(struc -> {
|
||||||
|
struc.get().setOpacity(0.8);
|
||||||
|
});
|
||||||
|
|
||||||
|
var vbox = new VBox(
|
||||||
|
Comp.vspacer().createRegion(),
|
||||||
|
loadingIcon,
|
||||||
|
Comp.vspacer(19).createRegion(),
|
||||||
|
version.createRegion(),
|
||||||
|
Comp.vspacer().createRegion(),
|
||||||
|
text.createRegion(),
|
||||||
|
Comp.vspacer(20).createRegion());
|
||||||
|
vbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var pane = new StackPane(vbox);
|
||||||
|
pane.setAlignment(Pos.CENTER);
|
||||||
|
pane.getStyleClass().add("background");
|
||||||
|
|
||||||
|
loaded.subscribe(struc -> {
|
||||||
|
if (struc != null) {
|
||||||
|
PlatformThread.runNestedLoopIteration();
|
||||||
|
struc.prepareAddition();
|
||||||
|
anim.stop();
|
||||||
|
pane.getChildren().add(struc.get());
|
||||||
|
struc.show();
|
||||||
|
pane.getChildren().remove(vbox);
|
||||||
|
pane.getStyleClass().remove("background");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
overlay.addListener((ListChangeListener<? super ModalOverlay>) c -> {
|
||||||
|
if (c.next() && c.wasAdded()) {
|
||||||
|
stage.requestFocus();
|
||||||
|
|
||||||
|
// Close blocking modal windows
|
||||||
|
var childWindows = Window.getWindows().stream()
|
||||||
|
.filter(window -> window instanceof Stage s && stage.equals(s.getOwner()))
|
||||||
|
.toList();
|
||||||
|
childWindows.forEach(window -> {
|
||||||
|
((Stage) window).close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loaded.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
stage.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pane;
|
||||||
|
});
|
||||||
|
var modal = new ModalOverlayStackComp(bg, overlay);
|
||||||
|
return modal.createRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@ package io.xpipe.app.comp.base;
|
|||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.CompStructure;
|
import io.xpipe.app.comp.CompStructure;
|
||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.css.Size;
|
import javafx.css.Size;
|
||||||
@@ -13,14 +13,16 @@ import javafx.css.SizeUnits;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
public class ButtonComp extends Comp<CompStructure<Button>> {
|
public class ButtonComp extends Comp<CompStructure<Button>> {
|
||||||
|
|
||||||
private final ObservableValue<String> name;
|
private final ObservableValue<String> name;
|
||||||
private final ObjectProperty<Node> graphic;
|
private final ObservableValue<LabelGraphic> graphic;
|
||||||
private final Runnable listener;
|
private final Runnable listener;
|
||||||
|
|
||||||
public ButtonComp(ObservableValue<String> name, Runnable listener) {
|
public ButtonComp(ObservableValue<String> name, Runnable listener) {
|
||||||
@@ -31,33 +33,39 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
|
|||||||
|
|
||||||
public ButtonComp(ObservableValue<String> name, Node graphic, Runnable listener) {
|
public ButtonComp(ObservableValue<String> name, Node graphic, Runnable listener) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.graphic = new SimpleObjectProperty<>(graphic);
|
this.graphic = new SimpleObjectProperty<>(new LabelGraphic.NodeGraphic(() -> graphic));
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getGraphic() {
|
|
||||||
return graphic.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Node> graphicProperty() {
|
|
||||||
return graphic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<Button> createBase() {
|
public CompStructure<Button> createBase() {
|
||||||
var button = new Button(null);
|
var button = new Button(null);
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
button.textProperty().bind(PlatformThread.sync(name));
|
name.subscribe(t -> {
|
||||||
}
|
PlatformThread.runLaterIfNeeded(() -> button.setText(t));
|
||||||
var graphic = getGraphic();
|
|
||||||
if (graphic instanceof FontIcon f) {
|
|
||||||
// f.iconColorProperty().bind(button.textFillProperty());
|
|
||||||
button.fontProperty().subscribe(c -> {
|
|
||||||
f.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (graphic != null) {
|
||||||
|
graphic.subscribe(t -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
if (t == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
button.setGraphic(getGraphic());
|
var n = t.createGraphicNode();
|
||||||
|
button.setGraphic(n);
|
||||||
|
if (n instanceof FontIcon f && button.getFont() != null) {
|
||||||
|
f.setIconSize((int) new Size(button.getFont().getSize(), SizeUnits.PT).pixels());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
button.fontProperty().subscribe(c -> {
|
||||||
|
if (button.getGraphic() instanceof FontIcon f) {
|
||||||
|
f.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
button.setOnAction(e -> getListener().run());
|
button.setOnAction(e -> getListener().run());
|
||||||
button.getStyleClass().add("button-comp");
|
button.getStyleClass().add("button-comp");
|
||||||
return new SimpleCompStructure<>(button);
|
return new SimpleCompStructure<>(button);
|
||||||
|
|||||||
@@ -92,5 +92,11 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
|||||||
return new SimpleCompStructure<>(vbox);
|
return new SimpleCompStructure<>(vbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Entry(ObservableValue<String> name, Comp<?> comp) {}
|
public record Entry(ObservableValue<String> name, Comp<?> comp) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import io.xpipe.app.comp.CompStructure;
|
|||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,6 +36,11 @@ public class ComboTextFieldComp extends Comp<CompStructure<ComboBox<String>>> {
|
|||||||
@Override
|
@Override
|
||||||
public CompStructure<ComboBox<String>> createBase() {
|
public CompStructure<ComboBox<String>> createBase() {
|
||||||
var text = new ComboBox<>(FXCollections.observableList(predefinedValues));
|
var text = new ComboBox<>(FXCollections.observableList(predefinedValues));
|
||||||
|
text.addEventFilter(KeyEvent.ANY, event -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
text.commitValue();
|
||||||
|
});
|
||||||
|
});
|
||||||
text.setEditable(true);
|
text.setEditable(true);
|
||||||
text.setMaxWidth(2000);
|
text.setMaxWidth(2000);
|
||||||
text.setValue(value.getValue() != null ? value.getValue() : null);
|
text.setValue(value.getValue() != null ? value.getValue() : null);
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import io.xpipe.app.core.window.AppWindowHelper;
|
|||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.ContextualFileReference;
|
import io.xpipe.app.storage.ContextualFileReference;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
@@ -27,7 +27,6 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
|||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -42,22 +41,22 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
|
|
||||||
private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem;
|
private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem;
|
||||||
private final Property<String> filePath;
|
private final Property<String> filePath;
|
||||||
private final boolean allowSync;
|
private final ContextualFileReferenceSync sync;
|
||||||
private final List<PreviousFileReference> previousFileReferences;
|
private final List<PreviousFileReference> previousFileReferences;
|
||||||
|
|
||||||
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
|
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
|
||||||
Property<DataStoreEntryRef<T>> fileSystem,
|
Property<DataStoreEntryRef<T>> fileSystem,
|
||||||
Property<String> filePath,
|
Property<String> filePath,
|
||||||
boolean allowSync,
|
ContextualFileReferenceSync sync,
|
||||||
List<PreviousFileReference> previousFileReferences) {
|
List<PreviousFileReference> previousFileReferences) {
|
||||||
this.allowSync = allowSync;
|
this.sync = sync;
|
||||||
this.previousFileReferences = previousFileReferences;
|
this.previousFileReferences = previousFileReferences;
|
||||||
this.fileSystem = new SimpleObjectProperty<>();
|
this.fileSystem = new SimpleObjectProperty<>();
|
||||||
fileSystem.subscribe(val -> {
|
fileSystem.subscribe(val -> {
|
||||||
this.fileSystem.setValue(val);
|
this.fileSystem.setValue(val);
|
||||||
});
|
});
|
||||||
this.fileSystem.addListener((observable, oldValue, newValue) -> {
|
this.fileSystem.addListener((observable, oldValue, newValue) -> {
|
||||||
fileSystem.setValue(newValue != null ? newValue.get().ref() : null);
|
fileSystem.setValue(newValue != null ? newValue.asNeeded() : null);
|
||||||
});
|
});
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
})
|
})
|
||||||
.styleClass(allowSync ? Styles.CENTER_PILL : Styles.RIGHT_PILL)
|
.styleClass(sync != null ? Styles.CENTER_PILL : Styles.RIGHT_PILL)
|
||||||
.grow(false, true);
|
.grow(false, true);
|
||||||
|
|
||||||
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
|
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
|
||||||
@@ -99,9 +98,8 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var data = DataStorage.get().getDataDir();
|
|
||||||
var f = data.resolve(FileNames.getFileName(currentPath.trim()));
|
|
||||||
var source = Path.of(currentPath.trim());
|
var source = Path.of(currentPath.trim());
|
||||||
|
var target = sync.getTargetLocation().apply(source);
|
||||||
if (Files.exists(source)) {
|
if (Files.exists(source)) {
|
||||||
var shouldCopy = AppWindowHelper.showConfirmationAlert(
|
var shouldCopy = AppWindowHelper.showConfirmationAlert(
|
||||||
"confirmGitShareTitle", "confirmGitShareHeader", "confirmGitShareContent");
|
"confirmGitShareTitle", "confirmGitShareHeader", "confirmGitShareContent");
|
||||||
@@ -109,9 +107,11 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.copy(source, f, StandardCopyOption.REPLACE_EXISTING);
|
var handler = DataStorageSyncHandler.getInstance();
|
||||||
|
var syncedTarget = handler.addDataFile(
|
||||||
|
source, target, sync.getPerUser().test(source));
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
filePath.setValue(f.toString());
|
filePath.setValue(syncedTarget.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -120,11 +120,17 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
});
|
});
|
||||||
gitShareButton.tooltipKey("gitShareFileTooltip");
|
gitShareButton.tooltipKey("gitShareFileTooltip");
|
||||||
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
|
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
|
||||||
|
gitShareButton.disable(Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
return filePath.getValue() != null
|
||||||
|
&& ContextualFileReference.of(filePath.getValue()).isInDataDirectory();
|
||||||
|
},
|
||||||
|
filePath));
|
||||||
|
|
||||||
var nodes = new ArrayList<Comp<?>>();
|
var nodes = new ArrayList<Comp<?>>();
|
||||||
nodes.add(path);
|
nodes.add(path);
|
||||||
nodes.add(fileBrowseButton);
|
nodes.add(fileBrowseButton);
|
||||||
if (allowSync) {
|
if (sync != null) {
|
||||||
nodes.add(gitShareButton);
|
nodes.add(gitShareButton);
|
||||||
}
|
}
|
||||||
var layout = new HorizontalComp(nodes).apply(struc -> struc.get().setFillHeight(true));
|
var layout = new HorizontalComp(nodes).apply(struc -> struc.get().setFillHeight(true));
|
||||||
@@ -139,7 +145,9 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Comp<?> createComboBox() {
|
private Comp<?> createComboBox() {
|
||||||
var items = previousFileReferences.stream()
|
var allFiles = new ArrayList<>(previousFileReferences);
|
||||||
|
allFiles.addAll(sync != null ? sync.getExistingFiles() : List.of());
|
||||||
|
var items = allFiles.stream()
|
||||||
.map(previousFileReference -> previousFileReference.getPath().toString())
|
.map(previousFileReference -> previousFileReference.getPath().toString())
|
||||||
.toList();
|
.toList();
|
||||||
var combo = new ComboTextFieldComp(filePath, items, param -> {
|
var combo = new ComboTextFieldComp(filePath, items, param -> {
|
||||||
@@ -151,7 +159,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var display = previousFileReferences.stream()
|
var display = allFiles.stream()
|
||||||
.filter(ref -> ref.path.toString().equals(item))
|
.filter(ref -> ref.path.toString().equals(item))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(previousFileReference -> previousFileReference.getDisplayName())
|
.map(previousFileReference -> previousFileReference.getDisplayName())
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class ContextualFileReferenceSync {
|
||||||
|
|
||||||
|
Path existingFilesDir;
|
||||||
|
Predicate<Path> perUser;
|
||||||
|
UnaryOperator<Path> targetLocation;
|
||||||
|
|
||||||
|
public List<ContextualFileReferenceChoiceComp.PreviousFileReference> getExistingFiles() {
|
||||||
|
var dataDir = DataStorage.get().getDataDir();
|
||||||
|
var files = new ArrayList<ContextualFileReferenceChoiceComp.PreviousFileReference>();
|
||||||
|
DataStorageSyncHandler.getInstance().getSavedDataFiles().forEach(path -> {
|
||||||
|
if (!path.startsWith(dataDir.resolve(existingFilesDir))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.add(new ContextualFileReferenceChoiceComp.PreviousFileReference(
|
||||||
|
path.getFileName().toString() + " (Git)", path));
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Comp<?> finishButton() {
|
protected Comp<?> finishButton() {
|
||||||
return new ButtonComp(AppI18n.observable(finishKey()), null, this::finish)
|
return new ButtonComp(AppI18n.observable(finishKey()), this::finish)
|
||||||
.apply(struc -> struc.get().setDefaultButton(true))
|
.apply(struc -> struc.get().setDefaultButton(true))
|
||||||
.styleClass(Styles.ACCENT)
|
.styleClass(Styles.ACCENT)
|
||||||
.styleClass("next");
|
.styleClass("next");
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package io.xpipe.app.comp.base;
|
|
||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
|
||||||
import io.xpipe.app.util.PlatformThread;
|
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.scene.control.TextArea;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
|
||||||
public class ErrorOverlayComp extends SimpleComp {
|
|
||||||
|
|
||||||
Comp<?> background;
|
|
||||||
Property<String> text;
|
|
||||||
|
|
||||||
public ErrorOverlayComp(Comp<?> background, Property<String> text) {
|
|
||||||
this.background = background;
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Region createSimple() {
|
|
||||||
var content = new SimpleObjectProperty<ModalOverlayComp.OverlayContent>();
|
|
||||||
this.text.addListener((observable, oldValue, newValue) -> {
|
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
|
||||||
var comp = Comp.of(() -> {
|
|
||||||
var l = new TextArea();
|
|
||||||
l.textProperty().bind(PlatformThread.sync(text));
|
|
||||||
l.setWrapText(true);
|
|
||||||
l.getStyleClass().add("error-overlay-comp");
|
|
||||||
l.setEditable(false);
|
|
||||||
return l;
|
|
||||||
});
|
|
||||||
content.set(new ModalOverlayComp.OverlayContent(
|
|
||||||
"error",
|
|
||||||
comp,
|
|
||||||
Comp.of(() -> {
|
|
||||||
var graphic = new FontIcon("mdomz-warning");
|
|
||||||
graphic.setIconColor(Color.RED);
|
|
||||||
return new StackPane(graphic);
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
() -> {},
|
|
||||||
false));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
content.addListener((observable, oldValue, newValue) -> {
|
|
||||||
// Handle close
|
|
||||||
if (newValue == null) {
|
|
||||||
this.text.setValue(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return new ModalOverlayComp(background, content).createRegion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ public class FontIconComp extends Comp<FontIconComp.Structure> {
|
|||||||
@Override
|
@Override
|
||||||
public FontIconComp.Structure createBase() {
|
public FontIconComp.Structure createBase() {
|
||||||
var fi = new FontIcon();
|
var fi = new FontIcon();
|
||||||
var obs = PlatformThread.sync(icon);
|
|
||||||
icon.subscribe(val -> {
|
icon.subscribe(val -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
fi.setIconLiteral(val);
|
fi.setIconLiteral(val);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.comp.CompStructure;
|
||||||
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
import atlantafx.base.layout.InputGroup;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InputGroupComp extends Comp<CompStructure<InputGroup>> {
|
||||||
|
|
||||||
|
private final List<Comp<?>> entries;
|
||||||
|
|
||||||
|
public InputGroupComp(List<Comp<?>> comps) {
|
||||||
|
entries = List.copyOf(comps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comp<CompStructure<InputGroup>> spacing(double spacing) {
|
||||||
|
return apply(struc -> struc.get().setSpacing(spacing));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompStructure<InputGroup> createBase() {
|
||||||
|
InputGroup b = new InputGroup();
|
||||||
|
b.getStyleClass().add("input-group-comp");
|
||||||
|
for (var entry : entries) {
|
||||||
|
b.getChildren().add(entry.createRegion());
|
||||||
|
}
|
||||||
|
b.setAlignment(Pos.CENTER);
|
||||||
|
return new SimpleCompStructure<>(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
public class IntroComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final String translationsKey;
|
||||||
|
private final LabelGraphic graphic;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private LabelGraphic buttonGraphic;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private Runnable buttonAction;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean buttonDefault;
|
||||||
|
|
||||||
|
public IntroComp(String translationsKey, LabelGraphic graphic) {
|
||||||
|
this.translationsKey = translationsKey;
|
||||||
|
this.graphic = graphic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region createSimple() {
|
||||||
|
var title = new Label();
|
||||||
|
title.textProperty().bind(AppI18n.observable(translationsKey + "Header"));
|
||||||
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
}
|
||||||
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
|
var introDesc = new Label();
|
||||||
|
introDesc.textProperty().bind(AppI18n.observable(translationsKey + "Content"));
|
||||||
|
introDesc.setWrapText(true);
|
||||||
|
introDesc.setMaxWidth(470);
|
||||||
|
|
||||||
|
var img = graphic.createGraphicNode();
|
||||||
|
if (img instanceof FontIcon fontIcon) {
|
||||||
|
fontIcon.setIconSize(80);
|
||||||
|
}
|
||||||
|
var text = new VBox(title, introDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
var hbox = new HBox(img, text);
|
||||||
|
hbox.setSpacing(55);
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var button = new ButtonComp(
|
||||||
|
AppI18n.observable(translationsKey + "Button"),
|
||||||
|
buttonGraphic != null ? buttonGraphic.createGraphicNode() : null,
|
||||||
|
buttonAction);
|
||||||
|
if (buttonDefault) {
|
||||||
|
button.apply(struc -> struc.get().setDefaultButton(true));
|
||||||
|
}
|
||||||
|
var buttonPane = new StackPane(button.createRegion());
|
||||||
|
buttonPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var v = new VBox(hbox, buttonPane);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
v.setSpacing(20);
|
||||||
|
v.getStyleClass().add("intro");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class IntroListComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final List<IntroComp> intros;
|
||||||
|
|
||||||
|
public IntroListComp(List<IntroComp> intros) {
|
||||||
|
this.intros = intros;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region createSimple() {
|
||||||
|
List<Comp<?>> l = intros.stream().map(introComp -> (Comp<?>) introComp).collect(Collectors.toList());
|
||||||
|
var v = new VerticalComp(l).createStructure().get();
|
||||||
|
v.setSpacing(80);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
var sp = new StackPane(v);
|
||||||
|
sp.setPadding(new Insets(40, 0, 0, 0));
|
||||||
|
sp.setAlignment(Pos.CENTER);
|
||||||
|
sp.setPickOnBounds(false);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,23 +3,33 @@ package io.xpipe.app.comp.base;
|
|||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.CompStructure;
|
import io.xpipe.app.comp.CompStructure;
|
||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
public class LabelComp extends Comp<CompStructure<Label>> {
|
public class LabelComp extends Comp<CompStructure<Label>> {
|
||||||
|
|
||||||
private final ObservableValue<String> text;
|
private final ObservableValue<String> text;
|
||||||
|
private final ObservableValue<LabelGraphic> graphic;
|
||||||
|
|
||||||
|
public LabelComp(String text, LabelGraphic graphic) {
|
||||||
|
this(new SimpleStringProperty(text), new SimpleObjectProperty<>(graphic));
|
||||||
|
}
|
||||||
|
|
||||||
public LabelComp(String text) {
|
public LabelComp(String text) {
|
||||||
this.text = new SimpleStringProperty(text);
|
this(new SimpleStringProperty(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LabelComp(ObservableValue<String> text) {
|
public LabelComp(ObservableValue<String> text) {
|
||||||
this.text = text;
|
this(text, new SimpleObjectProperty<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -28,6 +38,9 @@ public class LabelComp extends Comp<CompStructure<Label>> {
|
|||||||
text.subscribe(t -> {
|
text.subscribe(t -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> label.setText(t));
|
PlatformThread.runLaterIfNeeded(() -> label.setText(t));
|
||||||
});
|
});
|
||||||
|
graphic.subscribe(t -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> label.setGraphic(t != null ? t.createGraphicNode() : null));
|
||||||
|
});
|
||||||
label.setAlignment(Pos.CENTER);
|
label.setAlignment(Pos.CENTER);
|
||||||
return new SimpleCompStructure<>(label);
|
return new SimpleCompStructure<>(label);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import javafx.scene.control.ScrollPane;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -35,10 +37,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||||||
private final int limit = Integer.MAX_VALUE;
|
private final int limit = Integer.MAX_VALUE;
|
||||||
private final boolean scrollBar;
|
private final boolean scrollBar;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private int platformPauseInterval = -1;
|
||||||
|
|
||||||
public ListBoxViewComp(
|
public ListBoxViewComp(
|
||||||
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
||||||
this.shown = PlatformThread.sync(shown);
|
this.shown = shown;
|
||||||
this.all = PlatformThread.sync(all);
|
this.all = all;
|
||||||
this.compFunction = compFunction;
|
this.compFunction = compFunction;
|
||||||
this.scrollBar = scrollBar;
|
this.scrollBar = scrollBar;
|
||||||
}
|
}
|
||||||
@@ -95,10 +100,17 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||||||
// Clear cache of unused values
|
// Clear cache of unused values
|
||||||
cache.keySet().removeIf(t -> !all.contains(t));
|
cache.keySet().removeIf(t -> !all.contains(t));
|
||||||
|
|
||||||
|
final long[] lastPause = {System.currentTimeMillis()};
|
||||||
// Create copy to reduce chances of concurrent modification
|
// Create copy to reduce chances of concurrent modification
|
||||||
var shownCopy = new ArrayList<>(shown);
|
var shownCopy = new ArrayList<>(shown);
|
||||||
var newShown = shownCopy.stream()
|
var newShown = shownCopy.stream()
|
||||||
.map(v -> {
|
.map(v -> {
|
||||||
|
var elapsed = System.currentTimeMillis() - lastPause[0];
|
||||||
|
if (platformPauseInterval != -1 && elapsed > platformPauseInterval) {
|
||||||
|
PlatformThread.runNestedLoopIteration();
|
||||||
|
lastPause[0] = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
if (!cache.containsKey(v)) {
|
if (!cache.containsKey(v)) {
|
||||||
var comp = compFunction.apply(v);
|
var comp = compFunction.apply(v);
|
||||||
cache.put(v, comp != null ? comp.createRegion() : null);
|
cache.put(v, comp != null ? comp.createRegion() : null);
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package io.xpipe.app.comp.base;
|
|||||||
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.beans.property.ListProperty;
|
import javafx.beans.property.ListProperty;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -19,16 +22,17 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class ListSelectorComp<T> extends SimpleComp {
|
public class ListSelectorComp<T> extends SimpleComp {
|
||||||
|
|
||||||
List<T> values;
|
ObservableList<T> values;
|
||||||
Function<T, String> toString;
|
Function<T, String> toString;
|
||||||
ListProperty<T> selected;
|
ListProperty<T> selected;
|
||||||
Predicate<T> disable;
|
Predicate<T> disable;
|
||||||
boolean showAllSelector;
|
Supplier<Boolean> showAllSelector;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
@@ -36,7 +40,23 @@ public class ListSelectorComp<T> extends SimpleComp {
|
|||||||
vbox.setSpacing(8);
|
vbox.setSpacing(8);
|
||||||
vbox.getStyleClass().add("list-content");
|
vbox.getStyleClass().add("list-content");
|
||||||
var cbs = new ArrayList<CheckBox>();
|
var cbs = new ArrayList<CheckBox>();
|
||||||
for (var v : values) {
|
update(vbox, cbs);
|
||||||
|
values.addListener((ListChangeListener<? super T>) c -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
update(vbox, cbs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var sp = new ScrollPane(vbox);
|
||||||
|
sp.setFitToWidth(true);
|
||||||
|
sp.getStyleClass().add("list-selector-comp");
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(VBox vbox, List<CheckBox> cbs) {
|
||||||
|
var currentVals = new ArrayList<>(values);
|
||||||
|
vbox.getChildren().clear();
|
||||||
|
cbs.clear();
|
||||||
|
for (var v : currentVals) {
|
||||||
var cb = new CheckBox(null);
|
var cb = new CheckBox(null);
|
||||||
if (disable.test(v)) {
|
if (disable.test(v)) {
|
||||||
cb.setDisable(true);
|
cb.setDisable(true);
|
||||||
@@ -65,7 +85,7 @@ public class ListSelectorComp<T> extends SimpleComp {
|
|||||||
vbox.getChildren().add(l);
|
vbox.getChildren().add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAllSelector) {
|
if (showAllSelector.get()) {
|
||||||
var allSelector = new CheckBox(null);
|
var allSelector = new CheckBox(null);
|
||||||
allSelector.setSelected(
|
allSelector.setSelected(
|
||||||
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
values.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||||
@@ -85,10 +105,5 @@ public class ListSelectorComp<T> extends SimpleComp {
|
|||||||
vbox.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
vbox.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
||||||
vbox.getChildren().add(l);
|
vbox.getChildren().add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp = new ScrollPane(vbox);
|
|
||||||
sp.setFitToWidth(true);
|
|
||||||
sp.getStyleClass().add("list-selector-comp");
|
|
||||||
return sp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import atlantafx.base.controls.RingProgressIndicator;
|
|||||||
|
|
||||||
public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||||
|
|
||||||
private static final double FPS = 30.0;
|
|
||||||
private static final double cycleDurationSeconds = 4.0;
|
|
||||||
private final Comp<?> comp;
|
private final Comp<?> comp;
|
||||||
private final ObservableValue<Boolean> showLoading;
|
private final ObservableValue<Boolean> showLoading;
|
||||||
private final ObservableValue<Number> progress;
|
private final ObservableValue<Number> progress;
|
||||||
@@ -43,11 +41,6 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||||||
loading.progressProperty().bind(progress);
|
loading.progressProperty().bind(progress);
|
||||||
loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode()));
|
loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode()));
|
||||||
|
|
||||||
// var pane = new StackPane();
|
|
||||||
// Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode();
|
|
||||||
// pane.getChildren().add(node);
|
|
||||||
// pane.setAlignment(Pos.CENTER);
|
|
||||||
|
|
||||||
var loadingOverlay = new StackPane(loading);
|
var loadingOverlay = new StackPane(loading);
|
||||||
loadingOverlay.getStyleClass().add("loading-comp");
|
loadingOverlay.getStyleClass().add("loading-comp");
|
||||||
loadingOverlay.setVisible(showLoading.getValue());
|
loadingOverlay.setVisible(showLoading.getValue());
|
||||||
@@ -93,6 +86,9 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||||||
r.heightProperty()));
|
r.heightProperty()));
|
||||||
loading.prefHeightProperty().bind(loading.prefWidthProperty());
|
loading.prefHeightProperty().bind(loading.prefWidthProperty());
|
||||||
|
|
||||||
|
stack.prefWidthProperty().bind(r.prefWidthProperty());
|
||||||
|
stack.prefHeightProperty().bind(r.prefHeightProperty());
|
||||||
|
|
||||||
return new SimpleCompStructure<>(stack);
|
return new SimpleCompStructure<>(stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.web.WebEngine;
|
import javafx.scene.web.WebEngine;
|
||||||
@@ -33,15 +34,19 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||||||
|
|
||||||
private final ObservableValue<String> markdown;
|
private final ObservableValue<String> markdown;
|
||||||
private final UnaryOperator<String> htmlTransformation;
|
private final UnaryOperator<String> htmlTransformation;
|
||||||
|
private final boolean bodyPadding;
|
||||||
|
|
||||||
public MarkdownComp(String markdown, UnaryOperator<String> htmlTransformation) {
|
public MarkdownComp(String markdown, UnaryOperator<String> htmlTransformation, boolean bodyPadding) {
|
||||||
this.markdown = new SimpleStringProperty(markdown);
|
this.markdown = new SimpleStringProperty(markdown);
|
||||||
this.htmlTransformation = htmlTransformation;
|
this.htmlTransformation = htmlTransformation;
|
||||||
|
this.bodyPadding = bodyPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MarkdownComp(ObservableValue<String> markdown, UnaryOperator<String> htmlTransformation) {
|
public MarkdownComp(
|
||||||
|
ObservableValue<String> markdown, UnaryOperator<String> htmlTransformation, boolean bodyPadding) {
|
||||||
this.markdown = markdown;
|
this.markdown = markdown;
|
||||||
this.htmlTransformation = htmlTransformation;
|
this.htmlTransformation = htmlTransformation;
|
||||||
|
this.bodyPadding = bodyPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path TEMP;
|
private static Path TEMP;
|
||||||
@@ -55,13 +60,19 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = markdown.hashCode();
|
int hash;
|
||||||
|
// Rebuild files for updates in case the css have been changed
|
||||||
|
if (AppProperties.get().isImage()) {
|
||||||
|
hash = markdown.hashCode() + AppProperties.get().getVersion().hashCode();
|
||||||
|
} else {
|
||||||
|
hash = markdown.hashCode();
|
||||||
|
}
|
||||||
var file = TEMP.resolve("md-" + hash + ".html");
|
var file = TEMP.resolve("md-" + hash + ".html");
|
||||||
if (Files.exists(file)) {
|
if (Files.exists(file)) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = MarkdownHelper.toHtml(markdown, s -> s, htmlTransformation, null);
|
var html = MarkdownHelper.toHtml(markdown, s -> s, htmlTransformation, bodyPadding ? "padded" : null);
|
||||||
try {
|
try {
|
||||||
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014
|
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014
|
||||||
FileUtils.forceMkdir(file.getParent().toFile());
|
FileUtils.forceMkdir(file.getParent().toFile());
|
||||||
@@ -79,10 +90,10 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||||||
var wv = new WebView();
|
var wv = new WebView();
|
||||||
wv.getEngine().setJavaScriptEnabled(false);
|
wv.getEngine().setJavaScriptEnabled(false);
|
||||||
wv.setContextMenuEnabled(false);
|
wv.setContextMenuEnabled(false);
|
||||||
|
wv.setPageFill(Color.TRANSPARENT);
|
||||||
wv.getEngine()
|
wv.getEngine()
|
||||||
.setUserDataDirectory(
|
.setUserDataDirectory(
|
||||||
AppProperties.get().getDataDir().resolve("webview").toFile());
|
AppProperties.get().getDataDir().resolve("webview").toFile());
|
||||||
wv.setPageFill(Color.TRANSPARENT);
|
|
||||||
var theme = AppPrefs.get() != null
|
var theme = AppPrefs.get() != null
|
||||||
&& AppPrefs.get().theme.getValue() != null
|
&& AppPrefs.get().theme.getValue() != null
|
||||||
&& AppPrefs.get().theme.getValue().isDark()
|
&& AppPrefs.get().theme.getValue().isDark()
|
||||||
@@ -99,6 +110,14 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix initial scrollbar size
|
||||||
|
wv.lookupAll(".scroll-bar").stream().findFirst().ifPresent(node -> {
|
||||||
|
Region region = (Region) node;
|
||||||
|
region.setMinWidth(0);
|
||||||
|
region.setPrefWidth(7);
|
||||||
|
region.setMaxWidth(7);
|
||||||
|
});
|
||||||
|
|
||||||
wv.getStyleClass().add("markdown-comp");
|
wv.getStyleClass().add("markdown-comp");
|
||||||
addLinkHandler(wv.getEngine());
|
addLinkHandler(wv.getEngine());
|
||||||
return wv;
|
return wv;
|
||||||
@@ -109,7 +128,7 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||||||
.stateProperty()
|
.stateProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> Platform.runLater(() -> {
|
.addListener((observable, oldValue, newValue) -> Platform.runLater(() -> {
|
||||||
String toBeopen = engine.getLoadWorker().getMessage().trim().replace("Loading ", "");
|
String toBeopen = engine.getLoadWorker().getMessage().trim().replace("Loading ", "");
|
||||||
if (toBeopen.contains("http://") || toBeopen.contains("https://")) {
|
if (toBeopen.contains("http://") || toBeopen.contains("https://") || toBeopen.contains("mailto:")) {
|
||||||
engine.getLoadWorker().cancel();
|
engine.getLoadWorker().cancel();
|
||||||
Hyperlinks.open(toBeopen);
|
Hyperlinks.open(toBeopen);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class MarkdownEditorComp extends Comp<MarkdownEditorComp.Structure> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Structure createBase() {
|
public Structure createBase() {
|
||||||
var markdown = new MarkdownComp(value, s -> s).createRegion();
|
var markdown = new MarkdownComp(value, s -> s, true).createRegion();
|
||||||
var editButton = createOpenButton();
|
var editButton = createOpenButton();
|
||||||
var pane = new AnchorPane(markdown, editButton);
|
var pane = new AnchorPane(markdown, editButton);
|
||||||
pane.setPickOnBounds(false);
|
pane.setPickOnBounds(false);
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
|
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.experimental.NonFinal;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class ModalButton {
|
||||||
|
String key;
|
||||||
|
Runnable action;
|
||||||
|
boolean close;
|
||||||
|
boolean defaultButton;
|
||||||
|
|
||||||
|
public ModalButton(String key, Runnable action, boolean close, boolean defaultButton) {
|
||||||
|
this.key = key;
|
||||||
|
this.action = action;
|
||||||
|
this.close = close;
|
||||||
|
this.defaultButton = defaultButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonFinal
|
||||||
|
Consumer<Button> augment;
|
||||||
|
|
||||||
|
public static ModalButton finish(Runnable action) {
|
||||||
|
return new ModalButton("finish", action, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton ok(Runnable action) {
|
||||||
|
return new ModalButton("ok", action, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton ok() {
|
||||||
|
return new ModalButton("ok", null, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton cancel() {
|
||||||
|
return new ModalButton("cancel", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton skip() {
|
||||||
|
return new ModalButton("skip", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton confirm(Runnable action) {
|
||||||
|
return new ModalButton("confirm", action, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalButton quit() {
|
||||||
|
return new ModalButton(
|
||||||
|
"quit",
|
||||||
|
() -> {
|
||||||
|
OperationMode.halt(1);
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModalButton augment(Consumer<Button> augment) {
|
||||||
|
this.augment = augment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Runnable toggle(Property<Boolean> prop) {
|
||||||
|
return () -> {
|
||||||
|
prop.setValue(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.core.window.AppDialog;
|
||||||
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.NonFinal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@With
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public class ModalOverlay {
|
||||||
|
|
||||||
|
public static ModalOverlay of(Comp<?> content) {
|
||||||
|
return of(null, content, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalOverlay of(String titleKey, Comp<?> content) {
|
||||||
|
return of(titleKey, content, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModalOverlay of(String titleKey, Comp<?> content, LabelGraphic graphic) {
|
||||||
|
return new ModalOverlay(titleKey, content, graphic, new ArrayList<>(), false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModalOverlay withDefaultButtons(Runnable action) {
|
||||||
|
addButton(ModalButton.cancel());
|
||||||
|
addButton(ModalButton.ok(action));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModalOverlay withDefaultButtons() {
|
||||||
|
return withDefaultButtons(() -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
String titleKey;
|
||||||
|
Comp<?> content;
|
||||||
|
LabelGraphic graphic;
|
||||||
|
|
||||||
|
@Singular
|
||||||
|
List<Object> buttons;
|
||||||
|
|
||||||
|
@NonFinal
|
||||||
|
boolean persistent;
|
||||||
|
|
||||||
|
@NonFinal
|
||||||
|
@Setter
|
||||||
|
Runnable onClose;
|
||||||
|
|
||||||
|
public ModalButton addButton(ModalButton button) {
|
||||||
|
buttons.add(button);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addButtonBarComp(Comp<?> comp) {
|
||||||
|
buttons.add(comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void persist() {
|
||||||
|
persistent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
AppDialog.show(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showAndWait() {
|
||||||
|
AppDialog.showAndWait(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
AppDialog.closeDialog(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,18 @@ import io.xpipe.app.comp.Comp;
|
|||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.AppLogs;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import javafx.animation.*;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ButtonBar;
|
import javafx.scene.control.ButtonBar;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -17,18 +24,19 @@ import javafx.scene.input.KeyEvent;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import atlantafx.base.controls.ModalPane;
|
import atlantafx.base.controls.ModalPane;
|
||||||
import atlantafx.base.layout.ModalBox;
|
import atlantafx.base.layout.ModalBox;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import lombok.Value;
|
import atlantafx.base.util.Animations;
|
||||||
|
|
||||||
public class ModalOverlayComp extends SimpleComp {
|
public class ModalOverlayComp extends SimpleComp {
|
||||||
|
|
||||||
private final Comp<?> background;
|
private final Comp<?> background;
|
||||||
private final Property<OverlayContent> overlayContent;
|
private final Property<ModalOverlay> overlayContent;
|
||||||
|
|
||||||
public ModalOverlayComp(Comp<?> background, Property<OverlayContent> overlayContent) {
|
public ModalOverlayComp(Comp<?> background, Property<ModalOverlay> overlayContent) {
|
||||||
this.background = background;
|
this.background = background;
|
||||||
this.overlayContent = overlayContent;
|
this.overlayContent = overlayContent;
|
||||||
}
|
}
|
||||||
@@ -37,7 +45,9 @@ public class ModalOverlayComp extends SimpleComp {
|
|||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var bgRegion = background.createRegion();
|
var bgRegion = background.createRegion();
|
||||||
var modal = new ModalPane();
|
var modal = new ModalPane();
|
||||||
AppFont.small(modal);
|
modal.setInTransitionFactory(OsType.getLocal() == OsType.LINUX ? null : node -> fadeInDelyed(node));
|
||||||
|
modal.setOutTransitionFactory(
|
||||||
|
OsType.getLocal() == OsType.LINUX ? null : node -> Animations.fadeOut(node, Duration.millis(200)));
|
||||||
modal.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
modal.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
var c = modal.getContent();
|
var c = modal.getContent();
|
||||||
if (newValue && c != null) {
|
if (newValue && c != null) {
|
||||||
@@ -46,6 +56,7 @@ public class ModalOverlayComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
modal.getStyleClass().add("modal-overlay-comp");
|
modal.getStyleClass().add("modal-overlay-comp");
|
||||||
var pane = new StackPane(bgRegion, modal);
|
var pane = new StackPane(bgRegion, modal);
|
||||||
|
pane.setAlignment(Pos.CENTER);
|
||||||
pane.setPickOnBounds(false);
|
pane.setPickOnBounds(false);
|
||||||
pane.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
pane.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
@@ -57,88 +68,236 @@ public class ModalOverlayComp extends SimpleComp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
PlatformThread.sync(overlayContent).addListener((observable, oldValue, newValue) -> {
|
modal.contentProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (oldValue != null && newValue == null && modal.isDisplay()) {
|
if (newValue == null) {
|
||||||
modal.hide(true);
|
overlayContent.setValue(null);
|
||||||
return;
|
bgRegion.setDisable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
var l = new Label(
|
bgRegion.setDisable(true);
|
||||||
AppI18n.get(newValue.titleKey),
|
|
||||||
newValue.graphic != null ? newValue.graphic.createRegion() : null);
|
|
||||||
l.setGraphicTextGap(6);
|
|
||||||
AppFont.normal(l);
|
|
||||||
var r = newValue.content.createRegion();
|
|
||||||
var box = new VBox(l, r);
|
|
||||||
box.focusedProperty().addListener((o, old, n) -> {
|
|
||||||
if (n) {
|
|
||||||
r.requestFocus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
box.setSpacing(10);
|
|
||||||
box.setPadding(new Insets(10, 15, 15, 15));
|
|
||||||
|
|
||||||
if (newValue.finishKey != null) {
|
|
||||||
var finishButton = new Button(AppI18n.get(newValue.finishKey));
|
|
||||||
finishButton.getStyleClass().add(Styles.ACCENT);
|
|
||||||
finishButton.setOnAction(event -> {
|
|
||||||
newValue.onFinish.run();
|
|
||||||
overlayContent.setValue(null);
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
|
|
||||||
var buttonBar = new ButtonBar();
|
|
||||||
buttonBar.getButtons().addAll(finishButton);
|
|
||||||
box.getChildren().add(buttonBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
var modalBox = new ModalBox(box);
|
|
||||||
modalBox.setOnClose(event -> {
|
|
||||||
overlayContent.setValue(null);
|
|
||||||
modal.hide(true);
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
modalBox.prefWidthProperty().bind(box.widthProperty());
|
|
||||||
modalBox.prefHeightProperty().bind(box.heightProperty());
|
|
||||||
modalBox.maxWidthProperty().bind(box.widthProperty());
|
|
||||||
modalBox.maxHeightProperty().bind(box.heightProperty());
|
|
||||||
modalBox.focusedProperty().addListener((o, old, n) -> {
|
|
||||||
if (n) {
|
|
||||||
box.requestFocus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
modal.show(modalBox);
|
|
||||||
|
|
||||||
if (newValue.finishOnEnter) {
|
|
||||||
modalBox.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
|
||||||
if (event.getCode() == KeyCode.ENTER) {
|
|
||||||
newValue.onFinish.run();
|
|
||||||
overlayContent.setValue(null);
|
|
||||||
event.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 2 pulses before focus so that the scene can be assigned to r
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
modalBox.requestFocus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modal.displayProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (!newValue) {
|
||||||
|
overlayContent.setValue(null);
|
||||||
|
bgRegion.setDisable(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||||
|
if (event.getCode() == KeyCode.ENTER) {
|
||||||
|
var ov = overlayContent.getValue();
|
||||||
|
if (ov != null) {
|
||||||
|
var def = ov.getButtons().stream()
|
||||||
|
.filter(modalButton -> modalButton instanceof ModalButton mb && mb.isDefaultButton())
|
||||||
|
.findFirst();
|
||||||
|
if (def.isPresent()) {
|
||||||
|
var mb = (ModalButton) def.get();
|
||||||
|
if (mb.getAction() != null) {
|
||||||
|
mb.getAction().run();
|
||||||
|
}
|
||||||
|
if (mb.isClose()) {
|
||||||
|
overlayContent.setValue(null);
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
overlayContent.addListener((observable, oldValue, newValue) -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
if (oldValue != null && modal.isDisplay()) {
|
||||||
|
if (newValue == null) {
|
||||||
|
modal.hide(false);
|
||||||
|
}
|
||||||
|
if (oldValue.getContent() instanceof ModalOverlayContentComp mocc) {
|
||||||
|
mocc.onClose();
|
||||||
|
}
|
||||||
|
var runnable = oldValue.getOnClose();
|
||||||
|
if (runnable != null) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
if (oldValue.getContent() instanceof ModalOverlayContentComp mocc) {
|
||||||
|
mocc.setModalOverlay(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (newValue != null) {
|
||||||
|
if (newValue.getContent() instanceof ModalOverlayContentComp mocc) {
|
||||||
|
mocc.setModalOverlay(newValue);
|
||||||
|
}
|
||||||
|
showModalBox(modal, newValue);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
AppLogs.get().logException(null, t);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
overlayContent.setValue(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var current = overlayContent.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
showModalBox(modal, current);
|
||||||
|
}
|
||||||
|
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
private void showModalBox(ModalPane modal, ModalOverlay overlay) {
|
||||||
public static class OverlayContent {
|
var modalBox = toBox(modal, overlay);
|
||||||
|
modal.setPersistent(overlay.isPersistent());
|
||||||
|
modal.show(modalBox);
|
||||||
|
if (overlay.isPersistent() || overlay.getTitleKey() == null) {
|
||||||
|
var closeButton = modalBox.lookup(".close-button");
|
||||||
|
if (closeButton != null) {
|
||||||
|
closeButton.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String titleKey;
|
private Region toBox(ModalPane pane, ModalOverlay newValue) {
|
||||||
Comp<?> content;
|
Region r = newValue.getContent().createRegion();
|
||||||
Comp<?> graphic;
|
|
||||||
String finishKey;
|
var content = new VBox(r);
|
||||||
Runnable onFinish;
|
content.focusedProperty().addListener((o, old, n) -> {
|
||||||
boolean finishOnEnter;
|
if (n) {
|
||||||
|
r.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
content.setSpacing(25);
|
||||||
|
content.setPadding(new Insets(13, 27, 20, 27));
|
||||||
|
|
||||||
|
if (newValue.getTitleKey() != null) {
|
||||||
|
var l = new Label(
|
||||||
|
AppI18n.get(newValue.getTitleKey()),
|
||||||
|
newValue.getGraphic() != null ? newValue.getGraphic().createGraphicNode() : null);
|
||||||
|
l.setGraphicTextGap(8);
|
||||||
|
AppFont.normal(l);
|
||||||
|
content.getChildren().addFirst(l);
|
||||||
|
} else {
|
||||||
|
content.getChildren().addFirst(Comp.vspacer(0).createRegion());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue.getButtons().size() > 0) {
|
||||||
|
var buttonBar = new ButtonBar();
|
||||||
|
for (var o : newValue.getButtons()) {
|
||||||
|
var node = o instanceof ModalButton mb ? toButton(mb) : ((Comp<?>) o).createRegion();
|
||||||
|
buttonBar.getButtons().add(node);
|
||||||
|
ButtonBar.setButtonUniformSize(node, o instanceof ModalButton);
|
||||||
|
if (o instanceof ModalButton) {
|
||||||
|
node.prefHeightProperty().bind(buttonBar.heightProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content.getChildren().add(buttonBar);
|
||||||
|
AppFont.small(buttonBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
var modalBox = new ModalBox(content) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setCloseButtonPosition() {
|
||||||
|
setTopAnchor(closeButton, 10d);
|
||||||
|
setRightAnchor(closeButton, 19d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
modalBox.setOnClose(event -> {
|
||||||
|
overlayContent.setValue(null);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
r.maxHeightProperty().bind(pane.heightProperty().subtract(200));
|
||||||
|
|
||||||
|
content.prefWidthProperty().bind(modalBox.widthProperty());
|
||||||
|
modalBox.setMinWidth(100);
|
||||||
|
modalBox.setMinHeight(100);
|
||||||
|
modalBox.prefWidthProperty().bind(modalBoxWidth(pane, r));
|
||||||
|
modalBox.maxWidthProperty().bind(modalBox.prefWidthProperty());
|
||||||
|
modalBox.prefHeightProperty().bind(modalBoxHeight(pane, content));
|
||||||
|
modalBox.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
modalBox.focusedProperty().addListener((o, old, n) -> {
|
||||||
|
if (n) {
|
||||||
|
content.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newValue.getContent() instanceof ModalOverlayContentComp mocc) {
|
||||||
|
var busy = mocc.busy();
|
||||||
|
if (busy != null) {
|
||||||
|
var loading = LoadingOverlayComp.noProgress(Comp.of(() -> modalBox), busy);
|
||||||
|
return loading.createRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modalBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableDoubleValue modalBoxWidth(ModalPane pane, Region r) {
|
||||||
|
return Bindings.createDoubleBinding(
|
||||||
|
() -> {
|
||||||
|
var max = pane.getWidth() - 50;
|
||||||
|
if (r.getPrefWidth() != Region.USE_COMPUTED_SIZE) {
|
||||||
|
return Math.min(max, r.getPrefWidth() + 50);
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
},
|
||||||
|
pane.widthProperty(),
|
||||||
|
r.prefWidthProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableDoubleValue modalBoxHeight(ModalPane pane, Region content) {
|
||||||
|
return Bindings.createDoubleBinding(
|
||||||
|
() -> {
|
||||||
|
var max = pane.getHeight() - 20;
|
||||||
|
if (content.getPrefHeight() != Region.USE_COMPUTED_SIZE) {
|
||||||
|
return Math.min(max, content.getPrefHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(max, content.getHeight());
|
||||||
|
},
|
||||||
|
pane.heightProperty(),
|
||||||
|
pane.prefHeightProperty(),
|
||||||
|
content.prefHeightProperty(),
|
||||||
|
content.heightProperty(),
|
||||||
|
content.maxHeightProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button toButton(ModalButton mb) {
|
||||||
|
var button = new Button(mb.getKey() != null ? AppI18n.get(mb.getKey()) : null);
|
||||||
|
if (mb.isDefaultButton()) {
|
||||||
|
button.getStyleClass().add(Styles.ACCENT);
|
||||||
|
}
|
||||||
|
if (mb.getAugment() != null) {
|
||||||
|
mb.getAugment().accept(button);
|
||||||
|
}
|
||||||
|
button.setOnAction(event -> {
|
||||||
|
if (mb.getAction() != null) {
|
||||||
|
mb.getAction().run();
|
||||||
|
}
|
||||||
|
if (mb.isClose()) {
|
||||||
|
overlayContent.setValue(null);
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timeline fadeInDelyed(Node node) {
|
||||||
|
var t = new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO, new KeyValue(node.opacityProperty(), 0.01)),
|
||||||
|
new KeyFrame(Duration.millis(50), new KeyValue(node.opacityProperty(), 0.01, Animations.EASE)),
|
||||||
|
new KeyFrame(Duration.millis(150), new KeyValue(node.opacityProperty(), 1, Animations.EASE)));
|
||||||
|
|
||||||
|
t.statusProperty().addListener((obs, old, val) -> {
|
||||||
|
if (val == Animation.Status.STOPPED) {
|
||||||
|
node.setOpacity(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public abstract class ModalOverlayContentComp extends SimpleComp {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
protected ModalOverlay modalOverlay;
|
||||||
|
|
||||||
|
void setModalOverlay(ModalOverlay modalOverlay) {
|
||||||
|
this.modalOverlay = modalOverlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onClose() {}
|
||||||
|
|
||||||
|
protected ObservableValue<Boolean> busy() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class ModalOverlayStackComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final Comp<?> background;
|
||||||
|
private final ObservableList<ModalOverlay> modalOverlay;
|
||||||
|
|
||||||
|
public ModalOverlayStackComp(Comp<?> background, ObservableList<ModalOverlay> modalOverlay) {
|
||||||
|
this.background = background;
|
||||||
|
this.modalOverlay = modalOverlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var current = background;
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
current = buildModalOverlay(current, i);
|
||||||
|
}
|
||||||
|
return current.createRegion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comp<?> buildModalOverlay(Comp<?> current, int index) {
|
||||||
|
AtomicInteger currentIndex = new AtomicInteger(index);
|
||||||
|
var prop = new SimpleObjectProperty<>(modalOverlay.size() > index ? modalOverlay.get(index) : null);
|
||||||
|
modalOverlay.addListener((ListChangeListener<? super ModalOverlay>) c -> {
|
||||||
|
var ex = prop.get();
|
||||||
|
// Don't shift just for an index change
|
||||||
|
if (ex != null && modalOverlay.contains(ex)) {
|
||||||
|
currentIndex.set(modalOverlay.indexOf(ex));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
currentIndex.set(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.set(modalOverlay.size() > index ? modalOverlay.get(index) : null);
|
||||||
|
});
|
||||||
|
prop.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue == null && modalOverlay.indexOf(oldValue) == currentIndex.get()) {
|
||||||
|
modalOverlay.remove(oldValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var comp = new ModalOverlayComp(current, prop);
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,9 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
|||||||
firstComp = compRegion;
|
firstComp = compRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.name() != null && entry.description() != null) {
|
var showVertical = (entry.name() != null
|
||||||
|
&& (entry.description() != null || entry.comp() instanceof SimpleTitledPaneComp));
|
||||||
|
if (showVertical) {
|
||||||
var line = new VBox();
|
var line = new VBox();
|
||||||
line.prefWidthProperty().bind(pane.widthProperty());
|
line.prefWidthProperty().bind(pane.widthProperty());
|
||||||
line.setSpacing(5);
|
line.setSpacing(5);
|
||||||
@@ -70,51 +72,61 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
|||||||
}
|
}
|
||||||
line.getChildren().add(name);
|
line.getChildren().add(name);
|
||||||
|
|
||||||
var description = new Label();
|
if (entry.description() != null) {
|
||||||
description.setWrapText(true);
|
var description = new Label();
|
||||||
description.getStyleClass().add("description");
|
description.setWrapText(true);
|
||||||
description.textProperty().bind(entry.description());
|
description.getStyleClass().add("description");
|
||||||
description.setAlignment(Pos.CENTER_LEFT);
|
description.textProperty().bind(entry.description());
|
||||||
description.setMinHeight(Region.USE_PREF_SIZE);
|
description.setAlignment(Pos.CENTER_LEFT);
|
||||||
if (compRegion != null) {
|
description.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
description.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
|
if (compRegion != null) {
|
||||||
description.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
|
description.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
|
||||||
}
|
description.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.longDescriptionSource() != null) {
|
if (entry.longDescriptionSource() != null) {
|
||||||
var markDown = new MarkdownComp(entry.longDescriptionSource(), s -> s)
|
var markDown = new MarkdownComp(entry.longDescriptionSource(), s -> s, true)
|
||||||
.apply(struc -> struc.get().setMaxWidth(500))
|
.apply(struc -> struc.get().setMaxWidth(500))
|
||||||
.apply(struc -> struc.get().setMaxHeight(400));
|
.apply(struc -> struc.get().setMaxHeight(400));
|
||||||
var popover = new Popover(markDown.createRegion());
|
var popover = new Popover(markDown.createRegion());
|
||||||
popover.setCloseButtonEnabled(false);
|
popover.setCloseButtonEnabled(false);
|
||||||
popover.setHeaderAlwaysVisible(false);
|
popover.setHeaderAlwaysVisible(false);
|
||||||
popover.setDetachable(true);
|
popover.setDetachable(true);
|
||||||
AppFont.small(popover.getContentNode());
|
AppFont.small(popover.getContentNode());
|
||||||
|
|
||||||
var extendedDescription = new Button("... ?");
|
var extendedDescription = new Button("... ?");
|
||||||
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
|
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
extendedDescription.getStyleClass().add(Styles.BUTTON_OUTLINED);
|
extendedDescription.getStyleClass().add(Styles.BUTTON_OUTLINED);
|
||||||
extendedDescription.getStyleClass().add(Styles.ACCENT);
|
extendedDescription.getStyleClass().add(Styles.ACCENT);
|
||||||
extendedDescription.getStyleClass().add("long-description");
|
extendedDescription.getStyleClass().add("long-description");
|
||||||
extendedDescription.setAccessibleText("Help");
|
extendedDescription.setAccessibleText("Help");
|
||||||
AppFont.normal(extendedDescription);
|
AppFont.normal(extendedDescription);
|
||||||
extendedDescription.setOnAction(e -> {
|
extendedDescription.setOnAction(e -> {
|
||||||
popover.show(extendedDescription);
|
popover.show(extendedDescription);
|
||||||
e.consume();
|
e.consume();
|
||||||
});
|
});
|
||||||
|
|
||||||
var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), extendedDescription);
|
var descriptionBox =
|
||||||
descriptionBox.setSpacing(5);
|
new HBox(description, new Spacer(Orientation.HORIZONTAL), extendedDescription);
|
||||||
HBox.setHgrow(descriptionBox, Priority.ALWAYS);
|
descriptionBox.setSpacing(5);
|
||||||
descriptionBox.setAlignment(Pos.CENTER_LEFT);
|
HBox.setHgrow(descriptionBox, Priority.ALWAYS);
|
||||||
line.getChildren().add(descriptionBox);
|
descriptionBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
} else {
|
line.getChildren().add(descriptionBox);
|
||||||
line.getChildren().add(description);
|
|
||||||
|
if (compRegion != null) {
|
||||||
|
descriptionBox.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
|
||||||
|
descriptionBox.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line.getChildren().add(description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compRegion != null) {
|
if (compRegion != null) {
|
||||||
compRegion.accessibleTextProperty().bind(name.textProperty());
|
compRegion.accessibleTextProperty().bind(name.textProperty());
|
||||||
compRegion.accessibleHelpProperty().bind(description.textProperty());
|
if (entry.description() != null) {
|
||||||
|
compRegion.accessibleHelpProperty().bind(PlatformThread.sync(entry.description()));
|
||||||
|
}
|
||||||
line.getChildren().add(compRegion);
|
line.getChildren().add(compRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +163,11 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entries.size() == 1 && firstComp != null) {
|
||||||
|
pane.visibleProperty().bind(PlatformThread.sync(firstComp.visibleProperty()));
|
||||||
|
pane.managedProperty().bind(PlatformThread.sync(firstComp.managedProperty()));
|
||||||
|
}
|
||||||
|
|
||||||
if (entries.stream().anyMatch(entry -> entry.name() != null && entry.description() == null)) {
|
if (entries.stream().anyMatch(entry -> entry.name() != null && entry.description() == null)) {
|
||||||
var nameWidthBinding = Bindings.createDoubleBinding(
|
var nameWidthBinding = Bindings.createDoubleBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.core.App;
|
import io.xpipe.app.core.window.AppMainWindow;
|
||||||
import io.xpipe.app.resources.AppImages;
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.app.util.BindingsHelper;
|
import io.xpipe.app.util.BindingsHelper;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class PrettyImageHelper {
|
public class PrettyImageHelper {
|
||||||
|
|
||||||
@@ -33,7 +35,11 @@ public class PrettyImageHelper {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObservableValue<String> rasterizedImageIfExistsScaled(String img, int height) {
|
private static ObservableValue<String> rasterizedImageIfExistsScaled(
|
||||||
|
String img, int height, int... availableSizes) {
|
||||||
|
ObservableDoubleValue obs = AppMainWindow.getInstance() != null
|
||||||
|
? AppMainWindow.getInstance().displayScale()
|
||||||
|
: new SimpleDoubleProperty(1.0);
|
||||||
return Bindings.createStringBinding(
|
return Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (img == null) {
|
if (img == null) {
|
||||||
@@ -44,11 +50,11 @@ public class PrettyImageHelper {
|
|||||||
return rasterizedImageIfExists(img, height).orElse(null);
|
return rasterizedImageIfExists(img, height).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizes = List.of(16, 24, 40, 80);
|
var mult = Math.round(obs.get() * height);
|
||||||
var mult = Math.round(App.getApp().displayScale().get() * height);
|
|
||||||
var base = FileNames.getBaseName(img);
|
var base = FileNames.getBaseName(img);
|
||||||
var available = sizes.stream()
|
var available = IntStream.of(availableSizes)
|
||||||
.filter(integer -> AppImages.hasNormalImage(base + "-" + integer + ".png"))
|
.filter(integer -> AppImages.hasNormalImage(base + "-" + integer + ".png"))
|
||||||
|
.boxed()
|
||||||
.toList();
|
.toList();
|
||||||
var closest = available.stream()
|
var closest = available.stream()
|
||||||
.filter(integer -> integer >= mult)
|
.filter(integer -> integer >= mult)
|
||||||
@@ -56,7 +62,7 @@ public class PrettyImageHelper {
|
|||||||
.orElse(available.size() > 0 ? available.getLast() : 0);
|
.orElse(available.size() > 0 ? available.getLast() : 0);
|
||||||
return rasterizedImageIfExists(img, closest).orElse(null);
|
return rasterizedImageIfExists(img, closest).orElse(null);
|
||||||
},
|
},
|
||||||
App.getApp().displayScale());
|
obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
||||||
@@ -73,8 +79,13 @@ public class PrettyImageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var binding = BindingsHelper.flatMap(img, s -> {
|
var binding = BindingsHelper.flatMap(img, s -> {
|
||||||
return rasterizedImageIfExistsScaled(s, h);
|
return rasterizedImageIfExistsScaled(s, h, 16, 24, 40, 80);
|
||||||
});
|
});
|
||||||
return new PrettyImageComp(binding, w, h);
|
return new PrettyImageComp(binding, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Comp<?> ofSpecificFixedSize(String img, int w, int h) {
|
||||||
|
var b = rasterizedImageIfExistsScaled(img, h, h, h * 2);
|
||||||
|
return new PrettyImageComp(b, w, h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
var ig = new InputGroup(text);
|
var ig = new InputGroup(text);
|
||||||
|
ig.setFillHeight(true);
|
||||||
ig.getStyleClass().add("secret-field-comp");
|
ig.getStyleClass().add("secret-field-comp");
|
||||||
if (allowCopy) {
|
if (allowCopy) {
|
||||||
ig.getChildren().add(copyButton);
|
ig.getChildren().add(copyButton);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import io.xpipe.app.comp.CompStructure;
|
|||||||
import io.xpipe.app.comp.SimpleCompStructure;
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
import io.xpipe.app.update.UpdateAvailableDialog;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
|
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableDialog.showIfNeeded())
|
||||||
.tooltipKey("updateAvailableTooltip")
|
.tooltipKey("updateAvailableTooltip")
|
||||||
.accessibleTextKey("updateAvailableTooltip");
|
.accessibleTextKey("updateAvailableTooltip");
|
||||||
b.apply(struc -> {
|
b.apply(struc -> {
|
||||||
|
|||||||
@@ -11,19 +11,23 @@ public class SimpleTitledPaneComp extends Comp<CompStructure<TitledPane>> {
|
|||||||
|
|
||||||
private final ObservableValue<String> name;
|
private final ObservableValue<String> name;
|
||||||
private final Comp<?> content;
|
private final Comp<?> content;
|
||||||
|
private final boolean collapsible;
|
||||||
|
|
||||||
public SimpleTitledPaneComp(ObservableValue<String> name, Comp<?> content) {
|
public SimpleTitledPaneComp(ObservableValue<String> name, Comp<?> content, boolean collapsible) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
this.collapsible = collapsible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<TitledPane> createBase() {
|
public CompStructure<TitledPane> createBase() {
|
||||||
var tp = new TitledPane(null, content.createRegion());
|
var r = content.createRegion();
|
||||||
|
r.getStyleClass().add("content");
|
||||||
|
var tp = new TitledPane(null, r);
|
||||||
tp.textProperty().bind(name);
|
tp.textProperty().bind(name);
|
||||||
tp.getStyleClass().add("simple-titled-pane-comp");
|
tp.getStyleClass().add("simple-titled-pane-comp");
|
||||||
tp.setExpanded(true);
|
tp.setExpanded(true);
|
||||||
tp.setCollapsible(false);
|
tp.setCollapsible(collapsible);
|
||||||
return new SimpleCompStructure<>(tp);
|
return new SimpleCompStructure<>(tp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,15 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import atlantafx.base.controls.Spacer;
|
||||||
import lombok.Builder;
|
import lombok.*;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Value;
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
@Getter
|
||||||
public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||||
|
|
||||||
@@ -32,6 +30,12 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
private final ObservableValue<String> icon;
|
private final ObservableValue<String> icon;
|
||||||
private final Consumer<ActionEvent> action;
|
private final Consumer<ActionEvent> action;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private double iconSize = 0.55;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private Comp<?> right;
|
||||||
|
|
||||||
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
|
public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer<ActionEvent> action) {
|
||||||
this.name = AppI18n.observable(nameKey);
|
this.name = AppI18n.observable(nameKey);
|
||||||
this.description = AppI18n.observable(descriptionKey);
|
this.description = AppI18n.observable(descriptionKey);
|
||||||
@@ -39,12 +43,25 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TileButtonComp(
|
||||||
|
ObservableValue<String> name,
|
||||||
|
ObservableValue<String> description,
|
||||||
|
ObservableValue<String> icon,
|
||||||
|
Consumer<ActionEvent> action) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.icon = icon;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Structure createBase() {
|
public Structure createBase() {
|
||||||
var bt = new Button();
|
var bt = new Button();
|
||||||
bt.getStyleClass().add("tile-button-comp");
|
bt.getStyleClass().add("tile-button-comp");
|
||||||
bt.setOnAction(e -> {
|
bt.setOnAction(e -> {
|
||||||
action.accept(e);
|
if (action != null) {
|
||||||
|
action.accept(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var header = new Label();
|
var header = new Label();
|
||||||
@@ -59,6 +76,11 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
var fi = new FontIconComp(icon).createStructure();
|
var fi = new FontIconComp(icon).createStructure();
|
||||||
var pane = fi.getPane();
|
var pane = fi.getPane();
|
||||||
var hbox = new HBox(pane, text);
|
var hbox = new HBox(pane, text);
|
||||||
|
Region rightRegion = right != null ? right.createRegion() : null;
|
||||||
|
if (rightRegion != null) {
|
||||||
|
hbox.getChildren().add(new Spacer());
|
||||||
|
hbox.getChildren().add(rightRegion);
|
||||||
|
}
|
||||||
hbox.setSpacing(8);
|
hbox.setSpacing(8);
|
||||||
pane.prefWidthProperty()
|
pane.prefWidthProperty()
|
||||||
.bind(Bindings.createDoubleBinding(
|
.bind(Bindings.createDoubleBinding(
|
||||||
@@ -72,7 +94,7 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
desc.heightProperty()));
|
desc.heightProperty()));
|
||||||
pane.prefHeightProperty().addListener((c, o, n) -> {
|
pane.prefHeightProperty().addListener((c, o, n) -> {
|
||||||
var size = Math.min(n.intValue(), 100);
|
var size = Math.min(n.intValue(), 100);
|
||||||
fi.getIcon().setIconSize((int) (size * 0.55));
|
fi.getIcon().setIconSize((int) (size * iconSize));
|
||||||
});
|
});
|
||||||
bt.setGraphic(hbox);
|
bt.setGraphic(hbox);
|
||||||
return Structure.builder()
|
return Structure.builder()
|
||||||
@@ -81,6 +103,7 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
.content(hbox)
|
.content(hbox)
|
||||||
.name(header)
|
.name(header)
|
||||||
.description(desc)
|
.description(desc)
|
||||||
|
.right(rightRegion)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +115,7 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
|||||||
FontIcon graphic;
|
FontIcon graphic;
|
||||||
Label name;
|
Label name;
|
||||||
Label description;
|
Label description;
|
||||||
|
Region right;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Button get() {
|
public Button get() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.Comp;
|
||||||
|
import io.xpipe.app.comp.CompStructure;
|
||||||
|
import io.xpipe.app.comp.SimpleCompStructure;
|
||||||
import io.xpipe.app.util.LabelGraphic;
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
@@ -10,7 +12,6 @@ import javafx.css.PseudoClass;
|
|||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
|
|
||||||
import atlantafx.base.controls.ToggleSwitch;
|
import atlantafx.base.controls.ToggleSwitch;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -18,14 +19,14 @@ import lombok.Value;
|
|||||||
|
|
||||||
@Value
|
@Value
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class ToggleSwitchComp extends SimpleComp {
|
public class ToggleSwitchComp extends Comp<CompStructure<ToggleSwitch>> {
|
||||||
|
|
||||||
Property<Boolean> selected;
|
Property<Boolean> selected;
|
||||||
ObservableValue<String> name;
|
ObservableValue<String> name;
|
||||||
ObservableValue<LabelGraphic> graphic;
|
ObservableValue<LabelGraphic> graphic;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
public CompStructure<ToggleSwitch> createBase() {
|
||||||
var s = new ToggleSwitch();
|
var s = new ToggleSwitch();
|
||||||
s.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
s.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||||
if (event.getCode() == KeyCode.SPACE || event.getCode() == KeyCode.ENTER) {
|
if (event.getCode() == KeyCode.SPACE || event.getCode() == KeyCode.ENTER) {
|
||||||
@@ -52,6 +53,6 @@ public class ToggleSwitchComp extends SimpleComp {
|
|||||||
.bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
|
.bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
|
||||||
s.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-graphic"), true);
|
s.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-graphic"), true);
|
||||||
}
|
}
|
||||||
return s;
|
return new SimpleCompStructure<>(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package io.xpipe.app.comp.store;
|
|||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.augment.GrowAugment;
|
import io.xpipe.app.comp.augment.GrowAugment;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -33,18 +30,9 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||||||
: Comp.empty();
|
: Comp.empty();
|
||||||
information.setGraphic(state.createRegion());
|
information.setGraphic(state.createRegion());
|
||||||
|
|
||||||
ObservableValue<String> info = new SimpleStringProperty();
|
var summary = getWrapper().getShownSummary();
|
||||||
if (getWrapper().getEntry().getProvider() != null) {
|
|
||||||
try {
|
|
||||||
info = getWrapper().getEntry().getProvider().informationString(section);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObservableValue<String> finalInfo = info;
|
|
||||||
|
|
||||||
var summary = getWrapper().getSummary();
|
|
||||||
if (getWrapper().getEntry().getProvider() != null) {
|
if (getWrapper().getEntry().getProvider() != null) {
|
||||||
|
var info = getWrapper().getShownInformation();
|
||||||
information
|
information
|
||||||
.textProperty()
|
.textProperty()
|
||||||
.bind(PlatformThread.sync(Bindings.createStringBinding(
|
.bind(PlatformThread.sync(Bindings.createStringBinding(
|
||||||
@@ -53,10 +41,10 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||||||
var p = getWrapper().getEntry().getProvider();
|
var p = getWrapper().getEntry().getProvider();
|
||||||
if (val != null && grid.isHover() && p.alwaysShowSummary()) {
|
if (val != null && grid.isHover() && p.alwaysShowSummary()) {
|
||||||
return val;
|
return val;
|
||||||
} else if (finalInfo.getValue() == null && p.alwaysShowSummary()) {
|
} else if (info.getValue() == null && p.alwaysShowSummary()) {
|
||||||
return val;
|
return val;
|
||||||
} else {
|
} else {
|
||||||
return finalInfo.getValue();
|
return info.getValue();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid.hoverProperty(),
|
grid.hoverProperty(),
|
||||||
@@ -84,6 +72,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||||||
},
|
},
|
||||||
grid.widthProperty()));
|
grid.widthProperty()));
|
||||||
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
||||||
|
var userIcon = createUserIcon().createRegion();
|
||||||
|
|
||||||
if (showIcon) {
|
if (showIcon) {
|
||||||
var storeIcon = createIcon(28, 24);
|
var storeIcon = createIcon(28, 24);
|
||||||
@@ -106,7 +95,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||||||
grid.getColumnConstraints().addAll(nameCC);
|
grid.getColumnConstraints().addAll(nameCC);
|
||||||
|
|
||||||
var active = new StoreActiveComp(getWrapper()).createRegion();
|
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||||
var nameBox = new HBox(name, notes);
|
var nameBox = new HBox(name, userIcon, notes);
|
||||||
getWrapper().getSessionActive().subscribe(aBoolean -> {
|
getWrapper().getSessionActive().subscribe(aBoolean -> {
|
||||||
if (!aBoolean) {
|
if (!aBoolean) {
|
||||||
nameBox.getChildren().remove(active);
|
nameBox.getChildren().remove(active);
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package io.xpipe.app.comp.store;
|
|||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.util.PlatformThread;
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
@@ -25,7 +23,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||||||
|
|
||||||
private Label createSummary() {
|
private Label createSummary() {
|
||||||
var summary = new Label();
|
var summary = new Label();
|
||||||
summary.textProperty().bind(getWrapper().getSummary());
|
summary.textProperty().bind(getWrapper().getShownSummary());
|
||||||
summary.getStyleClass().add("summary");
|
summary.getStyleClass().add("summary");
|
||||||
AppFont.small(summary);
|
AppFont.small(summary);
|
||||||
return summary;
|
return summary;
|
||||||
@@ -34,6 +32,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||||||
protected Region createContent() {
|
protected Region createContent() {
|
||||||
var name = createName().createRegion();
|
var name = createName().createRegion();
|
||||||
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
||||||
|
var userIcon = createUserIcon().createRegion();
|
||||||
|
|
||||||
var grid = new GridPane();
|
var grid = new GridPane();
|
||||||
grid.setHgap(6);
|
grid.setHgap(6);
|
||||||
@@ -44,7 +43,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||||||
grid.getColumnConstraints().add(new ColumnConstraints(56));
|
grid.getColumnConstraints().add(new ColumnConstraints(56));
|
||||||
|
|
||||||
var active = new StoreActiveComp(getWrapper()).createRegion();
|
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||||
var nameBox = new HBox(name, notes);
|
var nameBox = new HBox(name, userIcon, notes);
|
||||||
nameBox.setSpacing(6);
|
nameBox.setSpacing(6);
|
||||||
nameBox.setAlignment(Pos.CENTER_LEFT);
|
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
grid.add(nameBox, 1, 0);
|
grid.add(nameBox, 1, 0);
|
||||||
@@ -98,16 +97,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||||||
private Label createInformation() {
|
private Label createInformation() {
|
||||||
var information = new Label();
|
var information = new Label();
|
||||||
information.setGraphicTextGap(7);
|
information.setGraphicTextGap(7);
|
||||||
if (getWrapper().getEntry().getProvider() != null) {
|
information.textProperty().bind(getWrapper().getShownInformation());
|
||||||
try {
|
|
||||||
information
|
|
||||||
.textProperty()
|
|
||||||
.bind(PlatformThread.sync(
|
|
||||||
getWrapper().getEntry().getProvider().informationString(section)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
information.getStyleClass().add("information");
|
information.getStyleClass().add("information");
|
||||||
|
|
||||||
var state = getWrapper().getEntry().getProvider() != null
|
var state = getWrapper().getEntry().getProvider() != null
|
||||||
|
|||||||
@@ -6,15 +6,18 @@ import io.xpipe.app.comp.augment.ContextMenuAugment;
|
|||||||
import io.xpipe.app.comp.base.*;
|
import io.xpipe.app.comp.base.*;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
|
import io.xpipe.app.util.ClipboardHelper;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
import io.xpipe.app.util.DerivedObservableList;
|
import io.xpipe.app.util.DerivedObservableList;
|
||||||
import io.xpipe.app.util.LabelGraphic;
|
import io.xpipe.app.util.LabelGraphic;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -47,19 +50,27 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var name = new LazyTextFieldComp(category.nameProperty())
|
var prop = new SimpleStringProperty(category.getName().getValue());
|
||||||
.styleClass("name")
|
AppPrefs.get().censorMode().subscribe(aBoolean -> {
|
||||||
.createRegion();
|
var n = category.getName().getValue();
|
||||||
|
prop.setValue(aBoolean ? "*".repeat(n.length()) : n);
|
||||||
|
});
|
||||||
|
prop.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (!AppPrefs.get().censorMode().get()) {
|
||||||
|
category.getName().setValue(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var name = new LazyTextFieldComp(prop).styleClass("name").createRegion();
|
||||||
var showing = new SimpleBooleanProperty();
|
var showing = new SimpleBooleanProperty();
|
||||||
|
|
||||||
var expandIcon = Bindings.createObjectBinding(
|
var expandIcon = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var exp = category.getExpanded().get()
|
var exp = category.getExpanded().get()
|
||||||
&& category.getChildren().size() > 0;
|
&& category.getChildren().getList().size() > 0;
|
||||||
return new LabelGraphic.IconGraphic(exp ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right");
|
return new LabelGraphic.IconGraphic(exp ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right");
|
||||||
},
|
},
|
||||||
category.getExpanded(),
|
category.getExpanded(),
|
||||||
category.getChildren());
|
category.getChildren().getList());
|
||||||
var expandButton = new IconButtonComp(expandIcon, () -> {
|
var expandButton = new IconButtonComp(expandIcon, () -> {
|
||||||
category.toggleExpanded();
|
category.toggleExpanded();
|
||||||
})
|
})
|
||||||
@@ -69,7 +80,7 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
struc.get().setPadding(new Insets(-2, 0, 0, 0));
|
struc.get().setPadding(new Insets(-2, 0, 0, 0));
|
||||||
struc.get().setFocusTraversable(false);
|
struc.get().setFocusTraversable(false);
|
||||||
})
|
})
|
||||||
.disable(Bindings.isEmpty(category.getChildren()))
|
.disable(Bindings.isEmpty(category.getChildren().getList()))
|
||||||
.styleClass("expand-button")
|
.styleClass("expand-button")
|
||||||
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
|
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
|
||||||
|
|
||||||
@@ -81,7 +92,10 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!DataStorage.get().supportsSharing()
|
if (!DataStorage.get().supportsSharing()
|
||||||
|| !category.getCategory().canShare()) {
|
|| (!category.getCategory().canShare()
|
||||||
|
&& !category.getCategory()
|
||||||
|
.getUuid()
|
||||||
|
.equals(DataStorage.LOCAL_IDENTITIES_CATEGORY_UUID))) {
|
||||||
return new LabelGraphic.IconGraphic("mdi2g-git");
|
return new LabelGraphic.IconGraphic("mdi2g-git");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +107,7 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
.apply(struc -> AppFont.small(struc.get()))
|
.apply(struc -> AppFont.small(struc.get()))
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get().setAlignment(Pos.CENTER);
|
struc.get().setAlignment(Pos.CENTER);
|
||||||
struc.get().setPadding(new Insets(0, 0, 7, 0));
|
struc.get().setPadding(new Insets(0, 0, 0, 0));
|
||||||
struc.get().setFocusTraversable(false);
|
struc.get().setFocusTraversable(false);
|
||||||
hover.bind(struc.get().hoverProperty());
|
hover.bind(struc.get().hoverProperty());
|
||||||
})
|
})
|
||||||
@@ -105,7 +119,8 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
}))
|
}))
|
||||||
.styleClass("status-button");
|
.styleClass("status-button");
|
||||||
|
|
||||||
var shownList = new DerivedObservableList<>(category.getAllContainedEntries(), true)
|
var shownList = new DerivedObservableList<>(
|
||||||
|
category.getAllContainedEntries().getList(), true)
|
||||||
.filtered(
|
.filtered(
|
||||||
storeEntryWrapper -> {
|
storeEntryWrapper -> {
|
||||||
return storeEntryWrapper.matchesFilter(
|
return storeEntryWrapper.matchesFilter(
|
||||||
@@ -113,7 +128,8 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
},
|
},
|
||||||
StoreViewState.get().getFilterString())
|
StoreViewState.get().getFilterString())
|
||||||
.getList();
|
.getList();
|
||||||
var count = new CountComp<>(shownList, category.getAllContainedEntries(), string -> "(" + string + ")");
|
var count =
|
||||||
|
new CountComp<>(shownList, category.getAllContainedEntries().getList(), string -> "(" + string + ")");
|
||||||
count.visible(Bindings.isNotEmpty(shownList));
|
count.visible(Bindings.isNotEmpty(shownList));
|
||||||
|
|
||||||
var showStatus = hover.or(new SimpleBooleanProperty(DataStorage.get().supportsSharing()))
|
var showStatus = hover.or(new SimpleBooleanProperty(DataStorage.get().supportsSharing()))
|
||||||
@@ -134,7 +150,7 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
.styleClass("category-button")
|
.styleClass("category-button")
|
||||||
.apply(struc -> hover.bind(struc.get().hoverProperty()))
|
.apply(struc -> hover.bind(struc.get().hoverProperty()))
|
||||||
.apply(struc -> focus.bind(struc.get().focusedProperty()))
|
.apply(struc -> focus.bind(struc.get().focusedProperty()))
|
||||||
.accessibleText(category.nameProperty())
|
.accessibleText(prop)
|
||||||
.grow(true, false);
|
.grow(true, false);
|
||||||
categoryButton.apply(new ContextMenuAugment<>(
|
categoryButton.apply(new ContextMenuAugment<>(
|
||||||
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
|
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
|
||||||
@@ -150,6 +166,7 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var l = category.getChildren()
|
var l = category.getChildren()
|
||||||
|
.getList()
|
||||||
.sorted(Comparator.comparing(storeCategoryWrapper ->
|
.sorted(Comparator.comparing(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.nameProperty().getValue().toLowerCase(Locale.ROOT)));
|
storeCategoryWrapper.nameProperty().getValue().toLowerCase(Locale.ROOT)));
|
||||||
var children =
|
var children =
|
||||||
@@ -159,9 +176,9 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
var hide = Bindings.createBooleanBinding(
|
var hide = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return !category.getExpanded().get()
|
return !category.getExpanded().get()
|
||||||
|| category.getChildren().isEmpty();
|
|| category.getChildren().getList().isEmpty();
|
||||||
},
|
},
|
||||||
category.getChildren(),
|
category.getChildren().getList(),
|
||||||
category.getExpanded());
|
category.getExpanded());
|
||||||
var v = new VerticalComp(List.of(categoryButton, children.hide(hide)));
|
var v = new VerticalComp(List.of(categoryButton, children.hide(hide)));
|
||||||
v.styleClass("category");
|
v.styleClass("category");
|
||||||
@@ -182,6 +199,13 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
var contextMenu = ContextMenuHelper.create();
|
var contextMenu = ContextMenuHelper.create();
|
||||||
AppFont.normal(contextMenu.getStyleableNode());
|
AppFont.normal(contextMenu.getStyleableNode());
|
||||||
|
|
||||||
|
if (AppPrefs.get().enableHttpApi().get()) {
|
||||||
|
var copyId = new MenuItem(AppI18n.get("copyId"), new FontIcon("mdi2c-content-copy"));
|
||||||
|
copyId.setOnAction(event ->
|
||||||
|
ClipboardHelper.copyText(category.getCategory().getUuid().toString()));
|
||||||
|
contextMenu.getItems().add(copyId);
|
||||||
|
}
|
||||||
|
|
||||||
var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick"));
|
var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick"));
|
||||||
newCategory.setOnAction(event -> {
|
newCategory.setOnAction(event -> {
|
||||||
DataStorage.get()
|
DataStorage.get()
|
||||||
@@ -252,6 +276,7 @@ public class StoreCategoryComp extends SimpleComp {
|
|||||||
del.setOnAction(event -> {
|
del.setOnAction(event -> {
|
||||||
category.delete();
|
category.delete();
|
||||||
});
|
});
|
||||||
|
del.setDisable(!DataStorage.get().canDeleteStoreCategory(category.getCategory()));
|
||||||
contextMenu.getItems().add(del);
|
contextMenu.getItems().add(del);
|
||||||
|
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ import io.xpipe.app.prefs.AppPrefs;
|
|||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
|
import io.xpipe.app.util.DerivedObservableList;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
import javafx.beans.value.ObservableStringValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -27,11 +30,12 @@ public class StoreCategoryWrapper {
|
|||||||
private final Property<Instant> lastAccess;
|
private final Property<Instant> lastAccess;
|
||||||
private final Property<StoreSortMode> sortMode;
|
private final Property<StoreSortMode> sortMode;
|
||||||
private final Property<Boolean> sync;
|
private final Property<Boolean> sync;
|
||||||
private final ObservableList<StoreCategoryWrapper> children;
|
private final DerivedObservableList<StoreCategoryWrapper> children;
|
||||||
private final ObservableList<StoreEntryWrapper> directContainedEntries;
|
private final DerivedObservableList<StoreEntryWrapper> directContainedEntries;
|
||||||
private final ObservableList<StoreEntryWrapper> allContainedEntries;
|
private final DerivedObservableList<StoreEntryWrapper> allContainedEntries;
|
||||||
private final BooleanProperty expanded = new SimpleBooleanProperty();
|
private final BooleanProperty expanded = new SimpleBooleanProperty();
|
||||||
private final Property<DataColor> color = new SimpleObjectProperty<>();
|
private final Property<DataColor> color = new SimpleObjectProperty<>();
|
||||||
|
private StoreCategoryWrapper cachedParent;
|
||||||
|
|
||||||
public StoreCategoryWrapper(DataStoreCategory category) {
|
public StoreCategoryWrapper(DataStoreCategory category) {
|
||||||
var d = 0;
|
var d = 0;
|
||||||
@@ -52,27 +56,54 @@ public class StoreCategoryWrapper {
|
|||||||
this.lastAccess = new SimpleObjectProperty<>(category.getLastAccess());
|
this.lastAccess = new SimpleObjectProperty<>(category.getLastAccess());
|
||||||
this.sortMode = new SimpleObjectProperty<>(category.getSortMode());
|
this.sortMode = new SimpleObjectProperty<>(category.getSortMode());
|
||||||
this.sync = new SimpleObjectProperty<>(category.isSync());
|
this.sync = new SimpleObjectProperty<>(category.isSync());
|
||||||
this.children = FXCollections.observableArrayList();
|
this.children = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||||
this.allContainedEntries = FXCollections.observableArrayList();
|
this.allContainedEntries = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||||
this.directContainedEntries = FXCollections.observableArrayList();
|
this.directContainedEntries = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||||
this.color.setValue(category.getColor());
|
this.color.setValue(category.getColor());
|
||||||
setupListeners();
|
setupListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableStringValue getShownName() {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var n = nameProperty().getValue();
|
||||||
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
|
},
|
||||||
|
AppPrefs.get().censorMode(),
|
||||||
|
nameProperty());
|
||||||
|
}
|
||||||
|
|
||||||
public StoreCategoryWrapper getRoot() {
|
public StoreCategoryWrapper getRoot() {
|
||||||
return StoreViewState.get().getCategoryWrapper(root);
|
return StoreViewState.get().getCategoryWrapper(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoreCategoryWrapper getParent() {
|
public StoreCategoryWrapper getParent() {
|
||||||
return StoreViewState.get().getCategories().getList().stream()
|
if (category.getParentCategory() == null) {
|
||||||
.filter(storeCategoryWrapper ->
|
return null;
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
}
|
||||||
.findAny()
|
|
||||||
.orElse(null);
|
if (cachedParent == null) {
|
||||||
|
cachedParent = StoreViewState.get().getCategories().getList().stream()
|
||||||
|
.filter(storeCategoryWrapper ->
|
||||||
|
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(StoreEntryWrapper entry) {
|
public boolean contains(StoreEntryWrapper entry) {
|
||||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid()) || allContainedEntries.contains(entry);
|
if (entry.getCategory().getValue() == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var c : children.getList()) {
|
||||||
|
if (c.contains(entry)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void select() {
|
public void select() {
|
||||||
@@ -82,6 +113,9 @@ public class StoreCategoryWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
|
for (var c : children.getList()) {
|
||||||
|
c.delete();
|
||||||
|
}
|
||||||
DataStorage.get().deleteStoreCategory(category);
|
DataStorage.get().deleteStoreCategory(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,22 +171,24 @@ public class StoreCategoryWrapper {
|
|||||||
expanded.setValue(category.isExpanded());
|
expanded.setValue(category.isExpanded());
|
||||||
color.setValue(category.getColor());
|
color.setValue(category.getColor());
|
||||||
|
|
||||||
directContainedEntries.setAll(StoreViewState.get().getAllEntries().getList().stream()
|
var allEntries = new ArrayList<>(StoreViewState.get().getAllEntries().getList());
|
||||||
|
directContainedEntries.setContent(allEntries.stream()
|
||||||
.filter(entry -> {
|
.filter(entry -> {
|
||||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid());
|
return entry.getEntry().getCategoryUuid().equals(category.getUuid());
|
||||||
})
|
})
|
||||||
.toList());
|
.toList());
|
||||||
allContainedEntries.setAll(StoreViewState.get().getAllEntries().getList().stream()
|
allContainedEntries.setContent(allEntries.stream()
|
||||||
.filter(entry -> {
|
.filter(entry -> {
|
||||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
||||||
|| (AppPrefs.get()
|
|| (AppPrefs.get()
|
||||||
.showChildCategoriesInParentCategory()
|
.showChildCategoriesInParentCategory()
|
||||||
.get()
|
.get()
|
||||||
&& children.stream()
|
&& children.getList().stream()
|
||||||
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
||||||
})
|
})
|
||||||
.toList());
|
.toList());
|
||||||
children.setAll(StoreViewState.get().getCategories().getList().stream()
|
|
||||||
|
children.setContent(StoreViewState.get().getCategories().getList().stream()
|
||||||
.filter(storeCategoryWrapper -> getCategory()
|
.filter(storeCategoryWrapper -> getCategory()
|
||||||
.getUuid()
|
.getUuid()
|
||||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||||
@@ -169,8 +205,17 @@ public class StoreCategoryWrapper {
|
|||||||
if (original.equals("All scripts")) {
|
if (original.equals("All scripts")) {
|
||||||
return AppI18n.get("allScripts");
|
return AppI18n.get("allScripts");
|
||||||
}
|
}
|
||||||
if (original.equals("Predefined")) {
|
if (original.equals("All identities")) {
|
||||||
return AppI18n.get("predefined");
|
return AppI18n.get("allIdentities");
|
||||||
|
}
|
||||||
|
if (original.equals("Local")) {
|
||||||
|
return AppI18n.get("local");
|
||||||
|
}
|
||||||
|
if (original.equals("Synced")) {
|
||||||
|
return AppI18n.get("synced");
|
||||||
|
}
|
||||||
|
if (original.equals("Predefined") || original.equals("Samples")) {
|
||||||
|
return AppI18n.get("samples");
|
||||||
}
|
}
|
||||||
if (original.equals("Custom")) {
|
if (original.equals("Custom")) {
|
||||||
return AppI18n.get("custom");
|
return AppI18n.get("custom");
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||||||
},
|
},
|
||||||
sec -> {
|
sec -> {
|
||||||
if (applicable.test(sec.getWrapper())) {
|
if (applicable.test(sec.getWrapper())) {
|
||||||
selected.setValue(sec.getWrapper().getEntry().ref());
|
this.selected.setValue(sec.getWrapper().getEntry().ref());
|
||||||
popover.hide();
|
popover.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -193,9 +193,8 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||||||
var button = new ButtonComp(
|
var button = new ButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return selected.getValue() != null
|
var val = selected.getValue();
|
||||||
? toName(selected.getValue().getEntry())
|
return val != null ? toName(val.get()) : null;
|
||||||
: null;
|
|
||||||
},
|
},
|
||||||
selected),
|
selected),
|
||||||
() -> {});
|
() -> {});
|
||||||
@@ -205,7 +204,12 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||||||
Comp<?> graphic = PrettyImageHelper.ofFixedSize(
|
Comp<?> graphic = PrettyImageHelper.ofFixedSize(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return selected.getValue().get().getEffectiveIconFile();
|
var val = selected.getValue();
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return val.get().getEffectiveIconFile();
|
||||||
},
|
},
|
||||||
selected),
|
selected),
|
||||||
16,
|
16,
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ package io.xpipe.app.comp.store;
|
|||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
import io.xpipe.app.comp.augment.GrowAugment;
|
import io.xpipe.app.comp.augment.GrowAugment;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.*;
|
||||||
import io.xpipe.app.comp.base.DialogComp;
|
|
||||||
import io.xpipe.app.comp.base.ErrorOverlayComp;
|
|
||||||
import io.xpipe.app.comp.base.PopupMenuButtonComp;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||||
import io.xpipe.app.ext.DataStoreProvider;
|
import io.xpipe.app.ext.DataStoreProvider;
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.ExceptionConverter;
|
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
@@ -31,17 +27,20 @@ import javafx.geometry.Orientation;
|
|||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
import atlantafx.base.controls.Spacer;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
import net.synedra.validatorfx.GraphicDecorationStackPane;
|
import net.synedra.validatorfx.GraphicDecorationStackPane;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
@@ -54,7 +53,7 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
Predicate<DataStoreProvider> filter;
|
Predicate<DataStoreProvider> filter;
|
||||||
BooleanProperty busy = new SimpleBooleanProperty();
|
BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
Property<Validator> validator = new SimpleObjectProperty<>(new SimpleValidator());
|
Property<Validator> validator = new SimpleObjectProperty<>(new SimpleValidator());
|
||||||
Property<String> messageProp = new SimpleStringProperty();
|
Property<ModalOverlay> messageProp = new SimpleObjectProperty<>();
|
||||||
BooleanProperty finished = new SimpleBooleanProperty();
|
BooleanProperty finished = new SimpleBooleanProperty();
|
||||||
ObservableValue<DataStoreEntry> entry;
|
ObservableValue<DataStoreEntry> entry;
|
||||||
BooleanProperty changedSinceError = new SimpleBooleanProperty();
|
BooleanProperty changedSinceError = new SimpleBooleanProperty();
|
||||||
@@ -132,15 +131,16 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
.getRootCategory(DataStorage.get()
|
.getRootCategory(DataStorage.get()
|
||||||
.getStoreCategoryIfPresent(targetCategory)
|
.getStoreCategoryIfPresent(targetCategory)
|
||||||
.orElseThrow());
|
.orElseThrow());
|
||||||
// Don't put connections in the scripts category ever
|
|
||||||
|
// Don't put it in the wrong root category
|
||||||
if ((provider.getValue().getCreationCategory() == null
|
if ((provider.getValue().getCreationCategory() == null
|
||||||
|| !provider.getValue()
|
|| !provider.getValue()
|
||||||
.getCreationCategory()
|
.getCreationCategory()
|
||||||
.equals(DataStoreCreationCategory.SCRIPT))
|
.getCategory()
|
||||||
&& rootCategory.equals(DataStorage.get().getAllScriptsCategory())) {
|
.equals(rootCategory.getUuid()))) {
|
||||||
targetCategory = DataStorage.get()
|
targetCategory = provider.getValue().getCreationCategory() != null
|
||||||
.getDefaultConnectionsCategory()
|
? provider.getValue().getCreationCategory().getCategory()
|
||||||
.getUuid();
|
: DataStorage.ALL_CONNECTIONS_CATEGORY_UUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't use the all connections category
|
// Don't use the all connections category
|
||||||
@@ -151,6 +151,21 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
.getUuid();
|
.getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use the all scripts category
|
||||||
|
if (targetCategory.equals(
|
||||||
|
DataStorage.get().getAllScriptsCategory().getUuid())) {
|
||||||
|
targetCategory = DataStorage.CUSTOM_SCRIPTS_CATEGORY_UUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use the all identities category
|
||||||
|
if (targetCategory.equals(
|
||||||
|
DataStorage.get().getAllIdentitiesCategory().getUuid())) {
|
||||||
|
targetCategory = DataStorage.LOCAL_IDENTITIES_CATEGORY_UUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom category stuff
|
||||||
|
targetCategory = provider.getValue().getTargetCategory(store.getValue(), targetCategory);
|
||||||
|
|
||||||
return DataStoreEntry.createNew(
|
return DataStoreEntry.createNew(
|
||||||
UUID.randomUUID(), targetCategory, name.getValue(), store.getValue());
|
UUID.randomUUID(), targetCategory, name.getValue(), store.getValue());
|
||||||
},
|
},
|
||||||
@@ -194,10 +209,14 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void showCreation(DataStoreProvider selected, DataStoreCreationCategory category) {
|
public static void showCreation(DataStoreProvider selected, DataStoreCreationCategory category) {
|
||||||
showCreation(selected != null ? selected.defaultStore() : null, category);
|
showCreation(selected != null ? selected.defaultStore() : null, category, dataStoreEntry -> {}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showCreation(DataStore base, DataStoreCreationCategory category) {
|
public static void showCreation(
|
||||||
|
DataStore base,
|
||||||
|
DataStoreCreationCategory category,
|
||||||
|
Consumer<DataStoreEntry> listener,
|
||||||
|
boolean selectCategory) {
|
||||||
var prov = base != null ? DataStoreProviders.byStore(base) : null;
|
var prov = base != null ? DataStoreProviders.byStore(base) : null;
|
||||||
show(
|
show(
|
||||||
null,
|
null,
|
||||||
@@ -207,13 +226,26 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
|| dataStoreProvider.equals(prov),
|
|| dataStoreProvider.equals(prov),
|
||||||
(e, validated) -> {
|
(e, validated) -> {
|
||||||
try {
|
try {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
var returned = DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||||
|
listener.accept(returned);
|
||||||
if (validated
|
if (validated
|
||||||
&& e.getProvider().shouldShowScan()
|
&& e.getProvider().shouldShowScan()
|
||||||
&& AppPrefs.get()
|
&& AppPrefs.get()
|
||||||
.openConnectionSearchWindowOnConnectionCreation()
|
.openConnectionSearchWindowOnConnectionCreation()
|
||||||
.get()) {
|
.get()) {
|
||||||
ScanAlert.showAsync(e);
|
ScanDialog.showAsync(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectCategory) {
|
||||||
|
// Select new category if needed
|
||||||
|
var cat = DataStorage.get()
|
||||||
|
.getStoreCategoryIfPresent(e.getCategoryUuid())
|
||||||
|
.orElseThrow();
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.setValue(StoreViewState.get().getCategoryWrapper(cat));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
@@ -262,7 +294,7 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
@Override
|
@Override
|
||||||
protected List<Comp<?>> customButtons() {
|
protected List<Comp<?>> customButtons() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new ButtonComp(AppI18n.observable("skipValidation"), null, () -> {
|
new ButtonComp(AppI18n.observable("skipValidation"), () -> {
|
||||||
if (showInvalidConfirmAlert()) {
|
if (showInvalidConfirmAlert()) {
|
||||||
commit(false);
|
commit(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -270,7 +302,7 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.visible(skippable),
|
.visible(skippable),
|
||||||
new ButtonComp(AppI18n.observable("connect"), null, () -> {
|
new ButtonComp(AppI18n.observable("connect"), () -> {
|
||||||
var temp = DataStoreEntry.createTempWrapper(store.getValue());
|
var temp = DataStoreEntry.createTempWrapper(store.getValue());
|
||||||
var action = provider.getValue().launchAction(temp);
|
var action = provider.getValue().launchAction(temp);
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
@@ -319,12 +351,7 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
.getFirst()
|
.getFirst()
|
||||||
.getText();
|
.getText();
|
||||||
TrackEvent.info(msg);
|
TrackEvent.info(msg);
|
||||||
var newMessage = msg;
|
messageProp.setValue(createErrorOverlay(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);
|
changedSinceError.setValue(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -340,19 +367,19 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
entry.getValue().validateOrThrow();
|
entry.getValue().validateOrThrow();
|
||||||
commit(true);
|
commit(true);
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
|
String message;
|
||||||
if (ex instanceof ValidationException) {
|
if (ex instanceof ValidationException) {
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
|
message = ex.getMessage();
|
||||||
} else if (ex instanceof StackOverflowError) {
|
} else if (ex instanceof StackOverflowError) {
|
||||||
// Cycles in connection graphs can fail hard but are expected
|
// Cycles in connection graphs can fail hard but are expected
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
|
message = "StackOverflowError";
|
||||||
|
} else {
|
||||||
|
message = ex.getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMessage = ExceptionConverter.convertMessage(ex);
|
messageProp.setValue(createErrorOverlay(message));
|
||||||
// Temporary fix for equal error message not showing up again
|
|
||||||
if (Objects.equals(newMessage, messageProp.getValue())) {
|
|
||||||
newMessage = newMessage + " ";
|
|
||||||
}
|
|
||||||
messageProp.setValue(newMessage);
|
|
||||||
changedSinceError.setValue(false);
|
changedSinceError.setValue(false);
|
||||||
|
|
||||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
@@ -370,7 +397,24 @@ public class StoreCreationComp extends DialogComp {
|
|||||||
@Override
|
@Override
|
||||||
protected Comp<?> pane(Comp<?> content) {
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
var back = super.pane(content);
|
var back = super.pane(content);
|
||||||
return new ErrorOverlayComp(back, messageProp);
|
return new ModalOverlayComp(back, messageProp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModalOverlay createErrorOverlay(String message) {
|
||||||
|
var comp = Comp.of(() -> {
|
||||||
|
var l = new TextArea();
|
||||||
|
l.setText(message);
|
||||||
|
l.setWrapText(true);
|
||||||
|
l.getStyleClass().add("error-overlay-comp");
|
||||||
|
l.setEditable(false);
|
||||||
|
return l;
|
||||||
|
});
|
||||||
|
var overlay = ModalOverlay.of("error", comp, new LabelGraphic.NodeGraphic(() -> {
|
||||||
|
var graphic = new FontIcon("mdomz-warning");
|
||||||
|
graphic.setIconColor(Color.RED);
|
||||||
|
return new StackPane(graphic);
|
||||||
|
}));
|
||||||
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import io.xpipe.app.comp.base.PrettyImageHelper;
|
|||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.util.ScanAlert;
|
import io.xpipe.app.util.ScanDialog;
|
||||||
|
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.control.Menu;
|
||||||
import javafx.scene.control.MenuButton;
|
import javafx.scene.control.MenuButton;
|
||||||
@@ -22,7 +22,7 @@ public class StoreCreationMenu {
|
|||||||
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
||||||
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
||||||
automatically.setOnAction(event -> {
|
automatically.setOnAction(event -> {
|
||||||
ScanAlert.showAsync(null);
|
ScanDialog.showAsync(null);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(automatically);
|
menu.getItems().add(automatically);
|
||||||
@@ -37,12 +37,22 @@ public class StoreCreationMenu {
|
|||||||
|
|
||||||
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, null));
|
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, null));
|
||||||
|
|
||||||
|
menu.getItems()
|
||||||
|
.add(category("addService", "mdi2l-link-plus", DataStoreCreationCategory.SERVICE, "customService"));
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category(
|
.add(category(
|
||||||
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "customService"));
|
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "sshLocalTunnel"));
|
||||||
|
|
||||||
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
|
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
|
||||||
|
|
||||||
|
menu.getItems()
|
||||||
|
.add(category(
|
||||||
|
"addIdentity",
|
||||||
|
"mdi2a-account-multiple-plus",
|
||||||
|
DataStoreCreationCategory.IDENTITY,
|
||||||
|
"localIdentity"));
|
||||||
|
|
||||||
// menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE,
|
// menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE,
|
||||||
// null));
|
// null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||||||
button.setPadding(Insets.EMPTY);
|
button.setPadding(Insets.EMPTY);
|
||||||
button.setMaxWidth(5000);
|
button.setMaxWidth(5000);
|
||||||
button.setFocusTraversable(true);
|
button.setFocusTraversable(true);
|
||||||
button.accessibleTextProperty().bind(getWrapper().nameProperty());
|
button.accessibleTextProperty().bind(getWrapper().getShownName());
|
||||||
button.setOnAction(event -> {
|
button.setOnAction(event -> {
|
||||||
event.consume();
|
event.consume();
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
@@ -137,12 +137,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||||||
.augment(button);
|
.augment(button);
|
||||||
|
|
||||||
var loading = LoadingOverlayComp.noProgress(
|
var loading = LoadingOverlayComp.noProgress(
|
||||||
Comp.of(() -> button),
|
Comp.of(() -> button), getWrapper().getEffectiveBusy());
|
||||||
getWrapper().getEntry().getValidity().isUsable()
|
|
||||||
? getWrapper()
|
|
||||||
.getBusy()
|
|
||||||
.or(getWrapper().getEntry().getProvider().busy(getWrapper()))
|
|
||||||
: getWrapper().getBusy());
|
|
||||||
AppFont.normal(button);
|
AppFont.normal(button);
|
||||||
return loading.createRegion();
|
return loading.createRegion();
|
||||||
}
|
}
|
||||||
@@ -169,12 +164,24 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Comp<?> createName() {
|
protected Comp<?> createName() {
|
||||||
LabelComp name = new LabelComp(getWrapper().nameProperty());
|
LabelComp name = new LabelComp(getWrapper().getShownName());
|
||||||
name.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS));
|
name.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS));
|
||||||
name.styleClass("name");
|
name.styleClass("name");
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Comp<?> createUserIcon() {
|
||||||
|
var button = new IconButtonComp("mdi2a-account");
|
||||||
|
button.styleClass("user-icon");
|
||||||
|
button.tooltipKey("personalConnection");
|
||||||
|
button.apply(struc -> {
|
||||||
|
AppFont.medium(struc.get());
|
||||||
|
struc.get().setOpacity(1.0);
|
||||||
|
});
|
||||||
|
button.hide(Bindings.not(getWrapper().getPerUser()));
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
protected Node createIcon(int w, int h) {
|
protected Node createIcon(int w, int h) {
|
||||||
return new StoreIconComp(getWrapper(), w, h).createRegion();
|
return new StoreIconComp(getWrapper(), w, h).createRegion();
|
||||||
}
|
}
|
||||||
@@ -327,7 +334,8 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||||||
contextMenu.getItems().add(color);
|
contextMenu.getItems().add(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getWrapper().getEntry().getProvider() != null) {
|
if (getWrapper().getEntry().getProvider() != null
|
||||||
|
&& getWrapper().getEntry().getProvider().canMoveCategories()) {
|
||||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||||
StoreViewState.get()
|
StoreViewState.get()
|
||||||
.getSortedCategories(getWrapper().getCategory().getValue().getRoot())
|
.getSortedCategories(getWrapper().getCategory().getValue().getRoot())
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class StoreEntryListComp extends SimpleComp {
|
|||||||
return custom;
|
return custom;
|
||||||
},
|
},
|
||||||
true);
|
true);
|
||||||
|
content.setPlatformPauseInterval(50);
|
||||||
content.apply(struc -> {
|
content.apply(struc -> {
|
||||||
// Reset scroll
|
// Reset scroll
|
||||||
StoreViewState.get().getActiveCategory().addListener((observable, oldValue, newValue) -> {
|
StoreViewState.get().getActiveCategory().addListener((observable, oldValue, newValue) -> {
|
||||||
@@ -70,6 +71,22 @@ public class StoreEntryListComp extends SimpleComp {
|
|||||||
},
|
},
|
||||||
StoreViewState.get().getAllEntries().getList(),
|
StoreViewState.get().getAllEntries().getList(),
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
|
var showIdentitiesIntro = Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
var allCat = StoreViewState.get().getAllIdentitiesCategory();
|
||||||
|
var connections = StoreViewState.get().getAllEntries().getList().stream()
|
||||||
|
.filter(wrapper -> allCat.equals(
|
||||||
|
wrapper.getCategory().getValue().getRoot()))
|
||||||
|
.toList();
|
||||||
|
return 0 == connections.size()
|
||||||
|
&& StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(allCat);
|
||||||
|
},
|
||||||
|
StoreViewState.get().getAllEntries().getList(),
|
||||||
|
StoreViewState.get().getActiveCategory());
|
||||||
var showScriptsIntro = Bindings.createBooleanBinding(
|
var showScriptsIntro = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (StoreViewState.get()
|
if (StoreViewState.get()
|
||||||
@@ -123,6 +140,7 @@ public class StoreEntryListComp extends SimpleComp {
|
|||||||
map.put(createList(), showList);
|
map.put(createList(), showList);
|
||||||
map.put(new StoreIntroComp(), showIntro);
|
map.put(new StoreIntroComp(), showIntro);
|
||||||
map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro);
|
map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro);
|
||||||
|
map.put(new StoreIdentitiesIntroComp(), showIdentitiesIntro);
|
||||||
|
|
||||||
return new MultiContentComp(map).createRegion();
|
return new MultiContentComp(map).createRegion();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
|||||||
categoryWrapper -> AppI18n.observable(
|
categoryWrapper -> AppI18n.observable(
|
||||||
categoryWrapper.getRoot().equals(StoreViewState.get().getAllConnectionsCategory())
|
categoryWrapper.getRoot().equals(StoreViewState.get().getAllConnectionsCategory())
|
||||||
? "connections"
|
? "connections"
|
||||||
: "scripts"));
|
: categoryWrapper
|
||||||
|
.getRoot()
|
||||||
|
.equals(StoreViewState.get().getAllScriptsCategory())
|
||||||
|
? "scripts"
|
||||||
|
: "identities"));
|
||||||
label.textProperty().bind(name);
|
label.textProperty().bind(name);
|
||||||
label.getStyleClass().add("name");
|
label.getStyleClass().add("name");
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ package io.xpipe.app.comp.store;
|
|||||||
import io.xpipe.app.ext.ActionProvider;
|
import io.xpipe.app.ext.ActionProvider;
|
||||||
import io.xpipe.app.ext.ShellStore;
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.PlatformThread;
|
import io.xpipe.app.util.PlatformThread;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.SingletonSessionStore;
|
import io.xpipe.core.store.SingletonSessionStore;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
import javafx.beans.value.ObservableStringValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -46,6 +50,12 @@ public class StoreEntryWrapper {
|
|||||||
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
||||||
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
||||||
private final BooleanProperty sessionActive = new SimpleBooleanProperty();
|
private final BooleanProperty sessionActive = new SimpleBooleanProperty();
|
||||||
|
private final Property<DataStore> store = new SimpleObjectProperty<>();
|
||||||
|
private final Property<String> information = new SimpleStringProperty();
|
||||||
|
private final BooleanProperty perUser = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
private boolean effectiveBusyProviderBound = false;
|
||||||
|
private final BooleanProperty effectiveBusy = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public StoreEntryWrapper(DataStoreEntry entry) {
|
public StoreEntryWrapper(DataStoreEntry entry) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
@@ -86,7 +96,7 @@ public class StoreEntryWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInStorage() {
|
public boolean isInStorage() {
|
||||||
return DataStorage.get().getStoreEntries().contains(entry);
|
return DataStorage.get() != null && DataStorage.get().getStoreEntries().contains(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editDialog() {
|
public void editDialog() {
|
||||||
@@ -139,6 +149,30 @@ public class StoreEntryWrapper {
|
|||||||
name.setValue(entry.getName());
|
name.setValue(entry.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effectiveBusyProviderBound && !getValidity().getValue().isUsable()) {
|
||||||
|
this.effectiveBusyProviderBound = false;
|
||||||
|
this.effectiveBusy.unbind();
|
||||||
|
this.effectiveBusy.bind(busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
var storeChanged = store.getValue() != entry.getStore();
|
||||||
|
store.setValue(entry.getStore());
|
||||||
|
if (storeChanged || !information.isBound()) {
|
||||||
|
if (entry.getProvider() != null) {
|
||||||
|
var section = StoreViewState.get().getSectionForWrapper(this);
|
||||||
|
if (section.isPresent()) {
|
||||||
|
information.unbind();
|
||||||
|
try {
|
||||||
|
var binding = PlatformThread.sync(entry.getProvider().informationString(section.get()));
|
||||||
|
information.bind(binding);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
|
information.bind(new SimpleStringProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lastAccess.setValue(entry.getLastAccess());
|
lastAccess.setValue(entry.getLastAccess());
|
||||||
disabled.setValue(entry.isDisabled());
|
disabled.setValue(entry.isDisabled());
|
||||||
validity.setValue(entry.getValidity());
|
validity.setValue(entry.getValidity());
|
||||||
@@ -153,17 +187,19 @@ public class StoreEntryWrapper {
|
|||||||
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
||||||
customIcon.setValue(entry.getIcon());
|
customIcon.setValue(entry.getIcon());
|
||||||
iconFile.setValue(entry.getEffectiveIconFile());
|
iconFile.setValue(entry.getEffectiveIconFile());
|
||||||
|
|
||||||
busy.setValue(entry.getBusyCounter().get() != 0);
|
busy.setValue(entry.getBusyCounter().get() != 0);
|
||||||
deletable.setValue(entry.getConfiguration().isDeletable());
|
deletable.setValue(entry.getConfiguration().isDeletable());
|
||||||
sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore<?> ss
|
sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore<?> ss
|
||||||
&& entry.getStore() instanceof ShellStore
|
&& entry.getStore() instanceof ShellStore
|
||||||
&& ss.isSessionRunning());
|
&& ss.isSessionRunning());
|
||||||
|
category.setValue(StoreViewState.get().getCategories().getList().stream()
|
||||||
category.setValue(StoreViewState.get()
|
.filter(storeCategoryWrapper ->
|
||||||
.getCategoryWrapper(DataStorage.get()
|
storeCategoryWrapper.getCategory().getUuid().equals(entry.getCategoryUuid()))
|
||||||
.getStoreCategoryIfPresent(entry.getCategoryUuid())
|
.findFirst()
|
||||||
.orElseThrow()));
|
.orElse(StoreViewState.get().getAllConnectionsCategory()));
|
||||||
|
perUser.setValue(
|
||||||
|
!category.getValue().getRoot().equals(StoreViewState.get().getAllIdentitiesCategory())
|
||||||
|
&& entry.isPerUserStore());
|
||||||
|
|
||||||
if (!entry.getValidity().isUsable()) {
|
if (!entry.getValidity().isUsable()) {
|
||||||
summary.setValue(null);
|
summary.setValue(null);
|
||||||
@@ -207,6 +243,16 @@ public class StoreEntryWrapper {
|
|||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!effectiveBusyProviderBound && getValidity().getValue().isUsable()) {
|
||||||
|
this.effectiveBusyProviderBound = true;
|
||||||
|
this.effectiveBusy.unbind();
|
||||||
|
this.effectiveBusy.bind(busy.or(getEntry().getProvider().busy(this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.effectiveBusy.isBound() && !getValidity().getValue().isUsable()) {
|
||||||
|
this.effectiveBusy.bind(busy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showActionProvider(ActionProvider p) {
|
public boolean showActionProvider(ActionProvider p) {
|
||||||
@@ -296,4 +342,34 @@ public class StoreEntryWrapper {
|
|||||||
public BooleanProperty disabledProperty() {
|
public BooleanProperty disabledProperty() {
|
||||||
return disabled;
|
return disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableStringValue getShownName() {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var n = nameProperty().getValue();
|
||||||
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
|
},
|
||||||
|
AppPrefs.get().censorMode(),
|
||||||
|
nameProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableStringValue getShownSummary() {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var n = summary.getValue();
|
||||||
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
|
},
|
||||||
|
AppPrefs.get().censorMode(),
|
||||||
|
summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableStringValue getShownInformation() {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
var n = information.getValue();
|
||||||
|
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||||
|
},
|
||||||
|
AppPrefs.get().censorMode(),
|
||||||
|
information);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
import io.xpipe.app.comp.base.*;
|
||||||
import io.xpipe.app.resources.SystemIcon;
|
import io.xpipe.app.resources.SystemIcon;
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.base.*;
|
||||||
|
import io.xpipe.app.resources.SystemIcon;
|
||||||
|
import io.xpipe.app.resources.SystemIcons;
|
||||||
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
public class StoreIconChoiceDialog {
|
||||||
|
|
||||||
|
public static void show(DataStoreEntry entry) {
|
||||||
|
var dialog = new StoreIconChoiceDialog(entry);
|
||||||
|
dialog.getOverlay().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObjectProperty<SystemIcon> selected = new SimpleObjectProperty<>();
|
||||||
|
private final DataStoreEntry entry;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ModalOverlay overlay;
|
||||||
|
|
||||||
|
public StoreIconChoiceDialog(DataStoreEntry entry) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.overlay = createOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModalOverlay createOverlay() {
|
||||||
|
var filterText = new SimpleStringProperty();
|
||||||
|
var filter = new FilterComp(filterText).grow(true, false);
|
||||||
|
filter.focusOnShow();
|
||||||
|
var github = new ButtonComp(null, new FontIcon("mdi2g-github"), () -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.SELFHST_ICONS);
|
||||||
|
})
|
||||||
|
.grow(false, true);
|
||||||
|
var modal = ModalOverlay.of(
|
||||||
|
"chooseCustomIcon",
|
||||||
|
new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText, () -> {
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.prefWidth(600));
|
||||||
|
modal.addButtonBarComp(github);
|
||||||
|
modal.addButtonBarComp(filter);
|
||||||
|
modal.addButton(new ModalButton(
|
||||||
|
"clear",
|
||||||
|
() -> {
|
||||||
|
selected.setValue(null);
|
||||||
|
finish();
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false));
|
||||||
|
modal.addButton(ModalButton.ok(() -> {
|
||||||
|
finish();
|
||||||
|
}))
|
||||||
|
.augment(button -> button.disableProperty().bind(selected.isNull()));
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finish() {
|
||||||
|
entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null, true);
|
||||||
|
overlay.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package io.xpipe.app.comp.store;
|
|
||||||
|
|
||||||
import io.xpipe.app.comp.Comp;
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
|
||||||
import io.xpipe.app.comp.base.DialogComp;
|
|
||||||
import io.xpipe.app.comp.base.FilterComp;
|
|
||||||
import io.xpipe.app.comp.base.HorizontalComp;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
|
||||||
import io.xpipe.app.resources.SystemIcon;
|
|
||||||
import io.xpipe.app.resources.SystemIcons;
|
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class StoreIconChoiceDialogComp extends SimpleComp {
|
|
||||||
|
|
||||||
public static void show(DataStoreEntry entry) {
|
|
||||||
var window = AppWindowHelper.sideWindow(
|
|
||||||
AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(entry, stage), false, null);
|
|
||||||
window.initModality(Modality.APPLICATION_MODAL);
|
|
||||||
window.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ObjectProperty<SystemIcon> selected = new SimpleObjectProperty<>();
|
|
||||||
private final DataStoreEntry entry;
|
|
||||||
private final Stage dialogStage;
|
|
||||||
|
|
||||||
public StoreIconChoiceDialogComp(DataStoreEntry entry, Stage dialogStage) {
|
|
||||||
this.entry = entry;
|
|
||||||
this.dialogStage = dialogStage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Region createSimple() {
|
|
||||||
var filterText = new SimpleStringProperty();
|
|
||||||
var filter = new FilterComp(filterText).apply(struc -> {
|
|
||||||
dialogStage.setOnShowing(event -> {
|
|
||||||
struc.get().requestFocus();
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var github = new ButtonComp(null, new FontIcon("mdi2g-github"), () -> {
|
|
||||||
Hyperlinks.open(Hyperlinks.SELFHST_ICONS);
|
|
||||||
})
|
|
||||||
.grow(false, true);
|
|
||||||
var dialog = new DialogComp() {
|
|
||||||
@Override
|
|
||||||
protected void finish() {
|
|
||||||
entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null, true);
|
|
||||||
dialogStage.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void discard() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Comp<?> content() {
|
|
||||||
return new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText, () -> {
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Comp<?> pane(Comp<?> content) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Comp<?> bottom() {
|
|
||||||
var clear = new ButtonComp(AppI18n.observable("clear"), () -> {
|
|
||||||
selected.setValue(null);
|
|
||||||
finish();
|
|
||||||
})
|
|
||||||
.grow(false, true);
|
|
||||||
return new HorizontalComp(List.of(github, filter.hgrow(), clear)).spacing(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Comp<?> finishButton() {
|
|
||||||
return super.finishButton().disable(selected.isNull());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dialog.prefWidth(600);
|
|
||||||
dialog.prefHeight(600);
|
|
||||||
return dialog.createRegion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,7 +54,7 @@ public class StoreIconComp extends SimpleComp {
|
|||||||
|
|
||||||
stack.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
stack.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
if (event.getButton() == MouseButton.PRIMARY) {
|
if (event.getButton() == MouseButton.PRIMARY) {
|
||||||
StoreIconChoiceDialogComp.show(wrapper.getEntry());
|
StoreIconChoiceDialog.show(wrapper.getEntry());
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||||
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
public class StoreIdentitiesIntroComp extends SimpleComp {
|
||||||
|
|
||||||
|
private Region createIntro() {
|
||||||
|
var title = new Label();
|
||||||
|
title.textProperty().bind(AppI18n.observable("identitiesIntroTitle"));
|
||||||
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
}
|
||||||
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
|
var introDesc = new Label();
|
||||||
|
introDesc.textProperty().bind(AppI18n.observable("identitiesIntroText"));
|
||||||
|
introDesc.setWrapText(true);
|
||||||
|
introDesc.setMaxWidth(470);
|
||||||
|
|
||||||
|
var img = new FontIcon("mdi2a-account-group");
|
||||||
|
img.setIconSize(80);
|
||||||
|
var text = new VBox(title, introDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
var hbox = new HBox(img, text);
|
||||||
|
hbox.setSpacing(55);
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var addButton = new Button(null, new FontIcon("mdi2p-play-circle"));
|
||||||
|
addButton.textProperty().bind(AppI18n.observable("createIdentity"));
|
||||||
|
addButton.setOnAction(event -> {
|
||||||
|
var canSync = DataStorage.get().supportsSharing();
|
||||||
|
var prov = canSync
|
||||||
|
? DataStoreProviders.byName("syncedIdentity").orElseThrow()
|
||||||
|
: DataStoreProviders.byName("localIdentity").orElseThrow();
|
||||||
|
StoreCreationComp.showCreation(prov, DataStoreCreationCategory.IDENTITY);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
var addPane = new StackPane(addButton);
|
||||||
|
addPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var v = new VBox(hbox, addPane);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
v.setSpacing(20);
|
||||||
|
v.getStyleClass().add("intro");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createBottom() {
|
||||||
|
var title = new Label();
|
||||||
|
title.textProperty().bind(AppI18n.observable("identitiesIntroBottomTitle"));
|
||||||
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
}
|
||||||
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
|
var importDesc = new Label();
|
||||||
|
importDesc.textProperty().bind(AppI18n.observable("identitiesIntroBottomText"));
|
||||||
|
importDesc.setWrapText(true);
|
||||||
|
importDesc.setMaxWidth(470);
|
||||||
|
|
||||||
|
var syncButton = new Button(null, new FontIcon("mdi2p-play-circle"));
|
||||||
|
syncButton.textProperty().bind(AppI18n.observable("setupSync"));
|
||||||
|
syncButton.setOnAction(event -> {
|
||||||
|
AppPrefs.get().selectCategory("sync");
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
var syncPane = new StackPane(syncButton);
|
||||||
|
syncPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var fi = new FontIcon("mdi2g-git");
|
||||||
|
fi.setIconSize(80);
|
||||||
|
var img = new StackPane(fi);
|
||||||
|
img.setPrefWidth(100);
|
||||||
|
img.setPrefHeight(150);
|
||||||
|
var text = new VBox(title, importDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
var hbox = new HBox(img, text);
|
||||||
|
hbox.setSpacing(35);
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var v = new VBox(hbox, syncPane);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
v.setSpacing(20);
|
||||||
|
v.getStyleClass().add("intro");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region createSimple() {
|
||||||
|
var intro = createIntro();
|
||||||
|
var introImport = createBottom();
|
||||||
|
var v = new VBox(intro, introImport);
|
||||||
|
v.setSpacing(80);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
var sp = new StackPane(v);
|
||||||
|
sp.setPadding(new Insets(40, 0, 0, 0));
|
||||||
|
sp.setAlignment(Pos.CENTER);
|
||||||
|
sp.setPickOnBounds(false);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.SimpleComp;
|
import io.xpipe.app.comp.SimpleComp;
|
||||||
import io.xpipe.app.comp.base.PrettySvgComp;
|
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.ScanAlert;
|
import io.xpipe.app.util.ScanDialog;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
@@ -39,12 +38,13 @@ public class StoreIntroComp extends SimpleComp {
|
|||||||
|
|
||||||
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
||||||
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
||||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
scanButton.setOnAction(event -> ScanDialog.showAsync(DataStorage.get().local()));
|
||||||
scanButton.setDefaultButton(true);
|
scanButton.setDefaultButton(true);
|
||||||
var scanPane = new StackPane(scanButton);
|
var scanPane = new StackPane(scanButton);
|
||||||
scanPane.setAlignment(Pos.CENTER);
|
scanPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
var img = new PrettySvgComp(new SimpleStringProperty("graphics/Wave.svg"), 80, 150).createRegion();
|
var img = PrettyImageHelper.ofSpecificFixedSize("graphics/Wave.svg", 80, 144)
|
||||||
|
.createRegion();
|
||||||
var text = new VBox(title, introDesc);
|
var text = new VBox(title, introDesc);
|
||||||
text.setSpacing(5);
|
text.setSpacing(5);
|
||||||
text.setAlignment(Pos.CENTER_LEFT);
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||||||
var w = section.getWrapper();
|
var w = section.getWrapper();
|
||||||
var graphic = w.getEntry().getEffectiveIconFile();
|
var graphic = w.getEntry().getEffectiveIconFile();
|
||||||
if (c.getList().isEmpty()) {
|
if (c.getList().isEmpty()) {
|
||||||
var item = ContextMenuHelper.item(
|
var item = new MenuItem(
|
||||||
new LabelGraphic.ImageGraphic(graphic, 16), w.getName().getValue());
|
w.getName().getValue(), new LabelGraphic.ImageGraphic(graphic, 16).createGraphicNode());
|
||||||
item.setOnAction(event -> {
|
item.setOnAction(event -> {
|
||||||
action.accept(section);
|
action.accept(section);
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
|
|||||||
@@ -139,14 +139,13 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getAllChildren().getList());
|
section.getAllChildren().getList());
|
||||||
var content = new ListBoxViewComp<>(
|
var content = new ListBoxViewComp<>(
|
||||||
listSections.getList(),
|
listSections.getList(),
|
||||||
section.getAllChildren().getList(),
|
section.getAllChildren().getList(),
|
||||||
(StoreSection e) -> {
|
(StoreSection e) -> {
|
||||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||||
},
|
},
|
||||||
false)
|
false);
|
||||||
.minHeight(0)
|
content.minHeight(0).hgrow();
|
||||||
.hgrow();
|
|
||||||
|
|
||||||
var expanded = Bindings.createBooleanBinding(
|
var expanded = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -192,6 +191,10 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||||||
}
|
}
|
||||||
struc.get().getStyleClass().setAll(newList);
|
struc.get().getStyleClass().setAll(newList);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
section.getWrapper().getPerUser().subscribe(val -> {
|
||||||
|
struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.createStructure();
|
.createStructure();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||||||
var list = new ArrayList<Comp<?>>();
|
var list = new ArrayList<Comp<?>>();
|
||||||
BooleanProperty expanded;
|
BooleanProperty expanded;
|
||||||
if (section.getWrapper() != null) {
|
if (section.getWrapper() != null) {
|
||||||
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
var root = new ButtonComp(section.getWrapper().getShownName(), () -> {})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
struc.get()
|
struc.get()
|
||||||
.setGraphic(PrettyImageHelper.ofFixedSize(
|
.setGraphic(PrettyImageHelper.ofFixedSize(
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ public class StoreSidebarComp extends SimpleComp {
|
|||||||
.styleClass("color-box")
|
.styleClass("color-box")
|
||||||
.styleClass("gray")
|
.styleClass("gray")
|
||||||
.styleClass("bar"),
|
.styleClass("bar"),
|
||||||
|
new StoreCategoryListComp(StoreViewState.get().getAllIdentitiesCategory())
|
||||||
|
.styleClass("color-box")
|
||||||
|
.styleClass("gray")
|
||||||
|
.styleClass("bar"),
|
||||||
Comp.of(() -> new Region())
|
Comp.of(() -> new Region())
|
||||||
.styleClass("color-box")
|
.styleClass("color-box")
|
||||||
.styleClass("gray")
|
.styleClass("gray")
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ public class StoreToggleComp extends SimpleComp {
|
|||||||
var val = new SimpleBooleanProperty();
|
var val = new SimpleBooleanProperty();
|
||||||
ObservableValue<LabelGraphic> g = graphic
|
ObservableValue<LabelGraphic> g = graphic
|
||||||
? val.map(aBoolean -> aBoolean
|
? val.map(aBoolean -> aBoolean
|
||||||
? new LabelGraphic.IconGraphic("mdi2c-circle-slice-8")
|
? new LabelGraphic.IconGraphic("mdi2e-eye-plus")
|
||||||
: new LabelGraphic.IconGraphic("mdi2c-circle-half-full"))
|
: new LabelGraphic.IconGraphic("mdi2e-eye-minus"))
|
||||||
: null;
|
: null;
|
||||||
var t = new StoreToggleComp(
|
var t = new StoreToggleComp(
|
||||||
nameKey,
|
nameKey,
|
||||||
@@ -91,7 +91,7 @@ public class StoreToggleComp extends SimpleComp {
|
|||||||
StoreViewState.get().toggleStoreListUpdate();
|
StoreViewState.get().toggleStoreListUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.tooltipKey("showAllChildren");
|
t.tooltipKey("showNonRunningChildren");
|
||||||
t.value.subscribe((newValue) -> {
|
t.value.subscribe((newValue) -> {
|
||||||
val.set(newValue);
|
val.set(newValue);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class StoreViewState {
|
|||||||
INSTANCE = new StoreViewState();
|
INSTANCE = new StoreViewState();
|
||||||
INSTANCE.updateContent();
|
INSTANCE.updateContent();
|
||||||
INSTANCE.initSections();
|
INSTANCE.initSections();
|
||||||
|
INSTANCE.updateContent();
|
||||||
INSTANCE.initFilterJump();
|
INSTANCE.initFilterJump();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ public class StoreViewState {
|
|||||||
var matchingCats = categories.getList().stream()
|
var matchingCats = categories.getList().stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getRoot().equals(all))
|
storeCategoryWrapper.getRoot().equals(all))
|
||||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getDirectContainedEntries().stream()
|
.filter(storeCategoryWrapper -> storeCategoryWrapper.getDirectContainedEntries().getList().stream()
|
||||||
.anyMatch(wrapper -> wrapper.matchesFilter(newValue)))
|
.anyMatch(wrapper -> wrapper.matchesFilter(newValue)))
|
||||||
.toList();
|
.toList();
|
||||||
if (matchingCats.size() == 1) {
|
if (matchingCats.size() == 1) {
|
||||||
@@ -239,13 +240,13 @@ public class StoreViewState {
|
|||||||
@Override
|
@Override
|
||||||
public void onCategoryAdd(DataStoreCategory category) {
|
public void onCategoryAdd(DataStoreCategory category) {
|
||||||
var l = new StoreCategoryWrapper(category);
|
var l = new StoreCategoryWrapper(category);
|
||||||
l.update();
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
// Don't update anything if we have already reset
|
// Don't update anything if we have already reset
|
||||||
if (INSTANCE == null) {
|
if (INSTANCE == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.update();
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
categories.getList().add(l);
|
categories.getList().add(l);
|
||||||
}
|
}
|
||||||
@@ -284,21 +285,27 @@ public class StoreViewState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntryCategoryChange(DataStoreCategory from, DataStoreCategory to) {
|
public void onEntryCategoryChange(DataStoreCategory from, DataStoreCategory to) {
|
||||||
synchronized (this) {
|
Platform.runLater(() -> {
|
||||||
categories.getList().forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
synchronized (this) {
|
||||||
}
|
categories.getList().forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
|
public Optional<StoreSection> getSectionForWrapper(StoreEntryWrapper wrapper) {
|
||||||
|
if (currentTopLevelSection == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
StoreSection current = getCurrentTopLevelSection();
|
StoreSection current = getCurrentTopLevelSection();
|
||||||
while (true) {
|
while (true) {
|
||||||
var child = current.getAllChildren().getList().stream()
|
var child = current.getAllChildren().getList().stream()
|
||||||
.filter(section -> section.getWrapper().equals(wrapper))
|
.filter(section -> section.getWrapper().equals(wrapper))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (child.isPresent()) {
|
if (child.isPresent()) {
|
||||||
return Optional.of(current);
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
var traverse = current.getAllChildren().getList().stream()
|
var traverse = current.getAllChildren().getList().stream()
|
||||||
@@ -325,35 +332,37 @@ public class StoreViewState {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o1.getParent() == null && o2.getParent() == null) {
|
var p1 = o1.getParent();
|
||||||
|
var p2 = o2.getParent();
|
||||||
|
if (p1 == null && p2 == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o1.getParent() == null) {
|
if (p1 == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o2.getParent() == null) {
|
if (p2 == null) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o1.getDepth() > o2.getDepth()) {
|
if (o1.getDepth() > o2.getDepth()) {
|
||||||
if (o1.getParent() == o2) {
|
if (p1 == o2) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return compare(o1.getParent(), o2);
|
return compare(p1, o2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o1.getDepth() < o2.getDepth()) {
|
if (o1.getDepth() < o2.getDepth()) {
|
||||||
if (o2.getParent() == o1) {
|
if (p2 == o1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return compare(o1, o2.getParent());
|
return compare(o1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
var parent = compare(o1.getParent(), o2.getParent());
|
var parent = compare(p1, p2);
|
||||||
if (parent != 0) {
|
if (parent != 0) {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
@@ -384,6 +393,14 @@ public class StoreViewState {
|
|||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StoreCategoryWrapper getAllIdentitiesCategory() {
|
||||||
|
return categories.getList().stream()
|
||||||
|
.filter(storeCategoryWrapper ->
|
||||||
|
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_IDENTITIES_CATEGORY_UUID))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
||||||
return allEntries.getList().stream()
|
return allEntries.getList().stream()
|
||||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.AppLayoutComp;
|
|
||||||
import io.xpipe.app.core.window.AppMainWindow;
|
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
|
||||||
import io.xpipe.app.util.PlatformThread;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
|
||||||
import javafx.beans.value.ObservableDoubleValue;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -34,43 +25,4 @@ public class App extends Application {
|
|||||||
APP = this;
|
APP = this;
|
||||||
stage = primaryStage;
|
stage = primaryStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupWindow() {
|
|
||||||
var content = new AppLayoutComp();
|
|
||||||
var t = LicenseProvider.get().licenseTitle();
|
|
||||||
var u = XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate();
|
|
||||||
var titleBinding = Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
var base = String.format(
|
|
||||||
"XPipe %s (%s)", t.getValue(), AppProperties.get().getVersion());
|
|
||||||
var prefix = AppProperties.get().isStaging() ? "[Public Test Build, Not a proper release] " : "";
|
|
||||||
var suffix = u.getValue() != null
|
|
||||||
? " " + AppI18n.get("updateReadyTitle", u.getValue().getVersion())
|
|
||||||
: "";
|
|
||||||
return prefix + base + suffix;
|
|
||||||
},
|
|
||||||
u,
|
|
||||||
t,
|
|
||||||
AppPrefs.get().language());
|
|
||||||
|
|
||||||
var appWindow = AppMainWindow.init(stage);
|
|
||||||
appWindow.getStage().titleProperty().bind(PlatformThread.sync(titleBinding));
|
|
||||||
appWindow.initialize();
|
|
||||||
appWindow.setContent(content);
|
|
||||||
TrackEvent.info("Application window initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void focus() {
|
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
|
||||||
stage.requestFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableDoubleValue displayScale() {
|
|
||||||
if (getStage() == null) {
|
|
||||||
return new SimpleDoubleProperty(1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getStage().outputScaleXProperty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.core.launcher.LauncherInput;
|
import io.xpipe.app.core.window.AppDialog;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
|
||||||
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.input.Clipboard;
|
import javafx.scene.input.Clipboard;
|
||||||
import javafx.scene.input.DataFormat;
|
import javafx.scene.input.DataFormat;
|
||||||
|
|
||||||
@@ -26,7 +24,7 @@ public class AppActionLinkDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void handle(String content, boolean showAlert) {
|
public static void handle(String content, boolean showAlert) {
|
||||||
var detected = LauncherInput.of(content);
|
var detected = AppOpenArguments.parseActions(content);
|
||||||
if (detected.size() == 0) {
|
if (detected.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -35,7 +33,7 @@ public class AppActionLinkDetector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LauncherInput.handle(List.of(content));
|
AppOpenArguments.handle(List.of(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void detectOnFocus() {
|
public static void detectOnFocus() {
|
||||||
@@ -61,15 +59,6 @@ public class AppActionLinkDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean showAlert() {
|
private static boolean showAlert() {
|
||||||
return AppWindowHelper.showBlockingAlert(alert -> {
|
return AppDialog.confirm("clipboardActionDetected");
|
||||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
|
||||||
alert.setTitle(AppI18n.get("clipboardActionDetectedTitle"));
|
|
||||||
alert.setHeaderText(AppI18n.get("clipboardActionDetectedHeader"));
|
|
||||||
alert.getDialogPane()
|
|
||||||
.setContent(
|
|
||||||
AppWindowHelper.alertContentText(AppI18n.get("clipboardActionDetectedContent")));
|
|
||||||
})
|
|
||||||
.map(buttonType -> buttonType.getButtonData().isDefaultButton())
|
|
||||||
.orElse(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.issue.LogErrorHandler;
|
||||||
|
import io.xpipe.core.util.XPipeDaemonMode;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class AppArguments {
|
||||||
|
|
||||||
|
List<String> rawArgs;
|
||||||
|
List<String> resolvedArgs;
|
||||||
|
XPipeDaemonMode modeArg;
|
||||||
|
List<String> openArgs;
|
||||||
|
|
||||||
|
private static final Pattern PROPERTY_PATTERN = Pattern.compile("^-[DP](.+)=(.+)$");
|
||||||
|
|
||||||
|
public static AppArguments init(String[] args) {
|
||||||
|
var rawArgs = Arrays.asList(args);
|
||||||
|
var resolvedArgs = Arrays.asList(parseProperties(args));
|
||||||
|
var command = LauncherCommand.resolveLauncher(resolvedArgs.toArray(String[]::new));
|
||||||
|
return new AppArguments(rawArgs, resolvedArgs, command.mode, command.inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] parseProperties(String[] args) {
|
||||||
|
List<String> newArgs = new ArrayList<>();
|
||||||
|
for (var a : args) {
|
||||||
|
var m = PROPERTY_PATTERN.matcher(a);
|
||||||
|
if (m.matches()) {
|
||||||
|
var k = m.group(1);
|
||||||
|
var v = m.group(2);
|
||||||
|
System.setProperty(k, v);
|
||||||
|
} else {
|
||||||
|
newArgs.add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newArgs.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ModeConverter implements CommandLine.ITypeConverter<XPipeDaemonMode> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XPipeDaemonMode convert(String value) {
|
||||||
|
return XPipeDaemonMode.get(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Command()
|
||||||
|
public static class LauncherCommand implements Callable<Integer> {
|
||||||
|
|
||||||
|
@CommandLine.Parameters(paramLabel = "<input>")
|
||||||
|
final List<String> inputs = List.of();
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = {"--mode"},
|
||||||
|
description = "The mode to launch the daemon in or switch too",
|
||||||
|
paramLabel = "<mode id>",
|
||||||
|
converter = ModeConverter.class)
|
||||||
|
XPipeDaemonMode mode;
|
||||||
|
|
||||||
|
public static LauncherCommand resolveLauncher(String[] args) {
|
||||||
|
var cmd = new CommandLine(new LauncherCommand());
|
||||||
|
cmd.setExecutionExceptionHandler((ex, commandLine, parseResult) -> {
|
||||||
|
var event = ErrorEvent.fromThrowable(ex).term().build();
|
||||||
|
// Print error in case we launched from the command-line
|
||||||
|
new LogErrorHandler().handle(event);
|
||||||
|
event.handle();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
cmd.setParameterExceptionHandler((ex, args1) -> {
|
||||||
|
var event = ErrorEvent.fromThrowable(ex).term().expected().build();
|
||||||
|
// Print error in case we launched from the command-line
|
||||||
|
new LogErrorHandler().handle(event);
|
||||||
|
event.handle();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (AppLogs.get() != null) {
|
||||||
|
// Use original output streams for command output
|
||||||
|
cmd.setOut(new PrintWriter(AppLogs.get().getOriginalSysOut()));
|
||||||
|
cmd.setErr(new PrintWriter(AppLogs.get().getOriginalSysErr()));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmd.parseArgs(args);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Fix serialization issues with exception class
|
||||||
|
var converted = t instanceof CommandLine.UnmatchedArgumentException u
|
||||||
|
? new IllegalArgumentException(u.getMessage())
|
||||||
|
: t;
|
||||||
|
var e = ErrorEvent.fromThrowable(converted).term().build();
|
||||||
|
// Print error in case we launched from the command-line
|
||||||
|
new LogErrorHandler().handle(e);
|
||||||
|
e.handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.getCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer call() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.JsonConfigHelper;
|
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -100,8 +99,7 @@ public class AppCache {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
FileUtils.forceMkdirParent(path.toFile());
|
FileUtils.forceMkdirParent(path.toFile());
|
||||||
var tree = JacksonMapper.getDefault().valueToTree(val);
|
JacksonMapper.getDefault().writeValue(path.toFile(), val);
|
||||||
JsonConfigHelper.writeConfig(path, tree);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable("Could not write cache data for key " + key, e)
|
ErrorEvent.fromThrowable("Could not write cache data for key " + key, e)
|
||||||
.omitted(true)
|
.omitted(true)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.Main;
|
import io.xpipe.app.Main;
|
||||||
import io.xpipe.app.core.launcher.LauncherInput;
|
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.storage.DataStorageUserHandler;
|
||||||
import io.xpipe.app.util.PlatformState;
|
import io.xpipe.app.util.PlatformState;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
@@ -16,13 +16,7 @@ import javax.imageio.ImageIO;
|
|||||||
|
|
||||||
public class AppDesktopIntegration {
|
public class AppDesktopIntegration {
|
||||||
|
|
||||||
public static void setupDesktopIntegrations() {
|
public static void init() {
|
||||||
// Check if we were/are able to initialize the platform
|
|
||||||
// If not, we don't have to attempt the awt setup as well
|
|
||||||
if (!PlatformState.initPlatformIfNeeded()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Desktop.isDesktopSupported()) {
|
if (Desktop.isDesktopSupported()) {
|
||||||
Desktop.getDesktop().addAppEventListener(new SystemSleepListener() {
|
Desktop.getDesktop().addAppEventListener(new SystemSleepListener() {
|
||||||
@@ -31,10 +25,11 @@ public class AppDesktopIntegration {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void systemAwoke(SystemSleepEvent e) {
|
public void systemAwoke(SystemSleepEvent e) {
|
||||||
|
var handler = DataStorageUserHandler.getInstance();
|
||||||
if (AppPrefs.get() != null
|
if (AppPrefs.get() != null
|
||||||
&& AppPrefs.get().lockVaultOnHibernation().get()
|
&& AppPrefs.get().lockVaultOnHibernation().get()
|
||||||
&& AppPrefs.get().getLockCrypt().get() != null
|
&& handler != null
|
||||||
&& !AppPrefs.get().getLockCrypt().get().isBlank()) {
|
&& handler.getActiveUser() != null) {
|
||||||
// If we run this at the same time as the system is sleeping, there might be exceptions
|
// If we run this at the same time as the system is sleeping, there might be exceptions
|
||||||
// because the platform does not like being shut down while sleeping
|
// because the platform does not like being shut down while sleeping
|
||||||
// This assures that it will be run later, on system wake
|
// This assures that it will be run later, on system wake
|
||||||
@@ -62,7 +57,7 @@ public class AppDesktopIntegration {
|
|||||||
|
|
||||||
// URL open operations have to be handled in a special way on macOS!
|
// URL open operations have to be handled in a special way on macOS!
|
||||||
Desktop.getDesktop().setOpenURIHandler(e -> {
|
Desktop.getDesktop().setOpenURIHandler(e -> {
|
||||||
LauncherInput.handle(List.of(e.getURI().toString()));
|
AppOpenArguments.handle(List.of(e.getURI().toString()));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do it this way to prevent IDE inspections from complaining
|
// Do it this way to prevent IDE inspections from complaining
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public class AppExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadAllExtensions() {
|
private void loadAllExtensions() {
|
||||||
for (var ext : List.of("proc", "uacc")) {
|
for (var ext : List.of("system", "proc", "uacc")) {
|
||||||
var extension = findAndParseExtension(ext, baseLayer)
|
var extension = findAndParseExtension(ext, baseLayer)
|
||||||
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
|
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
|
||||||
loadedExtensions.add(extension);
|
loadedExtensions.add(extension);
|
||||||
@@ -158,16 +158,20 @@ public class AppExtensionManager {
|
|||||||
private Optional<Extension> findAndParseExtension(String name, ModuleLayer parent) {
|
private Optional<Extension> findAndParseExtension(String name, ModuleLayer parent) {
|
||||||
var inModulePath = ModuleLayer.boot().findModule("io.xpipe.ext." + name);
|
var inModulePath = ModuleLayer.boot().findModule("io.xpipe.ext." + name);
|
||||||
if (inModulePath.isPresent()) {
|
if (inModulePath.isPresent()) {
|
||||||
|
TrackEvent.info("Loaded extension " + name + " from boot module path");
|
||||||
return Optional.of(new Extension(null, inModulePath.get().getName(), name, inModulePath.get(), 0));
|
return Optional.of(new Extension(null, inModulePath.get().getName(), name, inModulePath.get(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
for (Path extensionBaseDirectory : extensionBaseDirectories) {
|
||||||
var found = parseExtensionDirectory(extensionBaseDirectory.resolve(name), parent);
|
var extensionDir = extensionBaseDirectory.resolve(name);
|
||||||
|
var found = parseExtensionDirectory(extensionDir, parent);
|
||||||
if (found.isPresent()) {
|
if (found.isPresent()) {
|
||||||
|
TrackEvent.info("Loaded extension " + name + " from module " + extensionDir);
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TrackEvent.info("Unable to locate module " + name);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import io.xpipe.core.process.OsType;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
|
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -46,7 +48,12 @@ public class AppFont {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
TrackEvent.info("Loading fonts ...");
|
// Load ikonli fonts
|
||||||
|
TrackEvent.info("Loading ikonli fonts ...");
|
||||||
|
new FontIcon("mdi2s-stop");
|
||||||
|
new FontIcon("mdi2m-magnify");
|
||||||
|
|
||||||
|
TrackEvent.info("Loading bundled fonts ...");
|
||||||
AppResources.with(
|
AppResources.with(
|
||||||
AppResources.XPIPE_MODULE,
|
AppResources.XPIPE_MODULE,
|
||||||
"fonts",
|
"fonts",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user