Reformat [stage]

This commit is contained in:
crschnick 2024-06-15 11:10:39 +00:00
parent 5ce9538633
commit 8f49c35aca
201 changed files with 1335 additions and 820 deletions

View file

@ -1,7 +1,5 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
@ -10,6 +8,9 @@ import io.xpipe.app.util.MarkdownHelper;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.XPipeInstallation;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter;
import java.io.IOException;
@ -22,16 +23,22 @@ import java.util.concurrent.Executors;
public class AppBeaconServer {
private static AppBeaconServer INSTANCE;
@Getter
private final int port;
@Getter
private final boolean propertyPort;
private boolean running;
private HttpServer server;
@Getter
private final Set<BeaconSession> sessions = new HashSet<>();
@Getter
private final Set<BeaconShellSession> shellSessions = new HashSet<>();
@Getter
private String localAuthSecret;
@ -122,8 +129,7 @@ public class AppBeaconServer {
"openapi.yaml", "misc/openapi.yaml",
"markdown.css", "misc/github-markdown-dark.css",
"highlight.min.js", "misc/highlight.min.js",
"github-dark.min.css", "misc/github-dark.min.css"
);
"github-dark.min.css", "misc/github-dark.min.css");
resourceMap.forEach((s, s2) -> {
server.createContext("/" + s, exchange -> {
handleResource(exchange, s2);
@ -145,7 +151,7 @@ public class AppBeaconServer {
});
}
var body = resources.get(resource).getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200,body.length);
exchange.sendResponseHeaders(200, body.length);
try (var out = exchange.getResponseBody()) {
out.write(body);
}
@ -154,19 +160,21 @@ public class AppBeaconServer {
private void handleCatchAll(HttpExchange exchange) throws IOException {
if (notFoundHtml == null) {
AppResources.with(AppResources.XPIPE_MODULE, "misc/api.md", file -> {
notFoundHtml = MarkdownHelper.toHtml(Files.readString(file), head -> {
return head + "\n" +
"<link rel=\"stylesheet\" href=\"markdown.css\">" + "\n" +
"<link rel=\"stylesheet\" href=\"github-dark.min.css\">" + "\n" +
"<script src=\"highlight.min.js\"></script>" + "\n" +
"<script>hljs.highlightAll();</script>";
}, s -> {
return "<div style=\"max-width: 800px;margin: auto;\">" + s + "</div>";
});
notFoundHtml = MarkdownHelper.toHtml(
Files.readString(file),
head -> {
return head + "\n" + "<link rel=\"stylesheet\" href=\"markdown.css\">"
+ "\n" + "<link rel=\"stylesheet\" href=\"github-dark.min.css\">"
+ "\n" + "<script src=\"highlight.min.js\"></script>"
+ "\n" + "<script>hljs.highlightAll();</script>";
},
s -> {
return "<div style=\"max-width: 800px;margin: auto;\">" + s + "</div>";
});
});
}
var body = notFoundHtml.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200,body.length);
exchange.sendResponseHeaders(200, body.length);
try (var out = exchange.getResponseBody()) {
out.write(body);
}

View file

@ -1,12 +1,13 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.beacon.*;
import io.xpipe.core.util.JacksonMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import lombok.SneakyThrows;
import java.io.IOException;
@ -18,10 +19,12 @@ public class BeaconRequestHandler<T> implements HttpHandler {
private final BeaconInterface<T> beaconInterface;
public BeaconRequestHandler(BeaconInterface<T> beaconInterface) {this.beaconInterface = beaconInterface;}
public BeaconRequestHandler(BeaconInterface<T> beaconInterface) {
this.beaconInterface = beaconInterface;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
public void handle(HttpExchange exchange) {
if (!AppPrefs.get().disableApiAuthentication().get() && beaconInterface.requiresAuthentication()) {
var auth = exchange.getRequestHeaders().getFirst("Authorization");
if (auth == null) {
@ -30,7 +33,10 @@ public class BeaconRequestHandler<T> implements HttpHandler {
}
var token = auth.replace("Bearer ", "");
var session = AppBeaconServer.get().getSessions().stream().filter(s -> s.getToken().equals(token)).findFirst().orElse(null);
var session = AppBeaconServer.get().getSessions().stream()
.filter(s -> s.getToken().equals(token))
.findFirst()
.orElse(null);
if (session == null) {
writeError(exchange, new BeaconClientErrorResponse("Unknown token"), 403);
return;
@ -47,8 +53,11 @@ public class BeaconRequestHandler<T> implements HttpHandler {
try (InputStream is = exchange.getRequestBody()) {
var tree = JacksonMapper.getDefault().readTree(is);
TrackEvent.trace("Parsed raw request:\n" + tree.toPrettyString());
var emptyRequestClass = tree.isEmpty() && beaconInterface.getRequestClass().getDeclaredFields().length == 0;
object = emptyRequestClass ? createDefaultRequest(beaconInterface) : JacksonMapper.getDefault().treeToValue(tree, beaconInterface.getRequestClass());
var emptyRequestClass =
tree.isEmpty() && beaconInterface.getRequestClass().getDeclaredFields().length == 0;
object = emptyRequestClass
? createDefaultRequest(beaconInterface)
: JacksonMapper.getDefault().treeToValue(tree, beaconInterface.getRequestClass());
TrackEvent.trace("Parsed request object:\n" + object);
}
response = beaconInterface.handle(exchange, object);
@ -62,7 +71,8 @@ public class BeaconRequestHandler<T> implements HttpHandler {
writeError(exchange, new BeaconServerErrorResponse(cause), 500);
return;
} catch (IOException ex) {
// Handle serialization errors as normal exceptions and other IO exceptions as assuming that the connection is broken
// Handle serialization errors as normal exceptions and other IO exceptions as assuming that the connection
// is broken
if (!ex.getClass().getName().contains("jackson")) {
ErrorEvent.fromThrowable(ex).omit().expected().handle();
} else {
@ -76,32 +86,32 @@ public class BeaconRequestHandler<T> implements HttpHandler {
return;
}
try {
var emptyResponseClass = beaconInterface.getResponseClass().getDeclaredFields().length == 0;
if (!emptyResponseClass && response != null) {
TrackEvent.trace("Sending response:\n" + object);
var tree = JacksonMapper.getDefault().valueToTree(response);
TrackEvent.trace("Sending raw response:\n" + tree.toPrettyString());
var bytes = tree.toPrettyString().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} else {
exchange.sendResponseHeaders(200, -1);
try {
var emptyResponseClass = beaconInterface.getResponseClass().getDeclaredFields().length == 0;
if (!emptyResponseClass && response != null) {
TrackEvent.trace("Sending response:\n" + object);
var tree = JacksonMapper.getDefault().valueToTree(response);
TrackEvent.trace("Sending raw response:\n" + tree.toPrettyString());
var bytes = tree.toPrettyString().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} catch (IOException ioException) {
ErrorEvent.fromThrowable(ioException).omit().expected().handle();
} catch (Throwable other) {
ErrorEvent.fromThrowable(other).handle();
writeError(exchange, new BeaconServerErrorResponse(other), 500);
return;
} else {
exchange.sendResponseHeaders(200, -1);
}
} catch (IOException ioException) {
ErrorEvent.fromThrowable(ioException).omit().expected().handle();
} catch (Throwable other) {
ErrorEvent.fromThrowable(other).handle();
writeError(exchange, new BeaconServerErrorResponse(other), 500);
}
}
private void writeError(HttpExchange exchange, Object errorMessage, int code) {
try {
var bytes = JacksonMapper.getDefault().writeValueAsString(errorMessage).getBytes(StandardCharsets.UTF_8);
var bytes =
JacksonMapper.getDefault().writeValueAsString(errorMessage).getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(code, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);

View file

@ -1,6 +1,7 @@
package io.xpipe.app.beacon;
import io.xpipe.beacon.BeaconClientInformation;
import lombok.Value;
@Value

View file

@ -2,6 +2,7 @@ package io.xpipe.app.beacon;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import lombok.Value;
@Value

View file

@ -1,18 +1,19 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.AskpassAlert;
import io.xpipe.app.util.SecretManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.AskpassExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class AskpassExchangeImpl extends AskpassExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg) {
if (msg.getRequest() == null) {
var r = AskpassAlert.queryRaw(msg.getPrompt(), null);
return Response.builder().value(r.getSecret()).build();

View file

@ -1,6 +1,5 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.beacon.BeaconClientException;
@ -8,6 +7,8 @@ import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ConnectionQueryExchange;
import io.xpipe.core.store.StorePath;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -16,7 +17,7 @@ import java.util.regex.Pattern;
public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg) {
var catMatcher = Pattern.compile(toRegex("all connections/" + msg.getCategoryFilter()));
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter()));
@ -31,7 +32,9 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
continue;
}
var cat = DataStorage.get().getStoreCategoryIfPresent(storeEntry.getCategoryUuid()).orElse(null);
var cat = DataStorage.get()
.getStoreCategoryIfPresent(storeEntry.getCategoryUuid())
.orElse(null);
if (cat == null) {
continue;
}
@ -46,11 +49,18 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
var mapped = new ArrayList<QueryResponse>();
for (DataStoreEntry e : found) {
var names = DataStorage.get().getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow()).getNames();
var names = DataStorage.get()
.getStorePath(DataStorage.get()
.getStoreCategoryIfPresent(e.getCategoryUuid())
.orElseThrow())
.getNames();
var cat = new StorePath(names.subList(1, names.size()));
var obj = ConnectionQueryExchange.QueryResponse.builder()
.uuid(e.getUuid()).category(cat).connection(DataStorage.get()
.getStorePath(e)).type(e.getProvider().getId()).build();
.uuid(e.getUuid())
.category(cat)
.connection(DataStorage.get().getStorePath(e))
.type(e.getProvider().getId())
.build();
mapped.add(obj);
}
return Response.builder().found(mapped).build();
@ -86,20 +96,16 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
}
break;
case '*':
if (inClass == 0)
sb.append(".*");
else
sb.append('*');
if (inClass == 0) sb.append(".*");
else sb.append('*');
break;
case '?':
if (inClass == 0)
sb.append('.');
else
sb.append('?');
if (inClass == 0) sb.append('.');
else sb.append('?');
break;
case '[':
inClass++;
firstIndexInClass = i+1;
firstIndexInClass = i + 1;
sb.append('[');
break;
case ']':
@ -115,15 +121,12 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
case '$':
case '@':
case '%':
if (inClass == 0 || (firstIndexInClass == i && ch == '^'))
sb.append('\\');
if (inClass == 0 || (firstIndexInClass == i && ch == '^')) sb.append('\\');
sb.append(ch);
break;
case '!':
if (firstIndexInClass == i)
sb.append('^');
else
sb.append('!');
if (firstIndexInClass == i) sb.append('^');
else sb.append('!');
break;
case '{':
inGroup++;
@ -134,10 +137,8 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
sb.append(')');
break;
case ',':
if (inGroup > 0)
sb.append('|');
else
sb.append(',');
if (inGroup > 0) sb.append('|');
else sb.append(',');
break;
default:
sb.append(ch);

View file

@ -1,20 +1,20 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonFocusExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonFocusExchangeImpl extends DaemonFocusExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
OperationMode.switchUp(OperationMode.map(msg.getMode()));
return Response.builder().build();
}
OperationMode.switchUp(OperationMode.map(msg.getMode()));
return Response.builder().build();
}
}

View file

@ -1,17 +1,19 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonModeExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonModeExchangeImpl extends DaemonModeExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg)
throws BeaconClientException {
// Wait for startup
while (OperationMode.get() == null) {
ThreadHelper.sleep(100);
@ -21,11 +23,11 @@ public class DaemonModeExchangeImpl extends DaemonModeExchange {
if (!mode.isSupported()) {
throw new BeaconClientException("Unsupported mode: " + msg.getMode().getDisplayName() + ". Supported: "
+ String.join(
", ",
OperationMode.getAll().stream()
.filter(OperationMode::isSupported)
.map(OperationMode::getId)
.toList()));
", ",
OperationMode.getAll().stream()
.filter(OperationMode::isSupported)
.map(OperationMode::getId)
.toList()));
}
OperationMode.switchToSyncIfPossible(mode);

View file

@ -1,6 +1,5 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.launcher.LauncherInput;
import io.xpipe.app.util.PlatformState;
@ -8,11 +7,14 @@ import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonOpenExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonOpenExchangeImpl extends DaemonOpenExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg)
throws BeaconServerException {
if (msg.getArguments().isEmpty()) {
if (!OperationMode.switchToSyncIfPossible(OperationMode.GUI)) {
throw new BeaconServerException(PlatformState.getLastError());

View file

@ -1,18 +1,18 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonStatusExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonStatusExchangeImpl extends DaemonStatusExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request body) {
String mode;
if (OperationMode.get() == null) {
mode = "none";

View file

@ -1,18 +1,19 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonStopExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonStopExchangeImpl extends DaemonStopExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg) {
ThreadHelper.runAsync(() -> {
ThreadHelper.sleep(1000);
OperationMode.close();

View file

@ -1,17 +1,18 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.AppProperties;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.DaemonVersionExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class DaemonVersionExchangeImpl extends DaemonVersionExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg) {
var jvmVersion = System.getProperty("java.vm.vendor") + " "
+ System.getProperty("java.vm.name") + " ("
+ System.getProperty("java.vm.version") + ")";

View file

@ -1,7 +1,5 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.beacon.BeaconSession;
import io.xpipe.app.prefs.AppPrefs;
@ -10,13 +8,16 @@ import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.HandshakeExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.util.UUID;
public class HandshakeExchangeImpl extends HandshakeExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request body)
throws BeaconClientException {
if (!checkAuth(body.getAuth())) {
throw new BeaconClientException("Authentication failed");
}

View file

@ -1,11 +1,12 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellExecExchange;
import com.sun.net.httpserver.HttpExchange;
import lombok.SneakyThrows;
import java.io.IOException;
@ -15,9 +16,13 @@ public class ShellExecExchangeImpl extends ShellExecExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
public Object handle(HttpExchange exchange, Request msg) {
var e = DataStorage.get()
.getStoreEntryIfPresent(msg.getConnection())
.orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream()
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
.findFirst();
if (existing.isEmpty()) {
throw new BeaconClientException("No shell session active for connection");
}
@ -30,6 +35,10 @@ public class ShellExecExchangeImpl extends ShellExecExchange {
command.accumulateStderr(s -> err.set(s));
exitCode = command.getExitCode();
}
return Response.builder().stdout(out.get()).stderr(err.get()).exitCode(exitCode).build();
return Response.builder()
.stdout(out.get())
.stderr(err.get())
.exitCode(exitCode)
.build();
}
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.beacon.BeaconShellSession;
import io.xpipe.app.storage.DataStorage;
@ -8,6 +7,8 @@ import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellStartExchange;
import io.xpipe.core.store.ShellStore;
import com.sun.net.httpserver.HttpExchange;
import lombok.SneakyThrows;
import java.io.IOException;
@ -16,13 +17,17 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
public Object handle(HttpExchange exchange, Request msg) {
var e = DataStorage.get()
.getStoreEntryIfPresent(msg.getConnection())
.orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
if (!(e.getStore() instanceof ShellStore s)) {
throw new BeaconClientException("Not a shell connection");
}
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
var existing = AppBeaconServer.get().getShellSessions().stream()
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
.findFirst();
if (existing.isPresent()) {
return Response.builder().build();
}

View file

@ -1,11 +1,12 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellStopExchange;
import com.sun.net.httpserver.HttpExchange;
import lombok.SneakyThrows;
import java.io.IOException;
@ -14,9 +15,13 @@ public class ShellStopExchangeImpl extends ShellStopExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
public Object handle(HttpExchange exchange, Request msg) {
var e = DataStorage.get()
.getStoreEntryIfPresent(msg.getConnection())
.orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream()
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
.findFirst();
if (existing.isPresent()) {
existing.get().getControl().close();
AppBeaconServer.get().getShellSessions().remove(existing.get());

View file

@ -1,16 +1,18 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.TerminalLaunchExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg)
throws BeaconClientException {
var r = TerminalLauncherManager.performLaunch(msg.getRequest());
return Response.builder().targetFile(r).build();
}

View file

@ -1,16 +1,18 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.TerminalWaitExchange;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
public Object handle(HttpExchange exchange, Request msg)
throws BeaconClientException, BeaconServerException {
TerminalLauncherManager.waitForCompletion(msg.getRequest());
return Response.builder().build();
}

View file

@ -8,11 +8,13 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.util.FailableRunnable;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import lombok.SneakyThrows;
import lombok.Value;
@ -46,8 +48,7 @@ public class BrowserClipboard {
}
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
var files =
data.stream().map(f -> f.toPath()).toList();
var files = data.stream().map(f -> f.toPath()).toList();
if (files.size() == 0) {
return;
}
@ -57,7 +58,8 @@ public class BrowserClipboard {
entries.add(LocalFileSystem.getLocalBrowserEntry(file));
}
currentCopyClipboard.setValue(new Instance(UUID.randomUUID(), null, entries, BrowserFileTransferMode.COPY));
currentCopyClipboard.setValue(
new Instance(UUID.randomUUID(), null, entries, BrowserFileTransferMode.COPY));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).expected().omit().handle();
}
@ -66,7 +68,8 @@ public class BrowserClipboard {
}
@SneakyThrows
public static ClipboardContent startDrag(FileSystem.FileEntry base, List<BrowserEntry> selected, BrowserFileTransferMode mode) {
public static ClipboardContent startDrag(
FileSystem.FileEntry base, List<BrowserEntry> selected, BrowserFileTransferMode mode) {
if (selected.isEmpty()) {
return null;
}

View file

@ -21,14 +21,14 @@ public class BrowserFileOpener {
key,
new BooleanScope(model.getBusy()).exclusive(),
() -> {
return entry.getFileSystem().openInput(file);
return entry.getFileSystem().openInput(file);
},
(size) -> {
if (model.isClosed()) {
return OutputStream.nullOutputStream();
}
return entry.getFileSystem().openOutput(file, size);
return entry.getFileSystem().openOutput(file, size);
},
s -> FileOpener.openWithAnyApplication(s));
}
@ -42,14 +42,14 @@ public class BrowserFileOpener {
key,
new BooleanScope(model.getBusy()).exclusive(),
() -> {
return entry.getFileSystem().openInput(file);
return entry.getFileSystem().openInput(file);
},
(size) -> {
if (model.isClosed()) {
return OutputStream.nullOutputStream();
}
return entry.getFileSystem().openOutput(file, size);
return entry.getFileSystem().openOutput(file, size);
},
s -> FileOpener.openInDefaultApplication(s));
}
@ -68,15 +68,14 @@ public class BrowserFileOpener {
key,
new BooleanScope(model.getBusy()).exclusive(),
() -> {
return entry.getFileSystem().openInput(file);
return entry.getFileSystem().openInput(file);
},
(size) -> {
if (model.isClosed()) {
return OutputStream.nullOutputStream();
}
return entry.getFileSystem().openOutput(file, size);
return entry.getFileSystem().openOutput(file, size);
},
FileOpener::openInTextEditor);
}

View file

@ -1,12 +1,12 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.util.InputHelper;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos;
@ -16,6 +16,8 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
@ -40,7 +42,8 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
button.fire();
keyEvent.consume();
});
new TooltipAugment<>("app.search", new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)).augment(button);
new TooltipAugment<>("app.search", new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN))
.augment(button);
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue && filterString.getValue() == null) {
if (button.isFocused()) {
@ -110,7 +113,7 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
button.minWidthProperty().bind(text.heightProperty());
button.maxHeightProperty().bind(text.heightProperty());
button.maxWidthProperty().bind(text.heightProperty());
return new Structure(box, (TextField) text, button);
return new Structure(box, text, button);
}
public record Structure(HBox box, TextField textField, Button toggleButton) implements CompStructure<HBox> {

View file

@ -1,6 +1,5 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.FileIconManager;
@ -13,6 +12,7 @@ import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@ -28,6 +28,8 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
@ -94,8 +96,8 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
homeButton.getStyleClass().add(Styles.LEFT_PILL);
homeButton.getStyleClass().add("path-graphic-button");
new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, () -> {
return model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false);
})
return model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false);
})
.augment(new SimpleCompStructure<>(homeButton));
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
@ -103,7 +105,8 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, this::createContextMenu)
.augment(new SimpleCompStructure<>(historyButton));
new TooltipAugment<>("history", new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN)).augment(historyButton);
new TooltipAugment<>("history", new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN))
.augment(historyButton);
var breadcrumbs = new BrowserBreadcrumbBar(model).grow(false, true);
@ -114,7 +117,8 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
event.consume();
});
breadcrumbsRegion.setFocusTraversable(false);
breadcrumbsRegion.visibleProperty()
breadcrumbsRegion
.visibleProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return !pathRegion.isFocused()

View file

@ -11,11 +11,13 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.SneakyThrows;
import java.util.List;
@ -68,8 +70,9 @@ public class BrowserOverviewComp extends SimpleComp {
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true)
.mapped(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
.getList();
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);

View file

@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
@ -18,6 +19,7 @@ import javafx.scene.control.OverrunStyle;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Value;

View file

@ -1,6 +1,5 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.file.BrowserFileListCompEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
@ -12,10 +11,13 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.util.HumanReadableFormat;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import atlantafx.base.controls.Spacer;
import lombok.EqualsAndHashCode;
import lombok.Value;
@ -57,8 +59,10 @@ public class BrowserStatusBarComp extends SimpleComp {
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
var all = HumanReadableFormat.byteCount(p.getTotal());
var name = (p.getName() != null ? " @ " + p.getName() + " " : "");
var time = p.getTotal() > 50_000_000 && p.elapsedTime().compareTo(Duration.of(200, ChronoUnit.MILLIS)) > 0 ? " | "
+ HumanReadableFormat.duration(p.expectedTimeRemaining()) : " | ...";
var time =
p.getTotal() > 50_000_000 && p.elapsedTime().compareTo(Duration.of(200, ChronoUnit.MILLIS)) > 0
? " | " + HumanReadableFormat.duration(p.expectedTimeRemaining())
: " | ...";
return transferred + " / " + all + name + time;
}
});

View file

@ -10,6 +10,7 @@ import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
@ -18,6 +19,7 @@ import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.File;
@ -46,7 +48,9 @@ public class BrowserTransferComp extends SimpleComp {
var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = new DerivedObservableList<>(syncItems, true).mapped(item -> item.getBrowserEntry()).getList();
var binding = new DerivedObservableList<>(syncItems, true)
.mapped(item -> item.getBrowserEntry())
.getList();
var list = new BrowserSelectionListComp(
binding,
entry -> Bindings.createStringBinding(
@ -57,9 +61,15 @@ public class BrowserTransferComp extends SimpleComp {
if (sourceItem.isEmpty()) {
return "?";
}
var name = entry.getModel() == null || sourceItem.get().downloadFinished().get()
? "Local"
: entry.getModel().getFileSystemModel().getName();
var name = entry.getModel() == null
|| sourceItem
.get()
.downloadFinished()
.get()
? "Local"
: entry.getModel()
.getFileSystemModel()
.getName();
return entry.getFileName() + " (" + name + ")";
},
syncAllDownloaded))

View file

@ -9,6 +9,7 @@ import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ShellTemp;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
@ -17,6 +18,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Value;
import org.apache.commons.io.FileUtils;
@ -95,7 +97,8 @@ public class BrowserTransferModel {
}
var item = new Item(null, name, entry, path);
item.progress.setValue(BrowserTransferProgress.finished(entry.getFileName(), entry.getRawFileEntry().getSize()));
item.progress.setValue(BrowserTransferProgress.finished(
entry.getFileName(), entry.getRawFileEntry().getSize()));
items.add(item);
}
} catch (Exception ex) {
@ -155,8 +158,7 @@ public class BrowserTransferModel {
Path localFile;
Property<BrowserTransferProgress> progress;
public Item(
OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) {
public Item(OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) {
this.openFileSystemModel = openFileSystemModel;
this.name = name;
this.browserEntry = browserEntry;

View file

@ -32,7 +32,7 @@ public class BrowserTransferProgress {
public Duration elapsedTime() {
var now = Instant.now();
var elapsed = Duration.between(start,now);
var elapsed = Duration.between(start, now);
return elapsed;
}

View file

@ -1,7 +1,5 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
@ -18,6 +16,7 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -31,6 +30,9 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import java.util.List;
public class BrowserWelcomeComp extends SimpleComp {
@ -65,18 +67,20 @@ public class BrowserWelcomeComp extends SimpleComp {
return new VBox(hbox);
}
var list = new DerivedObservableList<>(state.getEntries(), true).filtered(e -> {
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
if (entry.isEmpty()) {
return false;
}
var list = new DerivedObservableList<>(state.getEntries(), true)
.filtered(e -> {
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
if (entry.isEmpty()) {
return false;
}
if (!entry.get().getValidity().isUsable()) {
return false;
}
if (!entry.get().getValidity().isUsable()) {
return false;
}
return true;
}).getList();
return true;
})
.getList();
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
var headerBinding = BindingsHelper.flatMap(empty, b -> {

View file

@ -7,10 +7,12 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ThreadHelper;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;

View file

@ -33,9 +33,8 @@ public final class BrowserContextMenu extends ContextMenu {
private static List<BrowserEntry> resolveIfNeeded(BrowserAction action, List<BrowserEntry> selected) {
return action.automaticallyResolveLinks()
? selected.stream()
.map(browserEntry -> new BrowserEntry(
browserEntry.getRawFileEntry().resolved(),
browserEntry.getModel()))
.map(browserEntry ->
new BrowserEntry(browserEntry.getRawFileEntry().resolved(), browserEntry.getModel()))
.toList()
: selected;
}

View file

@ -1,7 +1,5 @@
package io.xpipe.app.browser.file;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.core.AppI18n;
@ -15,6 +13,7 @@ import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
@ -38,6 +37,9 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
@ -159,16 +161,18 @@ public final class BrowserFileListComp extends SimpleComp {
private void prepareTableShortcuts(TableView<BrowserEntry> table) {
table.setOnKeyPressed(event -> {
var selected = fileList.getSelection();
var action = BrowserAction.getFlattened(fileList.getFileSystemModel(), selected).stream().filter(
browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected) &&
browserAction.isActive(fileList.getFileSystemModel(), selected)).filter(
browserAction -> browserAction.getShortcut() != null).filter(browserAction -> browserAction.getShortcut().match(event)).findAny();
var action = BrowserAction.getFlattened(fileList.getFileSystemModel(), selected).stream()
.filter(browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected)
&& browserAction.isActive(fileList.getFileSystemModel(), selected))
.filter(browserAction -> browserAction.getShortcut() != null)
.filter(browserAction -> browserAction.getShortcut().match(event))
.findAny();
action.ifPresent(browserAction -> {
ThreadHelper.runFailableAsync(() -> {
browserAction.execute(fileList.getFileSystemModel(), selected);
});
event.consume();
});
ThreadHelper.runFailableAsync(() -> {
browserAction.execute(fileList.getFileSystemModel(), selected);
});
event.consume();
});
if (action.isPresent()) {
return;
}
@ -326,7 +330,9 @@ public final class BrowserFileListComp extends SimpleComp {
Platform.runLater(() -> {
var newItems = new ArrayList<>(fileList.getShown().getValue());
var hasModifiedDate = newItems.size() == 0 || newItems.stream().anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
var hasModifiedDate = newItems.size() == 0
|| newItems.stream()
.anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
if (!hasModifiedDate) {
table.getColumns().remove(mtimeCol);
} else {
@ -336,7 +342,10 @@ public final class BrowserFileListComp extends SimpleComp {
}
if (fileList.getFileSystemModel().getFileSystem() != null) {
var shell = fileList.getFileSystemModel().getFileSystem().getShell().orElseThrow();
var shell = fileList.getFileSystemModel()
.getFileSystem()
.getShell()
.orElseThrow();
var hasAttributes = !OsType.WINDOWS.equals(shell.getOsType());
if (!hasAttributes) {
table.getColumns().remove(modeCol);
@ -357,8 +366,10 @@ public final class BrowserFileListComp extends SimpleComp {
if (!Objects.equals(lastDir.get(), currentDirectory)) {
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
if (skin != null) {
VirtualFlow<?> flow = (VirtualFlow<?>) skin.getChildren().get(1);
ScrollBar vbar = (ScrollBar) flow.getChildrenUnmodifiable().get(2);
VirtualFlow<?> flow =
(VirtualFlow<?>) skin.getChildren().get(1);
ScrollBar vbar =
(ScrollBar) flow.getChildrenUnmodifiable().get(2);
if (vbar.getValue() != 0.0) {
table.scrollTo(0);
}
@ -541,7 +552,8 @@ public final class BrowserFileListComp extends SimpleComp {
var selected = fileList.getSelection();
// Only show one menu across all selected entries
if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) {
var cm = new BrowserContextMenu(fileList.getFileSystemModel(), getTableRow().getItem(), false);
var cm = new BrowserContextMenu(
fileList.getFileSystemModel(), getTableRow().getItem(), false);
ContextMenuHelper.toggleShow(cm, this, Side.RIGHT);
event.consume();
}

View file

@ -3,11 +3,13 @@ package io.xpipe.app.browser.file;
import io.xpipe.app.browser.BrowserClipboard;
import io.xpipe.app.browser.BrowserSelectionListComp;
import io.xpipe.core.store.FileKind;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.TableView;
import javafx.scene.image.Image;
import javafx.scene.input.*;
import lombok.Getter;
import java.io.File;
@ -154,7 +156,13 @@ public class BrowserFileListCompEntry {
var target = item != null && item.getRawFileEntry().getKind() == FileKind.DIRECTORY
? item.getRawFileEntry()
: model.getFileSystemModel().getCurrentDirectory();
model.getFileSystemModel().dropFilesIntoAsync(target, files.stream().map(browserEntry -> browserEntry.getRawFileEntry()).toList(), db.getMode());
model.getFileSystemModel()
.dropFilesIntoAsync(
target,
files.stream()
.map(browserEntry -> browserEntry.getRawFileEntry())
.toList(),
db.getMode());
event.setDropCompleted(true);
event.consume();
}
@ -180,7 +188,10 @@ public class BrowserFileListCompEntry {
var selected = model.getSelection();
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
db.setContent(BrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected, event.isAltDown() ? BrowserFileTransferMode.MOVE : BrowserFileTransferMode.NORMAL));
db.setContent(BrowserClipboard.startDrag(
model.getFileSystemModel().getCurrentDirectory(),
selected,
event.isAltDown() ? BrowserFileTransferMode.MOVE : BrowserFileTransferMode.NORMAL));
Image image = BrowserSelectionListComp.snapshot(selected);
db.setDragView(image, -20, 15);

View file

@ -5,12 +5,14 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.util.ArrayList;
@ -54,7 +56,8 @@ public final class BrowserFileListModel {
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
try (var s = newFiles) {
var l = s.filter(entry -> entry != null).map(entry -> new BrowserEntry(entry, this))
var l = s.filter(entry -> entry != null)
.map(entry -> new BrowserEntry(entry, this))
.toList();
all.setValue(l);
refreshShown();
@ -90,9 +93,7 @@ public final class BrowserFileListModel {
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
var comp = comparatorProperty.getValue();
Comparator<BrowserEntry> us = comp != null
? dirsFirst.thenComparing(comp)
: dirsFirst;
Comparator<BrowserEntry> us = comp != null ? dirsFirst.thenComparing(comp) : dirsFirst;
return us;
}
@ -120,7 +121,11 @@ public final class BrowserFileListModel {
try {
fileSystemModel.getFileSystem().move(fullPath, newFullPath);
fileSystemModel.refresh();
var b = all.getValue().stream().filter(browserEntry -> browserEntry.getRawFileEntry().getPath().equals(newFullPath)).findFirst().orElse(old);
var b = all.getValue().stream()
.filter(browserEntry ->
browserEntry.getRawFileEntry().getPath().equals(newFullPath))
.findFirst()
.orElse(old);
return b;
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();

View file

@ -36,8 +36,7 @@ public class BrowserFileOverviewComp extends SimpleComp {
var icon = BrowserIcons.createIcon(entry);
var graphic = new HorizontalComp(List.of(
icon,
new BrowserQuickAccessButtonComp(
() -> new BrowserEntry(entry, model.getFileList()), model)));
new BrowserQuickAccessButtonComp(() -> new BrowserEntry(entry, model.getFileList()), model)));
var l = new Button(entry.getPath(), graphic.createRegion());
l.setGraphicTextGap(1);
l.setOnAction(event -> {

View file

@ -1,7 +1,6 @@
package io.xpipe.app.browser.file;
public enum BrowserFileTransferMode {
NORMAL,
COPY,
MOVE

View file

@ -28,9 +28,12 @@ public class BrowserFileTransferOperation {
BrowserAlerts.FileConflictChoice lastConflictChoice;
public BrowserFileTransferOperation(FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode transferMode, boolean checkConflicts,
Consumer<BrowserTransferProgress> progress
) {
public BrowserFileTransferOperation(
FileSystem.FileEntry target,
List<FileSystem.FileEntry> files,
BrowserFileTransferMode transferMode,
boolean checkConflicts,
Consumer<BrowserTransferProgress> progress) {
this.target = target;
this.files = files;
this.transferMode = transferMode;
@ -38,7 +41,12 @@ public class BrowserFileTransferOperation {
this.progress = progress;
}
public static BrowserFileTransferOperation ofLocal(FileSystem.FileEntry target, List<Path> files, BrowserFileTransferMode transferMode, boolean checkConflicts, Consumer<BrowserTransferProgress> progress) {
public static BrowserFileTransferOperation ofLocal(
FileSystem.FileEntry target,
List<Path> files,
BrowserFileTransferMode transferMode,
boolean checkConflicts,
Consumer<BrowserTransferProgress> progress) {
var entries = files.stream()
.map(path -> {
if (!Files.exists(path)) {
@ -60,11 +68,7 @@ public class BrowserFileTransferOperation {
this.progress.accept(progress);
}
private boolean handleChoice(
FileSystem fileSystem,
String target,
boolean multiple)
throws Exception {
private boolean handleChoice(FileSystem fileSystem, String target, boolean multiple) throws Exception {
if (lastConflictChoice == BrowserAlerts.FileConflictChoice.CANCEL) {
return false;
}
@ -101,15 +105,15 @@ public class BrowserFileTransferOperation {
return true;
}
public void execute()
throws Exception {
public void execute() throws Exception {
if (files.isEmpty()) {
updateProgress(BrowserTransferProgress.empty());
return;
}
var same = files.getFirst().getFileSystem().equals(target.getFileSystem());
var doesMove = transferMode == BrowserFileTransferMode.MOVE || (same && transferMode == BrowserFileTransferMode.NORMAL);
var doesMove = transferMode == BrowserFileTransferMode.MOVE
|| (same && transferMode == BrowserFileTransferMode.NORMAL);
if (doesMove) {
if (!BrowserAlerts.showMoveAlert(files, target)) {
return;
@ -132,8 +136,7 @@ public class BrowserFileTransferOperation {
}
}
private void handleSingleOnSameFileSystem(FileSystem.FileEntry source)
throws Exception {
private void handleSingleOnSameFileSystem(FileSystem.FileEntry source) throws Exception {
// Prevent dropping directory into itself
if (source.getPath().equals(target.getPath())) {
return;
@ -163,8 +166,7 @@ public class BrowserFileTransferOperation {
}
}
private void handleSingleAcrossFileSystems(FileSystem.FileEntry source)
throws Exception {
private void handleSingleAcrossFileSystems(FileSystem.FileEntry source) throws Exception {
if (target.getKind() != FileKind.DIRECTORY) {
throw new IllegalStateException("Target " + target.getPath() + " is not a directory");
}
@ -214,9 +216,7 @@ public class BrowserFileTransferOperation {
} else if (sourceFile.getKind() == FileKind.FILE) {
if (checkConflicts
&& !handleChoice(
target.getFileSystem(),
targetFile,
files.size() > 1 || flatFiles.size() > 1)) {
target.getFileSystem(), targetFile, files.size() > 1 || flatFiles.size() > 1)) {
continue;
}

View file

@ -4,6 +4,7 @@ import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.util.InputHelper;
import javafx.scene.layout.Region;
import java.util.function.Supplier;

View file

@ -8,6 +8,7 @@ import io.xpipe.app.util.InputHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Side;
@ -19,6 +20,7 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import lombok.Getter;
import java.util.ArrayList;
@ -139,8 +141,10 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
this.browserEntry = browserEntry;
this.menu = new Menu(
// Use original name, not the link target
browserEntry.getRawFileEntry().getName(), PrettyImageHelper.ofFixedRasterized(
FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24).createRegion());
browserEntry.getRawFileEntry().getName(),
PrettyImageHelper.ofFixedRasterized(
FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24)
.createRegion());
createMenu();
addInputListeners();
}
@ -243,7 +247,8 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
if (contextMenu != null) {
contextMenu.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
keyBasedNavigation = true;
if (event.getCode().equals(KeyCode.SPACE) || event.getCode().equals(KeyCode.ENTER)) {
if (event.getCode().equals(KeyCode.SPACE)
|| event.getCode().equals(KeyCode.ENTER)) {
expandBrowserActionMenuKey = true;
} else {
expandBrowserActionMenuKey = false;

View file

@ -66,7 +66,8 @@ public class FileSystemHelper {
}
}
public static String resolveDirectoryPath(OpenFileSystemModel model, String path, boolean allowRewrite) throws Exception {
public static String resolveDirectoryPath(OpenFileSystemModel model, String path, boolean allowRewrite)
throws Exception {
if (path == null) {
return null;
}
@ -97,7 +98,8 @@ public class FileSystemHelper {
return FileNames.toDirectory(resolved);
}
public static void validateDirectoryPath(OpenFileSystemModel model, String path, boolean verifyExists) throws Exception {
public static void validateDirectoryPath(OpenFileSystemModel model, String path, boolean verifyExists)
throws Exception {
if (path == null) {
return;
}

View file

@ -36,6 +36,6 @@ public class LocalFileSystem {
public static BrowserEntry getLocalBrowserEntry(Path file) throws Exception {
var e = getLocalFileEntry(file);
return new BrowserEntry(e,null);
return new BrowserEntry(e, null);
}
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.browser.fs;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.BrowserFilterComp;
import io.xpipe.app.browser.BrowserNavBar;
import io.xpipe.app.browser.BrowserOverviewComp;
@ -17,6 +16,7 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.util.InputHelper;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
@ -28,6 +28,8 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
@ -54,13 +56,15 @@ public class OpenFileSystemComp extends SimpleComp {
var root = new VBox();
var overview = new Button(null, new FontIcon("mdi2m-monitor"));
overview.setOnAction(e -> model.cdAsync(null));
new TooltipAugment<>("overview", new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN)).augment(overview);
new TooltipAugment<>("overview", new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN))
.augment(overview);
overview.disableProperty().bind(model.getInOverview());
overview.setAccessibleText("System overview");
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN), true, keyEvent -> {
overview.fire();
keyEvent.consume();
});
InputHelper.onKeyCombination(
root, new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN), true, keyEvent -> {
overview.fire();
keyEvent.consume();
});
var backBtn = BrowserAction.byId("back", model, List.of()).toButton(root, model, List.of());
var forthBtn = BrowserAction.byId("forward", model, List.of()).toButton(root, model, List.of());
@ -95,12 +99,12 @@ public class OpenFileSystemComp extends SimpleComp {
refreshBtn,
terminalBtn,
menuButton);
squaredSize(navBar.get(),overview,true);
squaredSize(navBar.get(),backBtn,true);
squaredSize(navBar.get(),forthBtn,true);
squaredSize(navBar.get(),refreshBtn,true);
squaredSize(navBar.get(),terminalBtn,true);
squaredSize(navBar.get(),menuButton,false);
squaredSize(navBar.get(), overview, true);
squaredSize(navBar.get(), backBtn, true);
squaredSize(navBar.get(), forthBtn, true);
squaredSize(navBar.get(), refreshBtn, true);
squaredSize(navBar.get(), terminalBtn, true);
squaredSize(navBar.get(), menuButton, false);
var content = createFileListContent();
root.getChildren().addAll(topBar, content);
@ -111,26 +115,30 @@ public class OpenFileSystemComp extends SimpleComp {
}
});
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN), true, keyEvent -> {
filter.toggleButton().fire();
filter.textField().requestFocus();
keyEvent.consume();
});
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN), true, keyEvent -> {
navBar.textField().requestFocus();
keyEvent.consume();
});
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN), true, keyEvent -> {
navBar.historyButton().fire();
keyEvent.consume();
});
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.UP, KeyCombination.ALT_DOWN), true, keyEvent -> {
var p = model.getCurrentParentDirectory();
if (p != null) {
model.cdAsync(p.getPath());
}
keyEvent.consume();
});
InputHelper.onKeyCombination(
root, new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN), true, keyEvent -> {
filter.toggleButton().fire();
filter.textField().requestFocus();
keyEvent.consume();
});
InputHelper.onKeyCombination(
root, new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN), true, keyEvent -> {
navBar.textField().requestFocus();
keyEvent.consume();
});
InputHelper.onKeyCombination(
root, new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN), true, keyEvent -> {
navBar.historyButton().fire();
keyEvent.consume();
});
InputHelper.onKeyCombination(
root, new KeyCodeCombination(KeyCode.UP, KeyCombination.ALT_DOWN), true, keyEvent -> {
var p = model.getCurrentParentDirectory();
if (p != null) {
model.cdAsync(p.getPath());
}
keyEvent.consume();
});
return root;
}

View file

@ -24,8 +24,10 @@ import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellOpenFunction;
import io.xpipe.core.store.*;
import io.xpipe.core.util.FailableConsumer;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import lombok.Getter;
import lombok.SneakyThrows;
@ -345,7 +347,8 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
}
startIfNeeded();
var op = BrowserFileTransferOperation.ofLocal(entry, files,BrowserFileTransferMode.COPY,true, progress::setValue);
var op = BrowserFileTransferOperation.ofLocal(
entry, files, BrowserFileTransferMode.COPY, true, progress::setValue);
op.execute();
refreshSync();
});
@ -353,8 +356,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
}
public void dropFilesIntoAsync(
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode mode
) {
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode mode) {
// We don't have to do anything in this case
if (files.isEmpty()) {
return;
@ -367,7 +369,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
}
startIfNeeded();
var op = new BrowserFileTransferOperation(target, files, mode,true, progress::setValue);
var op = new BrowserFileTransferOperation(target, files, mode, true, progress::setValue);
op.execute();
refreshSync();
});

View file

@ -10,10 +10,12 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import lombok.Setter;

View file

@ -11,8 +11,10 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.Getter;
import java.util.ArrayList;

View file

@ -9,8 +9,8 @@ import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.InputHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;

View file

@ -3,8 +3,8 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;

View file

@ -43,9 +43,11 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
.map(menuItem -> menuItem.getGraphic().visibleProperty())
.toList();
button.visibleProperty()
.bind(Bindings.createBooleanBinding(() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
}, l.toArray(ObservableValue[]::new)));
.bind(Bindings.createBooleanBinding(
() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
},
l.toArray(ObservableValue[]::new)));
var graphic = new FontIcon("mdi2c-chevron-double-down");
button.fontProperty().subscribe(c -> {

View file

@ -1,11 +1,11 @@
package io.xpipe.app.comp.base;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.TextAreaComp;
import io.xpipe.app.util.FileOpener;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
@ -13,6 +13,8 @@ import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import atlantafx.base.theme.Styles;
import lombok.Builder;
import lombok.Value;
@ -50,21 +52,23 @@ public class IntegratedTextAreaComp extends Comp<IntegratedTextAreaComp.Structur
@Override
public Structure createBase() {
var fileDrop = new FileDropOverlayComp<>(new Comp<TextAreaStructure>() {
@Override
public TextAreaStructure createBase() {
var textArea = new TextAreaComp(value, lazy).createStructure();
var copyButton = createOpenButton();
var pane = new AnchorPane(copyButton);
pane.setPickOnBounds(false);
AnchorPane.setTopAnchor(copyButton, 10.0);
AnchorPane.setRightAnchor(copyButton, 10.0);
var fileDrop = new FileDropOverlayComp<>(
new Comp<TextAreaStructure>() {
@Override
public TextAreaStructure createBase() {
var textArea = new TextAreaComp(value, lazy).createStructure();
var copyButton = createOpenButton();
var pane = new AnchorPane(copyButton);
pane.setPickOnBounds(false);
AnchorPane.setTopAnchor(copyButton, 10.0);
AnchorPane.setRightAnchor(copyButton, 10.0);
var c = new StackPane();
c.getChildren().addAll(textArea.get(), pane);
return new TextAreaStructure(c, textArea.getTextArea());
}
}, paths -> value.setValue(Files.readString(paths.getFirst())));
var c = new StackPane();
c.getChildren().addAll(textArea.get(), pane);
return new TextAreaStructure(c, textArea.getTextArea());
}
},
paths -> value.setValue(Files.readString(paths.getFirst())));
var struc = fileDrop.createStructure();
return new Structure(struc.get(), struc.getCompStructure().getTextArea());
}

View file

@ -5,6 +5,7 @@ import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

View file

@ -1,10 +1,10 @@
package io.xpipe.app.comp.base;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.util.FileOpener;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.scene.control.Button;
@ -12,6 +12,8 @@ import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import atlantafx.base.theme.Styles;
import lombok.Builder;
import lombok.Value;
@ -20,20 +22,15 @@ public class MarkdownEditorComp extends Comp<MarkdownEditorComp.Structure> {
private final Property<String> value;
private final String identifier;
public MarkdownEditorComp(
Property<String> value, String identifier) {
public MarkdownEditorComp(Property<String> value, String identifier) {
this.value = value;
this.identifier = identifier;
}
private Button createOpenButton() {
return new IconButtonComp(
"mdal-edit",
() -> FileOpener.openString(
identifier + ".md",
this,
value.getValue(),
(s) -> {
"mdal-edit",
() -> FileOpener.openString(identifier + ".md", this, value.getValue(), (s) -> {
Platform.runLater(() -> value.setValue(s));
}))
.styleClass("edit-button")

View file

@ -1,13 +1,11 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.ModalPane;
import atlantafx.base.layout.ModalBox;
import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.geometry.Insets;
@ -19,6 +17,10 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.ModalPane;
import atlantafx.base.layout.ModalBox;
import atlantafx.base.theme.Styles;
import lombok.Value;
public class ModalOverlayComp extends SimpleComp {

View file

@ -159,7 +159,10 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
}
{
var b = new IconButtonComp("mdi2c-code-json", () -> Hyperlinks.open("http://localhost:" + AppPrefs.get().httpServerPort().getValue()))
var b = new IconButtonComp(
"mdi2c-code-json",
() -> Hyperlinks.open("http://localhost:"
+ AppPrefs.get().httpServerPort().getValue()))
.tooltipKey("api")
.apply(simpleBorders)
.accessibleTextKey("api");

View file

@ -8,12 +8,14 @@ import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import lombok.Setter;
@ -34,7 +36,11 @@ public class StoreToggleComp extends SimpleComp {
private ObservableValue<Boolean> customVisibility = new SimpleBooleanProperty(true);
public static <T extends DataStore> StoreToggleComp simpleToggle(
String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
String nameKey,
ObservableValue<LabelGraphic> graphic,
StoreSection section,
Function<T, Boolean> initial,
BiConsumer<T, Boolean> setter) {
return new StoreToggleComp(
nameKey,
graphic,
@ -49,8 +55,9 @@ public class StoreToggleComp extends SimpleComp {
public static <T extends DataStore> StoreToggleComp enableToggle(
String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
var val = new SimpleBooleanProperty();
ObservableValue<LabelGraphic> g = val.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2p-power"));
ObservableValue<LabelGraphic> g = val.map(aBoolean -> aBoolean
? new LabelGraphic.IconGraphic("mdi2c-circle-slice-8")
: new LabelGraphic.IconGraphic("mdi2p-power"));
var t = new StoreToggleComp(
nameKey,
g,
@ -68,17 +75,29 @@ public class StoreToggleComp extends SimpleComp {
}
public static <T extends DataStore> StoreToggleComp childrenToggle(
String nameKey, boolean graphic, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
String nameKey,
boolean graphic,
StoreSection section,
Function<T, Boolean> initial,
BiConsumer<T, Boolean> setter) {
var val = new SimpleBooleanProperty();
ObservableValue<LabelGraphic> g = graphic ? val.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2c-circle-half-full")) : null;
var t = new StoreToggleComp(nameKey, g, section,
new SimpleBooleanProperty(initial.apply(section.getWrapper().getEntry().getStore().asNeeded())), v -> {
Platform.runLater(() -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
StoreViewState.get().toggleStoreListUpdate();
});
});
ObservableValue<LabelGraphic> g = graphic
? val.map(aBoolean -> aBoolean
? new LabelGraphic.IconGraphic("mdi2c-circle-slice-8")
: new LabelGraphic.IconGraphic("mdi2c-circle-half-full"))
: null;
var t = new StoreToggleComp(
nameKey,
g,
section,
new SimpleBooleanProperty(
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
v -> {
Platform.runLater(() -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
StoreViewState.get().toggleStoreListUpdate();
});
});
t.tooltipKey("showAllChildren");
t.value.subscribe((newValue) -> {
val.set(newValue);
@ -86,7 +105,12 @@ public class StoreToggleComp extends SimpleComp {
return t;
}
public StoreToggleComp(String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, boolean initial, Consumer<Boolean> onChange) {
public StoreToggleComp(
String nameKey,
ObservableValue<LabelGraphic> graphic,
StoreSection section,
boolean initial,
Consumer<Boolean> onChange) {
this.nameKey = nameKey;
this.graphic = graphic;
this.section = section;
@ -94,7 +118,12 @@ public class StoreToggleComp extends SimpleComp {
this.onChange = onChange;
}
public StoreToggleComp(String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, BooleanProperty initial, Consumer<Boolean> onChange) {
public StoreToggleComp(
String nameKey,
ObservableValue<LabelGraphic> graphic,
StoreSection section,
BooleanProperty initial,
Consumer<Boolean> onChange) {
this.nameKey = nameKey;
this.graphic = graphic;
this.section = section;

View file

@ -1,15 +1,17 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.ToggleSwitch;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import atlantafx.base.controls.ToggleSwitch;
import lombok.EqualsAndHashCode;
import lombok.Value;
@ -44,8 +46,9 @@ public class ToggleSwitchComp extends SimpleComp {
s.textProperty().bind(PlatformThread.sync(name));
}
if (graphic != null) {
s.graphicProperty().bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
s.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-graphic"),true);
s.graphicProperty()
.bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
s.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-graphic"), true);
}
return s;
}

View file

@ -39,8 +39,7 @@ public class StoreCreationMenu {
"addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script"));
menu.getItems()
.add(category(
"addService", "mdi2c-cloud-braces", DataStoreProvider.CreationCategory.SERVICE, null));
.add(category("addService", "mdi2c-cloud-braces", DataStoreProvider.CreationCategory.SERVICE, null));
menu.getItems()
.add(category(

View file

@ -1,7 +1,5 @@
package io.xpipe.app.comp.store;
import atlantafx.base.layout.InputGroup;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.*;
import io.xpipe.app.ext.ActionProvider;
@ -22,6 +20,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableDoubleValue;
@ -34,6 +33,9 @@ import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import atlantafx.base.layout.InputGroup;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files;
@ -207,9 +209,11 @@ public abstract class StoreEntryComp extends SimpleComp {
protected Region createButtonBar() {
var list = new DerivedObservableList<>(wrapper.getActionProviders(), false);
var buttons = list.mapped(actionProvider -> {
var button = buildButton(actionProvider);
return button != null ? button.createRegion() : null;
}).filtered(region -> region != null).getList();
var button = buildButton(actionProvider);
return button != null ? button.createRegion() : null;
})
.filtered(region -> region != null)
.getList();
var ig = new InputGroup();
Runnable update = () -> {
@ -236,28 +240,31 @@ public abstract class StoreEntryComp extends SimpleComp {
return null;
}
var button =
new IconButtonComp(cs.getIcon(wrapper.getEntry().ref()), leaf != null ? () -> {
ThreadHelper.runFailableAsync(() -> {
wrapper.runAction(leaf.createAction(wrapper.getEntry().ref()), leaf.showBusy());
});
} : null);
var button = new IconButtonComp(
cs.getIcon(wrapper.getEntry().ref()),
leaf != null
? () -> {
ThreadHelper.runFailableAsync(() -> {
wrapper.runAction(
leaf.createAction(wrapper.getEntry().ref()), leaf.showBusy());
});
}
: null);
if (branch != null) {
button.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY,keyEvent -> false,() -> {
var cm = ContextMenuHelper.create();
branch.getChildren().forEach(childProvider -> {
var menu = buildMenuItemForAction(childProvider);
if (menu != null) {
cm.getItems().add(menu);
}
});
return cm;
}));
button.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
var cm = ContextMenuHelper.create();
branch.getChildren().forEach(childProvider -> {
var menu = buildMenuItemForAction(childProvider);
if (menu != null) {
cm.getItems().add(menu);
}
});
return cm;
}));
}
button.accessibleText(
cs.getName(wrapper.getEntry().ref()).getValue());
button.apply(new TooltipAugment<>(
cs.getName(wrapper.getEntry().ref()), null));
button.accessibleText(cs.getName(wrapper.getEntry().ref()).getValue());
button.apply(new TooltipAugment<>(cs.getName(wrapper.getEntry().ref()), null));
return button;
}
@ -284,7 +291,9 @@ public abstract class StoreEntryComp extends SimpleComp {
continue;
}
if (p.getLeafDataStoreCallSite() != null && p.getLeafDataStoreCallSite().isSystemAction() && !hasSep) {
if (p.getLeafDataStoreCallSite() != null
&& p.getLeafDataStoreCallSite().isSystemAction()
&& !hasSep) {
if (contextMenu.getItems().size() > 0) {
contextMenu.getItems().add(new SeparatorMenuItem());
}
@ -312,8 +321,8 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(browse);
var copyId = new MenuItem(AppI18n.get("copyId"), new FontIcon("mdi2c-content-copy"));
copyId.setOnAction(
event -> ClipboardHelper.copyText(wrapper.getEntry().getUuid().toString()));
copyId.setOnAction(event ->
ClipboardHelper.copyText(wrapper.getEntry().getUuid().toString()));
contextMenu.getItems().add(copyId);
}
@ -343,12 +352,16 @@ public abstract class StoreEntryComp extends SimpleComp {
.getList()
.forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem();
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
m.textProperty()
.setValue(" ".repeat(storeCategoryWrapper.getDepth())
+ storeCategoryWrapper.getName().getValue());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
if (storeCategoryWrapper.getParent() == null || storeCategoryWrapper.equals(wrapper.getCategory().getValue())) {
if (storeCategoryWrapper.getParent() == null
|| storeCategoryWrapper.equals(
wrapper.getCategory().getValue())) {
m.setDisable(true);
}
@ -386,10 +399,17 @@ public abstract class StoreEntryComp extends SimpleComp {
section.get().getAllChildren().getList().forEach(other -> {
var ow = other.getWrapper();
var op = ow.getEntry().getProvider();
MenuItem m = new MenuItem(ow.getName().getValue(),
op != null ? PrettyImageHelper.ofFixedSizeSquare(op.getDisplayIconFileName(ow.getEntry().getStore()),
16).createRegion() : null);
if (other.getWrapper().equals(wrapper) || ow.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
MenuItem m = new MenuItem(
ow.getName().getValue(),
op != null
? PrettyImageHelper.ofFixedSizeSquare(
op.getDisplayIconFileName(
ow.getEntry().getStore()),
16)
.createRegion()
: null);
if (other.getWrapper().equals(wrapper)
|| ow.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
m.setDisable(true);
}
m.setOnAction(event -> {
@ -436,9 +456,7 @@ public abstract class StoreEntryComp extends SimpleComp {
: new MenuItem(null, new FontIcon(icon));
var proRequired = p.getProFeatureId() != null
&& !LicenseProvider.get()
.getFeature(p.getProFeatureId())
.isSupported();
&& !LicenseProvider.get().getFeature(p.getProFeatureId()).isSupported();
if (proRequired) {
item.setDisable(true);
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
@ -448,7 +466,9 @@ public abstract class StoreEntryComp extends SimpleComp {
Menu menu = item instanceof Menu m ? m : null;
if (branch != null) {
var items = branch.getChildren().stream().map(c -> buildMenuItemForAction(c)).toList();
var items = branch.getChildren().stream()
.map(c -> buildMenuItemForAction(c))
.toList();
menu.getItems().addAll(items);
return menu;
} else if (leaf.canLinkTo()) {
@ -462,18 +482,16 @@ public abstract class StoreEntryComp extends SimpleComp {
menu.getItems().add(run);
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
var url = "xpipe://action/" + p.getId() + "/"
+ wrapper.getEntry().getUuid();
var url = "xpipe://action/" + p.getId() + "/" + wrapper.getEntry().getUuid();
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
sc.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
DesktopShortcuts.create(
url,
wrapper.nameProperty().getValue() + " ("
+ p
.getLeafDataStoreCallSite()
.getName(wrapper.getEntry().ref())
.getValue() + ")");
+ p.getLeafDataStoreCallSite()
.getName(wrapper.getEntry().ref())
.getValue() + ")");
});
});
menu.getItems().add(sc);

View file

@ -18,8 +18,14 @@ public class StoreEntryListComp extends SimpleComp {
private Comp<?> createList() {
var content = new ListBoxViewComp<>(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList(),
StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList(),
StoreViewState.get()
.getCurrentTopLevelSection()
.getShownChildren()
.getList(),
StoreViewState.get()
.getCurrentTopLevelSection()
.getAllChildren()
.getList(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
@ -50,16 +56,21 @@ public class StoreEntryListComp extends SimpleComp {
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
map.put(
createList(),
Bindings.not(Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
Bindings.not(Bindings.isEmpty(StoreViewState.get()
.getCurrentTopLevelSection()
.getShownChildren()
.getList())));
map.put(new StoreIntroComp(), showIntro);
map.put(
new StoreNotFoundComp(),
Bindings.and(
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries().getList())),
Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
Bindings.not(Bindings.isEmpty(
StoreViewState.get().getAllEntries().getList())),
Bindings.isEmpty(StoreViewState.get()
.getCurrentTopLevelSection()
.getShownChildren()
.getList())));
return new MultiContentComp(map).createRegion();
}
}

View file

@ -10,6 +10,7 @@ import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -23,6 +24,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListStatusComp extends SimpleComp {
@ -55,17 +57,28 @@ public class StoreEntryListStatusComp extends SimpleComp {
label.textProperty().bind(name);
label.getStyleClass().add("name");
var all = StoreViewState.get().getAllEntries().filtered(
storeEntryWrapper -> {
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
// Sadly the all binding does not update when the individual visibility of entries changes
// But it is good enough.
var showProvider = !storeEntryWrapper.getEntry().getValidity().isUsable() ||
storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
return inRootCategory && showProvider;
},
StoreViewState.get().getActiveCategory());
var all = StoreViewState.get()
.getAllEntries()
.filtered(
storeEntryWrapper -> {
var rootCategory =
storeEntryWrapper.getCategory().getValue().getRoot();
var inRootCategory = StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(rootCategory);
// Sadly the all binding does not update when the individual visibility of entries changes
// But it is good enough.
var showProvider =
!storeEntryWrapper.getEntry().getValidity().isUsable()
|| storeEntryWrapper
.getEntry()
.getProvider()
.shouldShow(storeEntryWrapper);
return inRootCategory && showProvider;
},
StoreViewState.get().getActiveCategory());
var shownList = all.filtered(
storeEntryWrapper -> {
return storeEntryWrapper.matchesFilter(

View file

@ -9,8 +9,10 @@ import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import lombok.Getter;
import java.time.Duration;
@ -26,7 +28,8 @@ public class StoreEntryWrapper {
private final BooleanProperty disabled = new SimpleBooleanProperty();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final Property<DataStoreEntry.Validity> validity = new SimpleObjectProperty<>();
private final ListProperty<ActionProvider> actionProviders = new SimpleListProperty<>(FXCollections.observableArrayList());
private final ListProperty<ActionProvider> actionProviders =
new SimpleListProperty<>(FXCollections.observableArrayList());
private final Property<ActionProvider> defaultActionProvider = new SimpleObjectProperty<>();
private final BooleanProperty deletable = new SimpleBooleanProperty();
private final BooleanProperty expanded = new SimpleBooleanProperty();
@ -50,8 +53,8 @@ public class StoreEntryWrapper {
.getApplicableClass()
.isAssignableFrom(entry.getStore().getClass());
})
.sorted(Comparator.comparing(
actionProvider -> actionProvider.getLeafDataStoreCallSite().isSystemAction()))
.sorted(Comparator.comparing(actionProvider ->
actionProvider.getLeafDataStoreCallSite().isSystemAction()))
.forEach(dataStoreActionProvider -> {
actionProviders.add(dataStoreActionProvider);
});
@ -67,7 +70,7 @@ public class StoreEntryWrapper {
public void orderBefore(StoreEntryWrapper other) {
ThreadHelper.runAsync(() -> {
DataStorage.get().orderBefore(getEntry(),other.getEntry());
DataStorage.get().orderBefore(getEntry(), other.getEntry());
});
}
@ -155,10 +158,11 @@ public class StoreEntryWrapper {
defaultActionProvider.setValue(null);
} else {
var defaultProvider = ActionProvider.ALL.stream()
.filter(e -> entry.getStore() != null && e.getDefaultDataStoreCallSite() != null
.filter(e -> entry.getStore() != null
&& e.getDefaultDataStoreCallSite() != null
&& e.getDefaultDataStoreCallSite()
.getApplicableClass()
.isAssignableFrom(entry.getStore().getClass())
.getApplicableClass()
.isAssignableFrom(entry.getStore().getClass())
&& e.getDefaultDataStoreCallSite().isApplicable(entry.ref()))
.findFirst()
.orElse(null);
@ -167,11 +171,10 @@ public class StoreEntryWrapper {
try {
var newProviders = ActionProvider.ALL.stream()
.filter(dataStoreActionProvider -> {
return showActionProvider(dataStoreActionProvider);
return showActionProvider(dataStoreActionProvider);
})
.sorted(Comparator.comparing(
actionProvider -> actionProvider.getLeafDataStoreCallSite() != null &&
actionProvider.getLeafDataStoreCallSite().isSystemAction()))
.sorted(Comparator.comparing(actionProvider -> actionProvider.getLeafDataStoreCallSite() != null
&& actionProvider.getLeafDataStoreCallSite().isSystemAction()))
.toList();
if (!actionProviders.equals(newProviders)) {
actionProviders.setAll(newProviders);
@ -186,14 +189,15 @@ public class StoreEntryWrapper {
var leaf = p.getLeafDataStoreCallSite();
if (leaf != null) {
return (entry.getValidity().isUsable() || (!leaf.requiresValidStore() && entry.getProvider() != null))
&& leaf.getApplicableClass().isAssignableFrom(entry.getStore().getClass())
&& leaf
.isApplicable(entry.ref());
&& leaf.getApplicableClass()
.isAssignableFrom(entry.getStore().getClass())
&& leaf.isApplicable(entry.ref());
}
var branch = p.getBranchDataStoreCallSite();
if (branch != null && entry.getStore() != null && branch.getApplicableClass().isAssignableFrom(entry.getStore().getClass())) {
if (branch != null
&& entry.getStore() != null
&& branch.getApplicableClass().isAssignableFrom(entry.getStore().getClass())) {
return branch.getChildren().stream().anyMatch(child -> {
return showActionProvider(child);
});
@ -223,7 +227,7 @@ public class StoreEntryWrapper {
entry.notifyUpdate(true, false);
if (found != null) {
var act = found.getDefaultDataStoreCallSite().createAction(entry.ref());
runAction(act,found.getDefaultDataStoreCallSite().showBusy());
runAction(act, found.getDefaultDataStoreCallSite().showBusy());
} else {
entry.setExpanded(!entry.isExpanded());
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.store;
import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
@ -8,6 +7,7 @@ import io.xpipe.app.fxcomps.impl.PrettySvgComp;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ScanAlert;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -17,6 +17,8 @@ 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 StoreIntroComp extends SimpleComp {
@ -58,7 +60,6 @@ public class StoreIntroComp extends SimpleComp {
return v;
}
private Region createImportIntro() {
var title = new Label();
title.textProperty().bind(AppI18n.observable("importConnectionsTitle"));

View file

@ -4,8 +4,8 @@ import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppActionLinkDetector;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.util.InputHelper;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
@ -24,10 +24,11 @@ public class StoreLayoutComp extends SimpleComp {
struc.getLeft().setMinWidth(260);
struc.getLeft().setMaxWidth(500);
struc.get().getStyleClass().add("store-layout");
InputHelper.onKeyCombination(struc.get(),new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN), true, keyEvent -> {
AppActionLinkDetector.detectOnPaste();
keyEvent.consume();
});
InputHelper.onKeyCombination(
struc.get(), new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN), true, keyEvent -> {
AppActionLinkDetector.detectOnPaste();
keyEvent.consume();
});
return struc.get();
}
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.store;
import atlantafx.base.controls.Popover;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.comp.base.MarkdownEditorComp;
@ -11,12 +10,15 @@ import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import atlantafx.base.controls.Popover;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@ -24,7 +26,9 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
private final StoreEntryWrapper wrapper;
public StoreNotesComp(StoreEntryWrapper wrapper) {this.wrapper = wrapper;}
public StoreNotesComp(StoreEntryWrapper wrapper) {
this.wrapper = wrapper;
}
@Override
public Structure createBase() {
@ -36,7 +40,8 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
.styleClass("notes-button")
.grow(false, true)
.hide(BindingsHelper.map(n, s -> s.getCommited() == null && s.getCurrent() == null))
.createStructure().get();
.createStructure()
.get();
button.prefWidthProperty().bind(button.heightProperty());
var prop = new SimpleStringProperty(n.getValue().getCurrent());
@ -73,12 +78,16 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
private Popover createPopover(AtomicReference<Popover> ref, Property<String> prop) {
var n = wrapper.getNotes();
var md = new MarkdownEditorComp(prop, "notes-" + wrapper.getName().getValue()).prefWidth(600).prefHeight(600).createStructure();
var md = new MarkdownEditorComp(prop, "notes-" + wrapper.getName().getValue())
.prefWidth(600)
.prefHeight(600)
.createStructure();
var dialog = new DialogComp() {
@Override
protected void finish() {
n.setValue(new StoreNotes(n.getValue().getCurrent(), n.getValue().getCurrent()));
n.setValue(
new StoreNotes(n.getValue().getCurrent(), n.getValue().getCurrent()));
ref.get().hide();
}
@ -90,8 +99,9 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
@Override
public Comp<?> bottom() {
return new ButtonComp(AppI18n.observable("delete"), () -> {
n.setValue(new StoreNotes(null, null));
}).hide(BindingsHelper.map(n, v -> v.getCommited() == null));
n.setValue(new StoreNotes(null, null));
})
.hide(BindingsHelper.map(n, v -> v.getCommited() == null));
}
@Override
@ -114,7 +124,8 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
popover.setTitle(wrapper.getName().getValue());
popover.showingProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
n.setValue(new StoreNotes(n.getValue().getCommited(), n.getValue().getCommited()));
n.setValue(
new StoreNotes(n.getValue().getCommited(), n.getValue().getCommited()));
DataStorage.get().saveAsync();
ref.set(null);
}
@ -136,7 +147,6 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
return popover;
}
public record Structure(Popover popover, Button button) implements CompStructure<Button> {
@Override

View file

@ -6,12 +6,14 @@ import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import lombok.Value;
import java.util.*;
@ -39,7 +41,8 @@ public class StoreSection {
if (wrapper != null) {
this.showDetails = Bindings.createBooleanBinding(
() -> {
return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
return wrapper.getExpanded().get()
|| allChildren.getList().isEmpty();
},
wrapper.getExpanded(),
allChildren.getList());
@ -85,7 +88,12 @@ public class StoreSection {
}
seen.add(wrapper);
var found = list.getList().stream().filter(section -> section.getWrapper().getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())).findFirst();
var found = list.getList().stream()
.filter(section -> section.getWrapper()
.getEntry()
.getUuid()
.equals(wrapper.getEntry().getOrderBefore()))
.findFirst();
if (found.isPresent()) {
return count(found.get().getWrapper(), seen);
} else {
@ -99,7 +107,8 @@ public class StoreSection {
var mappedSortMode = BindingsHelper.flatMap(
category,
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
return list.sorted((o1, o2) -> {
return list.sorted(
(o1, o2) -> {
var current = mappedSortMode.getValue();
if (current != null) {
return comp.thenComparing(current.comparator())
@ -117,7 +126,8 @@ public class StoreSection {
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) {
var topLevel = all.filtered(section -> {
var topLevel = all.filtered(
section -> {
return DataStorage.get().isRootEntry(section.getEntry());
},
category,
@ -128,11 +138,15 @@ public class StoreSection {
var shown = ordered.filtered(
section -> {
// matches filter
return (filterString == null || section.matchesFilter(filterString.get())) &&
return (filterString == null || section.matchesFilter(filterString.get()))
&&
// matches selector
(section.anyMatches(entryFilter)) &&
(section.anyMatches(entryFilter))
&&
// same category
(category == null || category.getValue() == null || showInCategory(category.getValue(), section.getWrapper()));
(category == null
|| category.getValue() == null
|| showInCategory(category.getValue(), section.getWrapper()));
},
category,
filterString);
@ -148,23 +162,31 @@ public class StoreSection {
ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) {
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return new StoreSection(e, new DerivedObservableList<>(
FXCollections.observableArrayList(), true), new DerivedObservableList<>(
FXCollections.observableArrayList(), true), depth);
return new StoreSection(
e,
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
depth);
}
var allChildren = all.filtered(other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
var allChildren = all.filtered(
other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// is children. This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry()) &&
// show provider
(!other.getEntry().getValidity().isUsable() || other.getEntry().getProvider().shouldShow(other));
}, e.getPersistentState(), e.getCache(), StoreViewState.get().getEntriesListChangeObservable());
// is children. This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry())
&&
// show provider
(!other.getEntry().getValidity().isUsable()
|| other.getEntry().getProvider().shouldShow(other));
},
e.getPersistentState(),
e.getCache(),
StoreViewState.get().getEntriesListChangeObservable());
var l = new ArrayList<>(parents);
l.add(e);
var cached = allChildren.mapped(c -> create(l, c, depth + 1, all, entryFilter, filterString, category));
@ -172,14 +194,23 @@ public class StoreSection {
var filtered = ordered.filtered(
section -> {
// matches filter
return (filterString == null || section.matchesFilter(filterString.get()) || l.stream().anyMatch(p -> p.matchesFilter(filterString.get()))) &&
return (filterString == null
|| section.matchesFilter(filterString.get())
|| l.stream().anyMatch(p -> p.matchesFilter(filterString.get())))
&&
// matches selector
section.anyMatches(entryFilter) &&
section.anyMatches(entryFilter)
&&
// matches category
// Prevent updates for children on category switching by checking depth
(category == null || category.getValue() == null || showInCategory(category.getValue(), section.getWrapper()) || depth > 0) &&
(category == null
|| category.getValue() == null
|| showInCategory(category.getValue(), section.getWrapper())
|| depth > 0)
&&
// not root
// If this entry is already shown as root due to a different category than parent, don't show it
// If this entry is already shown as root due to a different category than parent, don't
// show it
// again here
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
},

View file

@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
@ -126,14 +127,16 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getShownChildren().filtered(
storeSection -> section.getAllChildren().getList().size() <= 20
|| section.getWrapper().getExpanded().get(),
section.getWrapper().getExpanded(),
section.getAllChildren().getList());
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
})
var listSections = section.getShownChildren()
.filtered(
storeSection -> section.getAllChildren().getList().size() <= 20
|| section.getWrapper().getExpanded().get(),
section.getWrapper().getExpanded(),
section.getAllChildren().getList());
var content = new ListBoxViewComp<>(
listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
})
.minHeight(0)
.hgrow();
@ -152,7 +155,8 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getShownChildren().getList()).isEqualTo(0)))));
Bindings.size(section.getShownChildren().getList())
.isEqualTo(0)))));
return full.styleClass("store-entry-section-comp")
.apply(struc -> {
struc.get().setFillWidth(true);

View file

@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -129,14 +130,17 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getWrapper() != null
? section.getShownChildren().filtered(
storeSection -> section.getAllChildren().getList().size() <= 20 || expanded.get(),
expanded,
section.getAllChildren().getList())
? section.getShownChildren()
.filtered(
storeSection ->
section.getAllChildren().getList().size() <= 20 || expanded.get(),
expanded,
section.getAllChildren().getList())
: section.getShownChildren();
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
})
var content = new ListBoxViewComp<>(
listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
})
.minHeight(0)
.hgrow();

View file

@ -9,9 +9,11 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import lombok.Getter;
import java.util.*;
@ -86,22 +88,26 @@ public class StoreViewState {
currentTopLevelSection =
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
} catch (Exception exception) {
currentTopLevelSection =
new StoreSection(null,
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
0);
currentTopLevelSection = new StoreSection(
null,
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
0);
ErrorEvent.fromThrowable(exception).handle();
}
}
private void initContent() {
allEntries.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
.map(StoreEntryWrapper::new)
.toList()));
categories.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
.map(StoreCategoryWrapper::new)
.toList()));
allEntries
.getList()
.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
.map(StoreEntryWrapper::new)
.toList()));
categories
.getList()
.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
.map(StoreCategoryWrapper::new)
.toList()));
activeCategory.addListener((observable, oldValue, newValue) -> {
DataStorage.get().setSelectedCategory(newValue.getCategory());
@ -274,12 +280,16 @@ public class StoreViewState {
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
StoreSection current = getCurrentTopLevelSection();
while (true) {
var child = current.getAllChildren().getList().stream().filter(section -> section.getWrapper().equals(wrapper)).findFirst();
var child = current.getAllChildren().getList().stream()
.filter(section -> section.getWrapper().equals(wrapper))
.findFirst();
if (child.isPresent()) {
return Optional.of(current);
}
var traverse = current.getAllChildren().getList().stream().filter(section -> section.anyMatches(w -> w.equals(wrapper))).findFirst();
var traverse = current.getAllChildren().getList().stream()
.filter(section -> section.anyMatches(w -> w.equals(wrapper)))
.findFirst();
if (traverse.isPresent()) {
current = traverse.get();
} else {
@ -339,7 +349,9 @@ public class StoreViewState {
.compareToIgnoreCase(o2.nameProperty().getValue());
}
};
return categories.filtered(cat -> root == null || cat.getRoot().equals(root)).sorted(comparator);
return categories
.filtered(cat -> root == null || cat.getRoot().equals(root))
.sorted(comparator);
}
public StoreCategoryWrapper getAllConnectionsCategory() {

View file

@ -6,9 +6,11 @@ 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 javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.stage.Stage;
import lombok.Getter;
import lombok.SneakyThrows;

View file

@ -5,11 +5,13 @@ import io.xpipe.app.launcher.LauncherInput;
import javafx.scene.control.Alert;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import lombok.Setter;
import java.util.List;
public class AppActionLinkDetector {
@Setter
private static String lastDetectedAction;
private static String getClipboardAction() {
@ -34,10 +36,6 @@ public class AppActionLinkDetector {
LauncherInput.handle(List.of(content));
}
public static void setLastDetectedAction(String s) {
lastDetectedAction = s;
}
public static void detectOnFocus() {
var content = getClipboardAction();
if (content == null) {

View file

@ -9,10 +9,10 @@ import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.desktop.*;
import java.util.List;
import javax.imageio.ImageIO;
public class AppDesktopIntegration {
@ -26,10 +26,10 @@ public class AppDesktopIntegration {
Desktop.getDesktop().addAppEventListener(new SystemSleepListener() {
@Override
public void systemAboutToSleep(SystemSleepEvent e) {
if (AppPrefs.get() != null &&
AppPrefs.get().lockVaultOnHibernation().get() &&
AppPrefs.get().getLockCrypt().get() != null &&
!AppPrefs.get().getLockCrypt().get().isBlank()) {
if (AppPrefs.get() != null
&& AppPrefs.get().lockVaultOnHibernation().get()
&& AppPrefs.get().getLockCrypt().get() != null
&& !AppPrefs.get().getLockCrypt().get().isBlank()) {
// 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
// This hopefully assures that it will be run later, probably on system wake
@ -70,7 +70,9 @@ public class AppDesktopIntegration {
// Set dock icon explicitly on mac
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
if (AppProperties.get().isDeveloperMode() && AppLogs.get().isWriteToSysout() && Taskbar.isTaskbarSupported()) {
if (AppProperties.get().isDeveloperMode()
&& AppLogs.get().isWriteToSysout()
&& Taskbar.isTaskbarSupported()) {
try {
var iconUrl = Main.class.getResourceAsStream("resources/img/logo/padded/logo_128x128.png");
if (iconUrl != null) {

View file

@ -88,7 +88,8 @@ public class AppExtensionManager {
}
if (!AppProperties.get().isFullVersion()) {
var localInstallation = XPipeInstallation.getLocalDefaultInstallationBasePath(AppProperties.get().isStaging() || AppProperties.get().isLocatePtb());
var localInstallation = XPipeInstallation.getLocalDefaultInstallationBasePath(
AppProperties.get().isStaging() || AppProperties.get().isLocatePtb());
Path p = Path.of(localInstallation);
if (!Files.exists(p)) {
throw new IllegalStateException(

View file

@ -10,10 +10,12 @@ import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.Translatable;
import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import lombok.SneakyThrows;
import lombok.Value;
import org.apache.commons.io.FilenameUtils;

View file

@ -19,14 +19,13 @@ import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
@Getter
public class AppLayoutModel {
private static AppLayoutModel INSTANCE;
@Getter
private final SavedState savedState;
@Getter
private final List<Entry> entries;
private final Property<Entry> selected;
@ -51,10 +50,6 @@ public class AppLayoutModel {
INSTANCE = null;
}
public Property<Entry> getSelected() {
return selected;
}
public void selectBrowser() {
selected.setValue(entries.getFirst());
}

View file

@ -7,6 +7,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.CloseBehaviourAlert;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Rectangle2D;
@ -16,17 +17,18 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.stage.Screen;
import javafx.stage.Stage;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import javax.imageio.ImageIO;
public class AppMainWindow {

View file

@ -2,8 +2,10 @@ package io.xpipe.app.core;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.process.OsType;
import javafx.application.Preloader;
import javafx.stage.Stage;
import lombok.Getter;
import lombok.SneakyThrows;

View file

@ -4,6 +4,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.util.ModuleHelper;
import lombok.Getter;
import lombok.Value;
@ -100,7 +101,8 @@ public class AppProperties {
locatePtb = Optional.ofNullable(System.getProperty("io.xpipe.app.locator.usePtbInstallation"))
.map(Boolean::parseBoolean)
.orElse(false);
locatorVersionCheck = Optional.ofNullable(System.getProperty("io.xpipe.app.locator.disableInstallationVersionCheck"))
locatorVersionCheck = Optional.ofNullable(
System.getProperty("io.xpipe.app.locator.disableInstallationVersionCheck"))
.map(s -> !Boolean.parseBoolean(s))
.orElse(true);
isTest = isJUnitTest();

View file

@ -260,8 +260,8 @@ public class AppTheme {
public static final Theme CUSTOM = new DerivedTheme("custom", "primer", "Custom", new PrimerDark());
// Also include your custom theme here
public static final List<Theme> ALL =
List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA, MOCHA);
public static final List<Theme> ALL = List.of(
PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA, MOCHA);
protected final String id;
@Getter

View file

@ -7,6 +7,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.InputHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;

View file

@ -81,8 +81,8 @@ public class AppAvCheck {
@Override
public boolean isActive() {
return WindowsRegistry.local().valueExists(
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir");
return WindowsRegistry.local()
.valueExists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir");
}
},
MALWAREBYTES("Malwarebytes") {
@ -93,7 +93,8 @@ public class AppAvCheck {
@Override
public boolean isActive() {
return WindowsRegistry.local().valueExists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Malwarebytes", "id");
return WindowsRegistry.local()
.valueExists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Malwarebytes", "id");
}
},
MCAFEE("McAfee") {
@ -104,7 +105,8 @@ public class AppAvCheck {
@Override
public boolean isActive() {
return WindowsRegistry.local().valueExists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\McAfee", "mi");
return WindowsRegistry.local()
.valueExists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\McAfee", "mi");
}
};

View file

@ -11,6 +11,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.util.ThreadHelper;
import javafx.stage.Stage;
public class GuiMode extends PlatformMode {

View file

@ -194,8 +194,9 @@ public abstract class OperationMode {
public static void restart() {
OperationMode.executeAfterShutdown(() -> {
var loc = AppProperties.get().isDevelopmentEnvironment() ?
XPipeInstallation.getLocalDefaultInstallationBasePath() : XPipeInstallation.getCurrentInstallationBasePath().toString();
var loc = AppProperties.get().isDevelopmentEnvironment()
? XPipeInstallation.getLocalDefaultInstallationBasePath()
: XPipeInstallation.getCurrentInstallationBasePath().toString();
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(loc, XPipeDaemonMode.GUI, "", true);
LocalShell.getShell().executeSimpleCommand(exec);
});

View file

@ -45,7 +45,6 @@ public interface ActionProvider {
return null;
}
default BranchDataStoreCallSite<?> getBranchDataStoreCallSite() {
return null;
}

View file

@ -15,7 +15,7 @@ public abstract class DataStorageExtensionProvider {
return ALL;
}
public void storageInit() throws Exception {}
public void storageInit() {}
public static class Loader implements ModuleLayerLoader {

View file

@ -14,6 +14,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
@ -29,9 +30,9 @@ public interface DataStoreProvider {
default boolean shouldShow(StoreEntryWrapper w) {
return true;
}
default void onChildrenRefresh(DataStoreEntry entry) {}
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
return new SimpleBooleanProperty(false);
}

View file

@ -13,7 +13,7 @@ import io.xpipe.core.store.StatefulDataStore;
public interface EnabledParentStoreProvider extends DataStoreProvider {
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return StoreEntryComp.create(sec.getWrapper(), null, preferLarge);
}

View file

@ -10,7 +10,7 @@ import io.xpipe.core.store.StatefulDataStore;
public interface EnabledStoreProvider extends DataStoreProvider {
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return StoreEntryComp.create(sec.getWrapper(), null, preferLarge);
}

View file

@ -40,8 +40,9 @@ public interface SingletonSessionStoreProvider extends DataStoreProvider {
enabled.set(s.isSessionEnabled());
});
ObservableValue<LabelGraphic> g = enabled.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2p-power"));
ObservableValue<LabelGraphic> g = enabled.map(aBoolean -> aBoolean
? new LabelGraphic.IconGraphic("mdi2c-circle-slice-8")
: new LabelGraphic.IconGraphic("mdi2p-power"));
var t = new StoreToggleComp(null, g, sec, enabled, aBoolean -> {
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
if (s.isSessionEnabled() != aBoolean) {

View file

@ -1,12 +1,12 @@
package io.xpipe.app.fxcomps;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
@ -18,6 +18,8 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

View file

@ -6,12 +6,14 @@ import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.Translatable;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.util.StringConverter;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;

View file

@ -1,6 +1,5 @@
package io.xpipe.app.fxcomps.impl;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserChooserComp;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
@ -16,6 +15,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -23,6 +23,8 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.Alert;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files;
@ -44,7 +46,6 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
this.filePath = filePath;
}
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
this.fileSystem = new SimpleObjectProperty<>();

View file

@ -77,7 +77,8 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
var e = storeEntryWrapper.getEntry();
if (e.equals(self) || DataStorage.get().getStoreParentHierarchy(e).contains(self)) {
if (e.equals(self)
|| DataStorage.get().getStoreParentHierarchy(e).contains(self)) {
return false;
}

View file

@ -15,6 +15,7 @@ import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.util.ContextMenuHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
@ -25,6 +26,7 @@ import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;
@ -77,12 +79,14 @@ public class StoreCategoryComp extends SimpleComp {
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
storeEntryWrapper -> {
return storeEntryWrapper.matchesFilter(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString()).getList();
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true)
.filtered(
storeEntryWrapper -> {
return storeEntryWrapper.matchesFilter(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString())
.getList();
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
var hover = new SimpleBooleanProperty();
var focus = new SimpleBooleanProperty();

View file

@ -4,6 +4,7 @@ import io.xpipe.app.core.AppProperties;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
@ -13,6 +14,7 @@ import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebView;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;

View file

@ -4,6 +4,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip;

View file

@ -8,6 +8,7 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.util.*;
@ -133,13 +134,15 @@ public class DerivedObservableList<T> {
var l1 = this.<V>createNewDerived();
Runnable runnable = () -> {
cache.keySet().removeIf(t -> !getList().contains(t));
l1.setContent(list.stream().map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
l1.setContent(list.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
}).toList());
return cache.get(v);
})
.toList());
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {
@ -160,17 +163,16 @@ public class DerivedObservableList<T> {
}
public DerivedObservableList<T> filtered(Predicate<T> predicate, Observable... observables) {
return filtered(
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(T v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
return filtered(Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(T v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
}
public DerivedObservableList<T> filtered(ObservableValue<Predicate<T>> predicate) {

View file

@ -1,9 +1,11 @@
package io.xpipe.app.fxcomps.util;
import io.xpipe.app.fxcomps.Comp;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;

View file

@ -11,6 +11,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.util.LicenseRequiredException;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -24,6 +25,7 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -252,7 +254,8 @@ public class ErrorHandlerComp extends SimpleComp {
}
if (!event.isDisableDefaultActions() || event.getCustomActions().isEmpty()) {
for (var action : List.of(ErrorAction.automaticallyReport(), ErrorAction.reportOnGithub(), ErrorAction.ignore())) {
for (var action :
List.of(ErrorAction.automaticallyReport(), ErrorAction.reportOnGithub(), ErrorAction.ignore())) {
var ac = createActionComp(action);
actionBox.getChildren().add(ac);
}

View file

@ -17,6 +17,7 @@ import io.xpipe.beacon.api.DaemonOpenExchange;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation;
import lombok.SneakyThrows;
import picocli.CommandLine;
@ -83,24 +84,33 @@ public class LauncherCommand implements Callable<Integer> {
private void checkStart() {
try {
var port = AppBeaconServer.get().getPort();
var client = BeaconClient.tryEstablishConnection(port, BeaconClientInformation.Daemon.builder().build());
var client = BeaconClient.tryEstablishConnection(
port, BeaconClientInformation.Daemon.builder().build());
if (client.isPresent()) {
client.get().performRequest(DaemonFocusExchange.Request.builder().mode(getEffectiveMode()).build());
if (!inputs.isEmpty()) {
client.get().performRequest(
DaemonOpenExchange.Request.builder().arguments(inputs).build());
}
client.get()
.performRequest(DaemonFocusExchange.Request.builder()
.mode(getEffectiveMode())
.build());
if (!inputs.isEmpty()) {
client.get()
.performRequest(DaemonOpenExchange.Request.builder()
.arguments(inputs)
.build());
}
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
try {
client.get().performRequest(DaemonOpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle();
}
});
ThreadHelper.sleep(1000);
}
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
try {
client.get()
.performRequest(DaemonOpenExchange.Request.builder()
.arguments(List.of(e.getURI().toString()))
.build());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle();
}
});
ThreadHelper.sleep(1000);
}
TrackEvent.info("Another instance is already running on this port. Quitting ...");
OperationMode.halt(1);
}

View file

@ -6,6 +6,7 @@ import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.storage.DataStorage;
import lombok.Value;
import java.net.URI;
@ -33,11 +34,11 @@ public abstract class LauncherInput {
}
});
// var requiresPlatform = all.stream().anyMatch(launcherInput -> launcherInput.requiresJavaFXPlatform());
// if (requiresPlatform) {
// OperationMode.switchToSyncIfPossible(OperationMode.GUI);
// }
// var hasGui = OperationMode.get() == OperationMode.GUI;
// var requiresPlatform = all.stream().anyMatch(launcherInput -> launcherInput.requiresJavaFXPlatform());
// if (requiresPlatform) {
// OperationMode.switchToSyncIfPossible(OperationMode.GUI);
// }
// var hasGui = OperationMode.get() == OperationMode.GUI;
all.forEach(launcherInput -> {
try {

View file

@ -15,12 +15,14 @@ import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import lombok.Getter;
import lombok.Value;
@ -120,8 +122,8 @@ public class AppPrefs {
private final StringProperty lockCrypt =
mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class);
final Property<Integer> httpServerPort =
mapVaultSpecific(new SimpleObjectProperty<>(XPipeInstallation.getDefaultBeaconPort()), "httpServerPort", Integer.class);
final Property<Integer> httpServerPort = mapVaultSpecific(
new SimpleObjectProperty<>(XPipeInstallation.getDefaultBeaconPort()), "httpServerPort", Integer.class);
final StringProperty apiKey =
mapVaultSpecific(new SimpleStringProperty(UUID.randomUUID().toString()), "apiKey", String.class);
final BooleanProperty disableApiAuthentication =

Some files were not shown because too many files have changed in this diff Show more