mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Dock rework
This commit is contained in:
parent
19b341d848
commit
c32f1fbaf7
5433 changed files with 53130 additions and 1874 deletions
|
@ -25,15 +25,15 @@ components from it when it is run in a development environment.
|
||||||
Note that in case the current master branch is ahead of the latest release, it might happen that there are some incompatibilities when loading data from your local XPipe installation.
|
Note that in case the current master branch is ahead of the latest release, it might happen that there are some incompatibilities when loading data from your local XPipe installation.
|
||||||
You should therefore always check out the matching version tag for your local repository and local XPipe installation.
|
You should therefore always check out the matching version tag for your local repository and local XPipe installation.
|
||||||
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
||||||
So for example if you currently have XPipe `10.0` installed, you should run `git reset --hard 10.0` first to properly compile against it.
|
So for example if you currently have XPipe `11.3` installed, you should run `git reset --hard 11.3` first to properly compile against it.
|
||||||
|
|
||||||
You need to have JDK for Java 21 installed to compile the project.
|
You need to have JDK for Java 21 installed to compile the project.
|
||||||
If you are on Linux or macOS, you can easily accomplish that by running
|
If you are on Linux or macOS, you can easily accomplish that by running
|
||||||
```bash
|
```bash
|
||||||
curl -s "https://get.sdkman.io" | bash
|
curl -s "https://get.sdkman.io" | bash
|
||||||
. "$HOME/.sdkman/bin/sdkman-init.sh"
|
. "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||||
sdk install java 21.0.1-graalce
|
sdk install java 22.0.2-graalce
|
||||||
sdk default java 21.0.1-graalce
|
sdk default java 22.0.2-graalce
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=21).
|
On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=21).
|
||||||
|
@ -74,7 +74,7 @@ Especially when starting out, it might be a good idea to start with easy tasks f
|
||||||
|
|
||||||
### Interacting via the HTTP API
|
### Interacting via the HTTP API
|
||||||
|
|
||||||
You can create clients they communicate with the XPipe daemon via its HTTP API.
|
You can create clients that communicate with the XPipe daemon via its HTTP API.
|
||||||
To get started, see the [OpenAPI spec](/openapi.yaml).
|
To get started, see the [OpenAPI spec](/openapi.yaml).
|
||||||
|
|
||||||
### Implementing support for a new editor
|
### Implementing support for a new editor
|
||||||
|
@ -98,9 +98,13 @@ All actions that you can perform for certain connections in the connection overv
|
||||||
|
|
||||||
You can add custom script definitions [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java) and [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/resources/io/xpipe/ext/base/resources/scripts).
|
You can add custom script definitions [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java) and [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/resources/io/xpipe/ext/base/resources/scripts).
|
||||||
|
|
||||||
|
### Adding more system icons for system autodetection
|
||||||
|
|
||||||
|
You can register new system types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/java/io/xpipe/app/resources/SystemIcons.java) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/img/system).
|
||||||
|
|
||||||
### Adding more file icons for specific types
|
### Adding more file icons for specific types
|
||||||
|
|
||||||
You can register file types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/file_list.txt) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/browser_icons).
|
You can register file types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/file_list.txt) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/img/browser).
|
||||||
|
|
||||||
The existing file list and icons are taken from the [vscode-icons](https://github.com/vscode-icons/vscode-icons) project. Due to limitations in the file definition list compatibility, some file types might not be listed by their proper extension and are therefore not being applied correctly even though the images and definitions exist already.
|
The existing file list and icons are taken from the [vscode-icons](https://github.com/vscode-icons/vscode-icons) project. Due to limitations in the file definition list compatibility, some file types might not be listed by their proper extension and are therefore not being applied correctly even though the images and definitions exist already.
|
||||||
|
|
||||||
|
@ -108,6 +112,6 @@ The existing file list and icons are taken from the [vscode-icons](https://githu
|
||||||
|
|
||||||
if you want to work on something that was not listed here, you can still do so of course. You can reach out on the [Discord server](https://discord.gg/8y89vS8cRb) to discuss any development plans and get you started.
|
if you want to work on something that was not listed here, you can still do so of course. You can reach out on the [Discord server](https://discord.gg/8y89vS8cRb) to discuss any development plans and get you started.
|
||||||
|
|
||||||
### Translations
|
### Adding translations
|
||||||
|
|
||||||
See the [translation guide](/lang) for details.
|
See the [translation guide](/lang) for details.
|
||||||
|
|
|
@ -17,8 +17,10 @@ It currently supports:
|
||||||
- [Docker](https://www.docker.com/), [Podman](https://podman.io/), and [LXD](https://linuxcontainers.org/lxd/introduction/) container instances located on any host
|
- [Docker](https://www.docker.com/), [Podman](https://podman.io/), and [LXD](https://linuxcontainers.org/lxd/introduction/) container instances located on any host
|
||||||
- [Windows Subsystem for Linux](https://ubuntu.com/wsl), [Cygwin](https://www.cygwin.com/), and [MSYS2](https://www.msys2.org/) instances
|
- [Windows Subsystem for Linux](https://ubuntu.com/wsl), [Cygwin](https://www.cygwin.com/), and [MSYS2](https://www.msys2.org/) instances
|
||||||
- [Proxmox PVE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) virtual machines and containers
|
- [Proxmox PVE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) virtual machines and containers
|
||||||
|
- [Hyper-V](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/) and [VMware Player/Workstation/Fusion](https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion) virtual machines
|
||||||
- [Kubernetes](https://kubernetes.io/) clusters, pods, and containers
|
- [Kubernetes](https://kubernetes.io/) clusters, pods, and containers
|
||||||
- [Powershell Remote Sessions](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.3)
|
- [Powershell Remote Sessions](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.3)
|
||||||
|
- Built-in VNC connections and RDP launchers
|
||||||
- Any other custom remote connection methods that work through the command-line
|
- Any other custom remote connection methods that work through the command-line
|
||||||
|
|
||||||
## Connection hub
|
## Connection hub
|
||||||
|
@ -50,6 +52,8 @@ It currently supports:
|
||||||
- Works with all command shells such as bash, zsh, cmd, PowerShell, and more, locally and remote
|
- Works with all command shells such as bash, zsh, cmd, PowerShell, and more, locally and remote
|
||||||
- Connects to a system while the terminal is still starting up, allowing for faster connections than otherwise possible
|
- Connects to a system while the terminal is still starting up, allowing for faster connections than otherwise possible
|
||||||
|
|
||||||
|
![Terminal](https://github.com/xpipe-io/.github/raw/main/img/terminal_shadow.png)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/xpipe-io/.github/raw/main/img/terminal.gif" alt="Terminal launcher"/>
|
<img src="https://github.com/xpipe-io/.github/raw/main/img/terminal.gif" alt="Terminal launcher"/>
|
||||||
|
|
|
@ -52,10 +52,10 @@ dependencies {
|
||||||
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
||||||
api 'org.bouncycastle:bcprov-jdk18on:1.78.1'
|
api 'org.bouncycastle:bcprov-jdk18on:1.78.1'
|
||||||
api 'info.picocli:picocli:4.7.6'
|
api 'info.picocli:picocli:4.7.6'
|
||||||
api ('org.kohsuke:github-api:1.324') {
|
api ('org.kohsuke:github-api:1.326') {
|
||||||
exclude group: 'org.apache.commons', module: 'commons-lang3'
|
exclude group: 'org.apache.commons', module: 'commons-lang3'
|
||||||
}
|
}
|
||||||
api 'org.apache.commons:commons-lang3:3.16.0'
|
api 'org.apache.commons:commons-lang3:3.17.0'
|
||||||
api 'io.sentry:sentry:7.14.0'
|
api 'io.sentry:sentry:7.14.0'
|
||||||
api 'commons-io:commons-io:2.16.1'
|
api 'commons-io:commons-io:2.16.1'
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.2"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.2"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package io.xpipe.app.beacon;
|
package io.xpipe.app.beacon;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppResources;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.app.util.MarkdownHelper;
|
import io.xpipe.app.util.MarkdownHelper;
|
||||||
import io.xpipe.beacon.BeaconConfig;
|
import io.xpipe.beacon.BeaconConfig;
|
||||||
import io.xpipe.beacon.BeaconInterface;
|
import io.xpipe.beacon.BeaconInterface;
|
||||||
|
|
|
@ -39,7 +39,8 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beaconInterface.requiresEnabledApi() && !AppPrefs.get().enableHttpApi().get()) {
|
if (beaconInterface.requiresEnabledApi()
|
||||||
|
&& !AppPrefs.get().enableHttpApi().get()) {
|
||||||
var ex = new BeaconServerException("HTTP API is not enabled in the settings menu");
|
var ex = new BeaconServerException("HTTP API is not enabled in the settings menu");
|
||||||
writeError(exchange, ex, 403);
|
writeError(exchange, ex, 403);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -15,9 +15,9 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange {
|
||||||
.getStoreEntryIfPresent(msg.getConnection())
|
.getStoreEntryIfPresent(msg.getConnection())
|
||||||
.orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection()));
|
.orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection()));
|
||||||
if (e.getStore() instanceof FixedHierarchyStore) {
|
if (e.getStore() instanceof FixedHierarchyStore) {
|
||||||
DataStorage.get().refreshChildren(e, true);
|
DataStorage.get().refreshChildren(e, null, true);
|
||||||
} else {
|
} else {
|
||||||
e.validateOrThrow();
|
e.validateOrThrowAndClose(null);
|
||||||
}
|
}
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ public class FsReadExchangeImpl extends FsReadExchange {
|
||||||
var out = exchange.getResponseBody()) {
|
var out = exchange.getResponseBody()) {
|
||||||
fileIn.transferTo(out);
|
fileIn.transferTo(out);
|
||||||
}
|
}
|
||||||
return Response.builder().build();
|
|
||||||
} else {
|
} else {
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
try (var in = fs.openInput(msg.getPath().toString())) {
|
try (var in = fs.openInput(msg.getPath().toString())) {
|
||||||
|
@ -55,7 +54,7 @@ public class FsReadExchangeImpl extends FsReadExchange {
|
||||||
try (var out = exchange.getResponseBody()) {
|
try (var out = exchange.getResponseBody()) {
|
||||||
out.write(bytes);
|
out.write(bytes);
|
||||||
}
|
}
|
||||||
return Response.builder().build();
|
|
||||||
}
|
}
|
||||||
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
import io.xpipe.app.beacon.AppBeaconServer;
|
import io.xpipe.app.beacon.AppBeaconServer;
|
||||||
import io.xpipe.app.beacon.BeaconShellSession;
|
import io.xpipe.app.beacon.BeaconShellSession;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.api.ShellStartExchange;
|
import io.xpipe.beacon.api.ShellStartExchange;
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
public class ShellStartExchangeImpl extends ShellStartExchange {
|
public class ShellStartExchangeImpl extends ShellStartExchange {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.util.TerminalLauncherManager;
|
import io.xpipe.app.util.TerminalLauncherManager;
|
||||||
import io.xpipe.beacon.api.SshLaunchExchange;
|
import io.xpipe.beacon.api.SshLaunchExchange;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
|
||||||
import io.xpipe.core.process.ShellDialects;
|
import io.xpipe.core.process.ShellDialects;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SshLaunchExchangeImpl extends SshLaunchExchange {
|
public class SshLaunchExchangeImpl extends SshLaunchExchange {
|
||||||
|
@ -27,7 +28,7 @@ public class SshLaunchExchangeImpl extends SshLaunchExchange {
|
||||||
|
|
||||||
// There are sometimes multiple requests by a terminal client (e.g. Termius)
|
// There are sometimes multiple requests by a terminal client (e.g. Termius)
|
||||||
// This might fail sometimes, but it is expected
|
// This might fail sometimes, but it is expected
|
||||||
var r = TerminalLauncherManager.waitForNextLaunch();
|
var r = TerminalLauncherManager.sshLaunchExchange();
|
||||||
var c = ProcessControlProvider.get()
|
var c = ProcessControlProvider.get()
|
||||||
.getEffectiveLocalDialect()
|
.getEffectiveLocalDialect()
|
||||||
.getOpenScriptCommand(r.toString())
|
.getOpenScriptCommand(r.toString())
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.sun.net.httpserver.HttpExchange;
|
||||||
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
|
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
|
||||||
@Override
|
@Override
|
||||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
|
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
|
||||||
var r = TerminalLauncherManager.performLaunch(msg.getRequest());
|
var r = TerminalLauncherManager.launchExchange(msg.getRequest());
|
||||||
return Response.builder().targetFile(r).build();
|
return Response.builder().targetFile(r).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
import io.xpipe.app.util.TerminalLauncherManager;
|
import io.xpipe.app.util.TerminalLauncherManager;
|
||||||
|
import io.xpipe.app.util.TerminalView;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.BeaconServerException;
|
import io.xpipe.beacon.BeaconServerException;
|
||||||
import io.xpipe.beacon.api.TerminalWaitExchange;
|
import io.xpipe.beacon.api.TerminalWaitExchange;
|
||||||
|
@ -10,7 +11,8 @@ import com.sun.net.httpserver.HttpExchange;
|
||||||
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
|
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
|
||||||
@Override
|
@Override
|
||||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
|
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
|
||||||
TerminalLauncherManager.waitForCompletion(msg.getRequest());
|
TerminalLauncherManager.waitExchange(msg.getRequest());
|
||||||
|
TerminalView.get().open(msg.getPid());
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package io.xpipe.app.browser;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||||
import io.xpipe.app.browser.file.LocalFileSystem;
|
import io.xpipe.app.browser.file.LocalFileSystem;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
import io.xpipe.core.util.FailableRunnable;
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,76 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.FileBridge;
|
import io.xpipe.app.util.FileBridge;
|
||||||
import io.xpipe.app.util.FileOpener;
|
import io.xpipe.app.util.FileOpener;
|
||||||
|
import io.xpipe.core.process.ElevationFunction;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.store.ConnectionFileSystem;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
|
import io.xpipe.core.store.FileInfo;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class BrowserFileOpener {
|
public class BrowserFileOpener {
|
||||||
|
|
||||||
|
private static OutputStream openFileOutput(OpenFileSystemModel model, FileEntry file, long totalBytes)
|
||||||
|
throws Exception {
|
||||||
|
var fileSystem = model.getFileSystem();
|
||||||
|
if (model.isClosed() || fileSystem.getShell().isEmpty()) {
|
||||||
|
return OutputStream.nullOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sc = fileSystem.getShell().get();
|
||||||
|
if (sc.getOsType() == OsType.WINDOWS) {
|
||||||
|
return fileSystem.openOutput(file.getPath(), totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = (FileInfo.Unix) file.getInfo();
|
||||||
|
var zero = Integer.valueOf(0);
|
||||||
|
var otherWrite = info.getPermissions().charAt(7) == 'w';
|
||||||
|
var requiresRoot = zero.equals(info.getUid()) && zero.equals(info.getGid()) && !otherWrite;
|
||||||
|
if (!requiresRoot || model.getCache().isRoot()) {
|
||||||
|
return fileSystem.openOutput(file.getPath(), totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var elevate = AppWindowHelper.showConfirmationAlert(
|
||||||
|
"app.fileWriteSudoTitle", "app.fileWriteSudoHeader", "app.fileWriteSudoContent");
|
||||||
|
if (!elevate) {
|
||||||
|
return fileSystem.openOutput(file.getPath(), totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootSc = sc.identicalSubShell()
|
||||||
|
.elevated(ElevationFunction.elevated("sudo"))
|
||||||
|
.start();
|
||||||
|
var rootFs = new ConnectionFileSystem(rootSc);
|
||||||
|
try {
|
||||||
|
return new FilterOutputStream(rootFs.openOutput(file.getPath(), totalBytes)) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
rootFs.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (Exception ex) {
|
||||||
|
rootFs.close();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateKey(FileEntry entry) {
|
||||||
|
return Objects.hash(entry.getPath(), entry.getFileSystem(), entry.getKind(), entry.getInfo());
|
||||||
|
}
|
||||||
|
|
||||||
public static void openWithAnyApplication(OpenFileSystemModel model, FileEntry entry) {
|
public static void openWithAnyApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||||
var file = entry.getPath();
|
var file = entry.getPath();
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
var key = calculateKey(entry);
|
||||||
FileBridge.get()
|
FileBridge.get()
|
||||||
.openIO(
|
.openIO(
|
||||||
FileNames.getFileName(file),
|
FileNames.getFileName(file),
|
||||||
|
@ -35,7 +91,7 @@ public class BrowserFileOpener {
|
||||||
|
|
||||||
public static void openInDefaultApplication(OpenFileSystemModel model, FileEntry entry) {
|
public static void openInDefaultApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||||
var file = entry.getPath();
|
var file = entry.getPath();
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
var key = calculateKey(entry);
|
||||||
FileBridge.get()
|
FileBridge.get()
|
||||||
.openIO(
|
.openIO(
|
||||||
FileNames.getFileName(file),
|
FileNames.getFileName(file),
|
||||||
|
@ -61,7 +117,7 @@ public class BrowserFileOpener {
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = entry.getPath();
|
var file = entry.getPath();
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
var key = calculateKey(entry);
|
||||||
FileBridge.get()
|
FileBridge.get()
|
||||||
.openIO(
|
.openIO(
|
||||||
FileNames.getFileName(file),
|
FileNames.getFileName(file),
|
||||||
|
@ -71,11 +127,7 @@ public class BrowserFileOpener {
|
||||||
return entry.getFileSystem().openInput(file);
|
return entry.getFileSystem().openInput(file);
|
||||||
},
|
},
|
||||||
(size) -> {
|
(size) -> {
|
||||||
if (model.isClosed()) {
|
return openFileOutput(model, entry, size);
|
||||||
return OutputStream.nullOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.getFileSystem().openOutput(file, size);
|
|
||||||
},
|
},
|
||||||
FileOpener::openInTextEditor);
|
FileOpener::openInTextEditor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
|
||||||
var graphic = Bindings.createStringBinding(
|
var graphic = Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return model.getCurrentDirectory() != null
|
return model.getCurrentDirectory() != null
|
||||||
? FileIconManager.getFileIcon(model.getCurrentDirectory(), false)
|
? FileIconManager.getFileIcon(model.getCurrentDirectory())
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
model.getCurrentPath());
|
model.getCurrentPath());
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> createProgressEstimateStatus() {
|
private Comp<?> createProgressEstimateStatus() {
|
||||||
var text = BindingsHelper.map(model.getProgress(), p -> {
|
var text = BindingsHelper.map(model.getProgress(), p -> {
|
||||||
if (p == null || p.done()) {
|
if (p == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
var expected = p.expectedTimeRemaining();
|
var expected = p.expectedTimeRemaining();
|
||||||
|
@ -74,7 +74,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> createProgressStatus() {
|
private Comp<?> createProgressStatus() {
|
||||||
var text = BindingsHelper.map(model.getProgress(), p -> {
|
var text = BindingsHelper.map(model.getProgress(), p -> {
|
||||||
if (p == null || p.done()) {
|
if (p == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
|
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
|
||||||
|
@ -91,7 +91,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> createProgressNameStatus() {
|
private Comp<?> createProgressNameStatus() {
|
||||||
var text = BindingsHelper.map(model.getProgress(), p -> {
|
var text = BindingsHelper.map(model.getProgress(), p -> {
|
||||||
if (p == null || p.done()) {
|
if (p == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return p.getName();
|
return p.getName();
|
||||||
|
|
|
@ -10,12 +10,14 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.DesktopHelper;
|
import io.xpipe.app.util.DesktopHelper;
|
||||||
import io.xpipe.app.util.ShellTemp;
|
import io.xpipe.app.util.ShellTemp;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
@ -133,6 +135,12 @@ public class BrowserTransferModel {
|
||||||
BrowserFileTransferMode.COPY,
|
BrowserFileTransferMode.COPY,
|
||||||
false,
|
false,
|
||||||
progress -> {
|
progress -> {
|
||||||
|
// Don't update item progress to keep it as finished
|
||||||
|
if (progress == null) {
|
||||||
|
item.getOpenFileSystemModel().getProgress().setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (item.getProgress()) {
|
synchronized (item.getProgress()) {
|
||||||
item.getProgress().setValue(progress);
|
item.getProgress().setValue(progress);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +178,7 @@ public class BrowserTransferModel {
|
||||||
if (Files.isDirectory(file)) {
|
if (Files.isDirectory(file)) {
|
||||||
FileUtils.moveDirectory(file.toFile(), target.toFile());
|
FileUtils.moveDirectory(file.toFile(), target.toFile());
|
||||||
} else {
|
} else {
|
||||||
FileUtils.moveFile(file.toFile(), target.toFile(), StandardCopyOption.REPLACE_EXISTING);
|
Files.move(file, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DesktopHelper.browseFileInDirectory(downloads.resolve(files.getFirst().getFileName()));
|
DesktopHelper.browseFileInDirectory(downloads.resolve(files.getFirst().getFileName()));
|
||||||
|
|
|
@ -14,14 +14,6 @@ public class BrowserTransferProgress {
|
||||||
long total;
|
long total;
|
||||||
Instant start;
|
Instant start;
|
||||||
|
|
||||||
public static BrowserTransferProgress empty() {
|
|
||||||
return new BrowserTransferProgress(null, 0, 0, Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
static BrowserTransferProgress empty(String name, long size) {
|
|
||||||
return new BrowserTransferProgress(name, 0, size, Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BrowserTransferProgress finished(String name, long size) {
|
public static BrowserTransferProgress finished(String name, long size) {
|
||||||
return new BrowserTransferProgress(name, size, size, Instant.now());
|
return new BrowserTransferProgress(name, size, size, Instant.now());
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
||||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
var img = new PrettySvgComp(new SimpleStringProperty("Hips.svg"), 50, 75)
|
var img = new PrettySvgComp(new SimpleStringProperty("graphics/Hips.svg"), 50, 75)
|
||||||
.padding(new Insets(5, 0, 0, 0))
|
.padding(new Insets(5, 0, 0, 0))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
|
@ -145,8 +145,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
|
|
||||||
private Comp<?> entryButton(BrowserSavedState.Entry e, BooleanProperty disable) {
|
private Comp<?> entryButton(BrowserSavedState.Entry e, BooleanProperty disable) {
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
var graphic =
|
var graphic = entry.get().getEffectiveIconFile();
|
||||||
entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
|
||||||
var view = PrettyImageHelper.ofFixedSize(graphic, 30, 24);
|
var view = PrettyImageHelper.ofFixedSize(graphic, 30, 24);
|
||||||
return new ButtonComp(
|
return new ButtonComp(
|
||||||
new SimpleStringProperty(DataStorage.get().getStoreEntryDisplayName(entry.get())),
|
new SimpleStringProperty(DataStorage.get().getStoreEntryDisplayName(entry.get())),
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FilePath;
|
import io.xpipe.core.store.FilePath;
|
||||||
|
@ -62,7 +63,8 @@ public class BrowserAlerts {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean showDeleteAlert(List<FileEntry> source) {
|
public static boolean showDeleteAlert(List<FileEntry> source) {
|
||||||
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
if (!AppPrefs.get().confirmDeletions().get()
|
||||||
|
&& source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,11 +65,11 @@ public class BrowserEntry {
|
||||||
if (fileType != null) {
|
if (fileType != null) {
|
||||||
return fileType.getIcon();
|
return fileType.getIcon();
|
||||||
} else if (directoryType != null) {
|
} else if (directoryType != null) {
|
||||||
return directoryType.getIcon(rawFileEntry, false);
|
return directoryType.getIcon(rawFileEntry);
|
||||||
} else {
|
} else {
|
||||||
return rawFileEntry != null && rawFileEntry.resolved().getKind() == FileKind.DIRECTORY
|
return rawFileEntry != null && rawFileEntry.resolved().getKind() == FileKind.DIRECTORY
|
||||||
? "default_folder.svg"
|
? "browser/default_folder.svg"
|
||||||
: "default_file.svg";
|
: "browser/default_file.svg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -194,9 +194,9 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
? unix.getGroup()
|
? unix.getGroup()
|
||||||
: m.getCache().getGroups().getOrDefault(unix.getGid(), "?");
|
: m.getCache().getGroups().getOrDefault(unix.getGid(), "?");
|
||||||
var uid = String.valueOf(
|
var uid = String.valueOf(
|
||||||
unix.getUid() != null ? unix.getUid() : m.getCache().getUidForUser(user));
|
unix.getUid() != null ? unix.getUid() : m.getCache().getUidForUser(user));
|
||||||
var gid = String.valueOf(
|
var gid = String.valueOf(
|
||||||
unix.getGid() != null ? unix.getGid() : m.getCache().getGidForGroup(group));
|
unix.getGid() != null ? unix.getGid() : m.getCache().getGidForGroup(group));
|
||||||
if (uid.equals(gid) && user.equals(group)) {
|
if (uid.equals(gid) && user.equals(group)) {
|
||||||
return user + " [" + uid + "]";
|
return user + " [" + uid + "]";
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,6 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
if (inCooldown) {
|
if (inCooldown) {
|
||||||
lastType.set(Instant.now());
|
lastType.set(Instant.now());
|
||||||
event.consume();
|
event.consume();
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
lastType.set(null);
|
lastType.set(null);
|
||||||
typedSelection.set("");
|
typedSelection.set("");
|
||||||
|
@ -256,8 +255,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
if (!recursive) {
|
if (!recursive) {
|
||||||
updateTypedSelection(table, lastType, event, true);
|
updateTypedSelection(table, lastType, event, true);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastType.set(Instant.now());
|
lastType.set(Instant.now());
|
||||||
|
@ -631,6 +630,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||||
.hide(Bindings.createBooleanBinding(
|
.hide(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
if (getTableRow() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var item = getTableRow().getItem();
|
var item = getTableRow().getItem();
|
||||||
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
||||||
var isParentLink = item.getRawFileEntry()
|
var isParentLink = item.getRawFileEntry()
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class BrowserFileTransferOperation {
|
||||||
|
|
||||||
public void execute() throws Exception {
|
public void execute() throws Exception {
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
updateProgress(BrowserTransferProgress.empty());
|
updateProgress(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,18 +115,22 @@ public class BrowserFileTransferOperation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var file : files) {
|
try {
|
||||||
if (same) {
|
|
||||||
handleSingleOnSameFileSystem(file);
|
|
||||||
} else {
|
|
||||||
handleSingleAcrossFileSystems(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!same && doesMove) {
|
|
||||||
for (var file : files) {
|
for (var file : files) {
|
||||||
deleteSingle(file);
|
if (same) {
|
||||||
|
handleSingleOnSameFileSystem(file);
|
||||||
|
} else {
|
||||||
|
handleSingleAcrossFileSystems(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!same && doesMove) {
|
||||||
|
for (var file : files) {
|
||||||
|
deleteSingle(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
updateProgress(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,8 +142,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||||
this.menu = new Menu(
|
this.menu = new Menu(
|
||||||
// Use original name, not the link target
|
// Use original name, not the link target
|
||||||
browserEntry.getRawFileEntry().getName(),
|
browserEntry.getRawFileEntry().getName(),
|
||||||
PrettyImageHelper.ofFixedRasterized(
|
PrettyImageHelper.ofFixedSize(FileIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24)
|
||||||
FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24)
|
|
||||||
.createRegion());
|
.createRegion());
|
||||||
createMenu();
|
createMenu();
|
||||||
addInputListeners();
|
addInputListeners();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package io.xpipe.app.browser.file;
|
package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.LocalStore;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import io.xpipe.app.ext.LocalStore;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -19,6 +19,13 @@ public class LocalFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void reset() throws Exception {
|
||||||
|
if (localFileSystem != null) {
|
||||||
|
localFileSystem.close();
|
||||||
|
localFileSystem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static FileEntry getLocalFileEntry(Path file) throws Exception {
|
public static FileEntry getLocalFileEntry(Path file) throws Exception {
|
||||||
if (localFileSystem == null) {
|
if (localFileSystem == null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
|
@ -60,7 +60,8 @@ public class OpenFileSystemCache extends ShellControlCache {
|
||||||
var split = s.split(":");
|
var split = s.split(":");
|
||||||
try {
|
try {
|
||||||
users.putIfAbsent(Integer.parseInt(split[2]), split[0]);
|
users.putIfAbsent(Integer.parseInt(split[2]), split[0]);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
|
@ -81,7 +82,8 @@ public class OpenFileSystemCache extends ShellControlCache {
|
||||||
var split = s.split(":");
|
var split = s.split(":");
|
||||||
try {
|
try {
|
||||||
groups.putIfAbsent(Integer.parseInt(split[2]), split[0]);
|
groups.putIfAbsent(Integer.parseInt(split[2]), split[0]);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (groups.isEmpty()) {
|
if (groups.isEmpty()) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import io.xpipe.app.browser.file.FileSystemHelper;
|
||||||
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionTab;
|
import io.xpipe.app.browser.session.BrowserSessionTab;
|
||||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -18,7 +19,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.TerminalLauncher;
|
import io.xpipe.app.util.TerminalLauncher;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.process.ShellDialects;
|
import io.xpipe.core.process.ShellDialects;
|
||||||
import io.xpipe.core.process.ShellOpenFunction;
|
import io.xpipe.core.process.ShellOpenFunction;
|
||||||
|
@ -47,8 +48,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
||||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
||||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||||
private final Property<BrowserTransferProgress> progress =
|
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>();
|
||||||
new SimpleObjectProperty<>(BrowserTransferProgress.empty());
|
|
||||||
private FileSystem fileSystem;
|
private FileSystem fileSystem;
|
||||||
private OpenFileSystemSavedState savedState;
|
private OpenFileSystemSavedState savedState;
|
||||||
private OpenFileSystemCache cache;
|
private OpenFileSystemCache cache;
|
||||||
|
@ -73,10 +73,13 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canImmediatelyClose() {
|
public boolean canImmediatelyClose() {
|
||||||
return !progress.getValue().done()
|
if (fileSystem == null
|
||||||
|| (fileSystem != null
|
|| fileSystem.getShell().isEmpty()
|
||||||
&& fileSystem.getShell().isPresent()
|
|| !fileSystem.getShell().get().getLock().isLocked()) {
|
||||||
&& fileSystem.getShell().get().getLock().isLocked());
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress.getValue() == null || progress.getValue().done();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -252,7 +255,11 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
entry.getEntry(),
|
entry.getEntry(),
|
||||||
name,
|
name,
|
||||||
directory,
|
directory,
|
||||||
fileSystem.getShell().get().singularSubShell(ShellOpenFunction.of(adjustedPath)));
|
fileSystem
|
||||||
|
.getShell()
|
||||||
|
.get()
|
||||||
|
.singularSubShell(
|
||||||
|
ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false)));
|
||||||
} else {
|
} else {
|
||||||
TerminalLauncher.open(
|
TerminalLauncher.open(
|
||||||
entry.getEntry(),
|
entry.getEntry(),
|
||||||
|
@ -453,7 +460,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
return fileSystem == null;
|
return fileSystem == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initWithGivenDirectory(String dir) throws Exception {
|
public void initWithGivenDirectory(String dir) {
|
||||||
cdSync(dir);
|
cdSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.browser.icon;
|
package io.xpipe.app.browser.icon;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppResources;
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
@ -42,8 +42,8 @@ public abstract class BrowserIconDirectoryType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIcon(FileEntry entry, boolean open) {
|
public String getIcon(FileEntry entry) {
|
||||||
return open ? "default_root_folder_opened.svg" : "default_root_folder.svg";
|
return "browser/default_root_folder.svg";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,17 +60,10 @@ public abstract class BrowserIconDirectoryType {
|
||||||
})
|
})
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
var closedIcon = split[2].trim();
|
var closedIcon = "browser/" + split[2].trim();
|
||||||
var openIcon = split[3].trim();
|
var lightClosedIcon = split.length > 4 ? "browser/" + split[4].trim() : closedIcon;
|
||||||
|
|
||||||
var lightClosedIcon = split.length > 4 ? split[4].trim() : closedIcon;
|
ALL.add(new Simple(id, new IconVariant(lightClosedIcon, closedIcon), filter));
|
||||||
var lightOpenIcon = split.length > 4 ? split[5].trim() : openIcon;
|
|
||||||
|
|
||||||
ALL.add(new Simple(
|
|
||||||
id,
|
|
||||||
new IconVariant(lightClosedIcon, closedIcon),
|
|
||||||
new IconVariant(lightOpenIcon, openIcon),
|
|
||||||
filter));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -84,7 +77,7 @@ public abstract class BrowserIconDirectoryType {
|
||||||
|
|
||||||
public abstract boolean matches(FileEntry entry);
|
public abstract boolean matches(FileEntry entry);
|
||||||
|
|
||||||
public abstract String getIcon(FileEntry entry, boolean open);
|
public abstract String getIcon(FileEntry entry);
|
||||||
|
|
||||||
public static class Simple extends BrowserIconDirectoryType {
|
public static class Simple extends BrowserIconDirectoryType {
|
||||||
|
|
||||||
|
@ -92,13 +85,11 @@ public abstract class BrowserIconDirectoryType {
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
||||||
private final IconVariant closed;
|
private final IconVariant closed;
|
||||||
private final IconVariant open;
|
|
||||||
private final Set<String> names;
|
private final Set<String> names;
|
||||||
|
|
||||||
public Simple(String id, IconVariant closed, IconVariant open, Set<String> names) {
|
public Simple(String id, IconVariant closed, Set<String> names) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.closed = closed;
|
this.closed = closed;
|
||||||
this.open = open;
|
|
||||||
this.names = names;
|
this.names = names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +104,8 @@ public abstract class BrowserIconDirectoryType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIcon(FileEntry entry, boolean open) {
|
public String getIcon(FileEntry entry) {
|
||||||
return open ? this.open.getIcon() : this.closed.getIcon();
|
return this.closed.getIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.browser.icon;
|
package io.xpipe.app.browser.icon;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppResources;
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
@ -47,8 +47,8 @@ public abstract class BrowserIconFileType {
|
||||||
return "." + r;
|
return "." + r;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
var darkIcon = split[2].trim();
|
var darkIcon = "browser/" + split[2].trim();
|
||||||
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
|
var lightIcon = (split.length > 3 ? "browser/" + split[3].trim() : darkIcon);
|
||||||
ALL.add(new BrowserIconFileType.Simple(id, lightIcon, darkIcon, filter));
|
ALL.add(new BrowserIconFileType.Simple(id, lightIcon, darkIcon, filter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import io.xpipe.core.store.FileEntry;
|
||||||
public class BrowserIcons {
|
public class BrowserIcons {
|
||||||
|
|
||||||
public static Comp<?> createDefaultFileIcon() {
|
public static Comp<?> createDefaultFileIcon() {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare("default_file.svg", 24);
|
return PrettyImageHelper.ofFixedSizeSquare("browser/default_file.svg", 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createDefaultDirectoryIcon() {
|
public static Comp<?> createDefaultDirectoryIcon() {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare("default_folder.svg", 24);
|
return PrettyImageHelper.ofFixedSizeSquare("browser/default_folder.svg", 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createIcon(BrowserIconFileType type) {
|
public static Comp<?> createIcon(BrowserIconFileType type) {
|
||||||
|
@ -19,6 +19,6 @@ public class BrowserIcons {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createIcon(FileEntry entry) {
|
public static Comp<?> createIcon(FileEntry entry) {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24);
|
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry), 24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package io.xpipe.app.browser.icon;
|
package io.xpipe.app.browser.icon;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppImages;
|
|
||||||
import io.xpipe.app.core.AppResources;
|
|
||||||
import io.xpipe.core.store.FileEntry;
|
import io.xpipe.core.store.FileEntry;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
|
||||||
|
@ -13,12 +11,11 @@ public class FileIconManager {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
BrowserIconFileType.loadDefinitions();
|
BrowserIconFileType.loadDefinitions();
|
||||||
BrowserIconDirectoryType.loadDefinitions();
|
BrowserIconDirectoryType.loadDefinitions();
|
||||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "browser_icons", true, false);
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized String getFileIcon(FileEntry entry, boolean open) {
|
public static synchronized String getFileIcon(FileEntry entry) {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -33,13 +30,11 @@ public class FileIconManager {
|
||||||
} else {
|
} else {
|
||||||
for (var f : BrowserIconDirectoryType.getAll()) {
|
for (var f : BrowserIconDirectoryType.getAll()) {
|
||||||
if (f.matches(r)) {
|
if (f.matches(r)) {
|
||||||
return f.getIcon(r, open);
|
return f.getIcon(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.getKind() == FileKind.DIRECTORY
|
return "browser/" + (r.getKind() == FileKind.DIRECTORY ? "default_folder.svg" : "default_file.svg");
|
||||||
? (open ? "default_folder_opened.svg" : "default_folder.svg")
|
|
||||||
: "default_file.svg";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,8 @@ import io.xpipe.app.comp.base.DialogComp;
|
||||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
@ -30,7 +27,6 @@ import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
|
|
||||||
|
@ -40,7 +36,7 @@ import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class BrowserChooserComp extends SimpleComp {
|
public class BrowserChooserComp extends DialogComp {
|
||||||
|
|
||||||
private final BrowserFileChooserModel model;
|
private final BrowserFileChooserModel model;
|
||||||
|
|
||||||
|
@ -52,24 +48,16 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
||||||
var comp = new BrowserChooserComp(model)
|
DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> {
|
||||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
var comp = new BrowserChooserComp(model);
|
||||||
.apply(struc -> AppFont.normal(struc.get()));
|
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||||
var window = AppWindowHelper.sideWindow(
|
.apply(struc -> AppFont.normal(struc.get()))
|
||||||
AppI18n.get(save ? "saveFileTitle" : "openFileTitle"),
|
.styleClass("browser")
|
||||||
stage -> {
|
.styleClass("chooser");
|
||||||
return comp;
|
return comp;
|
||||||
},
|
});
|
||||||
false,
|
|
||||||
null);
|
|
||||||
model.setOnFinish(fileStores -> {
|
model.setOnFinish(fileStores -> {
|
||||||
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||||
window.close();
|
|
||||||
});
|
|
||||||
window.show();
|
|
||||||
window.setOnHidden(event -> {
|
|
||||||
model.finishWithoutChoice();
|
|
||||||
event.consume();
|
|
||||||
});
|
});
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
model.openFileSystemAsync(store.get(), null, null);
|
model.openFileSystemAsync(store.get(), null, null);
|
||||||
|
@ -78,7 +66,27 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected String finishKey() {
|
||||||
|
return "select";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
model.finishChooser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void discard() {
|
||||||
|
model.finishWithoutChoice();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> content() {
|
||||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
|
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
|
||||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||||
|
@ -96,7 +104,7 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getStore() instanceof ShellStore fileSystem) {
|
if (entry.getStore() instanceof ShellStore) {
|
||||||
model.openFileSystemAsync(entry.ref(), null, busy);
|
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -144,60 +152,33 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
struc.getLeft().setMinWidth(200);
|
struc.getLeft().setMinWidth(200);
|
||||||
struc.getLeft().setMaxWidth(500);
|
struc.getLeft().setMaxWidth(500);
|
||||||
});
|
});
|
||||||
|
return splitPane;
|
||||||
|
}
|
||||||
|
|
||||||
var dialogPane = new DialogComp() {
|
@Override
|
||||||
|
public Comp<?> bottom() {
|
||||||
@Override
|
return Comp.of(() -> {
|
||||||
protected String finishKey() {
|
var selected = new HBox();
|
||||||
return "select";
|
selected.setAlignment(Pos.CENTER_LEFT);
|
||||||
}
|
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
@Override
|
selected.getChildren()
|
||||||
protected Comp<?> pane(Comp<?> content) {
|
.setAll(c.getList().stream()
|
||||||
return content;
|
.map(s -> {
|
||||||
}
|
var field = new TextField(
|
||||||
|
s.getRawFileEntry().getPath());
|
||||||
@Override
|
field.setEditable(false);
|
||||||
protected void finish() {
|
field.getStyleClass().add("chooser-selection");
|
||||||
model.finishChooser();
|
HBox.setHgrow(field, Priority.ALWAYS);
|
||||||
}
|
return field;
|
||||||
|
})
|
||||||
@Override
|
.toList());
|
||||||
public Comp<?> content() {
|
|
||||||
return splitPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Comp<?> bottom() {
|
|
||||||
return Comp.of(() -> {
|
|
||||||
var selected = new HBox();
|
|
||||||
selected.setAlignment(Pos.CENTER_LEFT);
|
|
||||||
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
|
||||||
selected.getChildren()
|
|
||||||
.setAll(c.getList().stream()
|
|
||||||
.map(s -> {
|
|
||||||
var field = new TextField(
|
|
||||||
s.getRawFileEntry().getPath());
|
|
||||||
field.setEditable(false);
|
|
||||||
field.getStyleClass().add("chooser-selection");
|
|
||||||
HBox.setHgrow(field, Priority.ALWAYS);
|
|
||||||
return field;
|
|
||||||
})
|
|
||||||
.toList());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var bottomBar = new HBox(selected);
|
|
||||||
HBox.setHgrow(selected, Priority.ALWAYS);
|
|
||||||
bottomBar.setAlignment(Pos.CENTER);
|
|
||||||
return bottomBar;
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
var bottomBar = new HBox(selected);
|
||||||
|
HBox.setHgrow(selected, Priority.ALWAYS);
|
||||||
var r = dialogPane.createRegion();
|
bottomBar.setAlignment(Pos.CENTER);
|
||||||
r.getStyleClass().add("browser");
|
return bottomBar;
|
||||||
r.getStyleClass().add("chooser");
|
});
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,11 +51,12 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||||
for (var o : new ArrayList<>(sessionEntries)) {
|
for (var o : new ArrayList<>(sessionEntries)) {
|
||||||
// Don't close busy connections gracefully
|
// Don't close busy connections gracefully
|
||||||
// as we otherwise might lock up
|
// as we otherwise might lock up
|
||||||
if (o.canImmediatelyClose()) {
|
if (!o.canImmediatelyClose()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSync(o);
|
// Prevent blocking of shutdown
|
||||||
|
closeAsync(o);
|
||||||
}
|
}
|
||||||
BrowserSavedStateImpl.get().save();
|
BrowserSavedStateImpl.get().save();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
|
@ -238,7 +239,6 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
% tabs.getTabs().size();
|
% tabs.getTabs().size();
|
||||||
tabs.getSelectionModel().select(previous);
|
tabs.getSelectionModel().select(previous);
|
||||||
keyEvent.consume();
|
keyEvent.consume();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -329,12 +329,14 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
ring.setMaxSize(16, 16);
|
ring.setMaxSize(16, 16);
|
||||||
ring.progressProperty()
|
ring.progressProperty()
|
||||||
.bind(Bindings.createDoubleBinding(
|
.bind(Bindings.createDoubleBinding(
|
||||||
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
() -> model.getBusy().get()
|
||||||
|
&& !AppPrefs.get().performanceMode().get()
|
||||||
|
? -1d
|
||||||
|
: 0,
|
||||||
|
PlatformThread.sync(model.getBusy()),
|
||||||
|
AppPrefs.get().performanceMode()));
|
||||||
|
|
||||||
var image = model.getEntry()
|
var image = model.getEntry().get().getEffectiveIconFile();
|
||||||
.get()
|
|
||||||
.getProvider()
|
|
||||||
.getDisplayIconFileName(model.getEntry().getStore());
|
|
||||||
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
||||||
|
|
||||||
tab.graphicProperty()
|
tab.graphicProperty()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
|
||||||
|
import io.xpipe.app.util.TerminalView;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
|
@ -49,14 +50,16 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
||||||
var sidebarR = sidebar.createRegion();
|
var sidebarR = sidebar.createRegion();
|
||||||
pane.setRight(sidebarR);
|
pane.setRight(sidebarR);
|
||||||
model.getSelected().addListener((c, o, n) -> {
|
model.getSelected().addListener((c, o, n) -> {
|
||||||
if (o != null && o.equals(model.getEntries().get(2))) {
|
if (o != null && o.equals(model.getEntries().get(3))) {
|
||||||
AppPrefs.get().save();
|
AppPrefs.get().save();
|
||||||
DataStorage.get().saveAsync();
|
DataStorage.get().saveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o != null && o.equals(model.getEntries().get(1))) {
|
if (o != null && o.equals(model.getEntries().get(0))) {
|
||||||
StoreViewState.get().updateDisplay();
|
StoreViewState.get().updateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TerminalView.get().toggleView(model.getEntries().get(2).equals(n));
|
||||||
});
|
});
|
||||||
pane.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
pane.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||||
sidebarR.getChildrenUnmodifiable().forEach(node -> {
|
sidebarR.getChildrenUnmodifiable().forEach(node -> {
|
||||||
|
@ -64,7 +67,6 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
||||||
if (shortcut != null && shortcut.match(event)) {
|
if (shortcut != null && shortcut.match(event)) {
|
||||||
((ButtonBase) ((Parent) node).getChildrenUnmodifiable().get(1)).fire();
|
((ButtonBase) ((Parent) node).getChildrenUnmodifiable().get(1)).fire();
|
||||||
event.consume();
|
event.consume();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,22 +20,30 @@ import javafx.stage.Stage;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
|
|
||||||
public static void showWindow(String titleKey, Function<Stage, DialogComp> f) {
|
public static void showWindow(String titleKey, Function<Stage, DialogComp> f) {
|
||||||
var loading = new SimpleBooleanProperty();
|
var loading = new SimpleBooleanProperty();
|
||||||
|
var dialog = new AtomicReference<DialogComp>();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
var stage = AppWindowHelper.sideWindow(
|
var stage = AppWindowHelper.sideWindow(
|
||||||
AppI18n.get(titleKey),
|
AppI18n.get(titleKey),
|
||||||
window -> {
|
window -> {
|
||||||
var c = f.apply(window);
|
var c = f.apply(window);
|
||||||
|
dialog.set(c);
|
||||||
loading.bind(c.busy());
|
loading.bind(c.busy());
|
||||||
return c;
|
return c;
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
loading);
|
loading);
|
||||||
|
stage.setOnCloseRequest(event -> {
|
||||||
|
if (dialog.get() != null) {
|
||||||
|
dialog.get().discard();
|
||||||
|
}
|
||||||
|
});
|
||||||
stage.show();
|
stage.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -60,12 +68,16 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
.addAll(customButtons().stream()
|
.addAll(customButtons().stream()
|
||||||
.map(buttonComp -> buttonComp.createRegion())
|
.map(buttonComp -> buttonComp.createRegion())
|
||||||
.toList());
|
.toList());
|
||||||
var nextButton = new ButtonComp(AppI18n.observable(finishKey()), null, this::finish)
|
var nextButton = finishButton();
|
||||||
|
buttons.getChildren().add(nextButton.createRegion());
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Comp<?> finishButton() {
|
||||||
|
return new ButtonComp(AppI18n.observable(finishKey()), null, this::finish)
|
||||||
.apply(struc -> struc.get().setDefaultButton(true))
|
.apply(struc -> struc.get().setDefaultButton(true))
|
||||||
.styleClass(Styles.ACCENT)
|
.styleClass(Styles.ACCENT)
|
||||||
.styleClass("next");
|
.styleClass("next");
|
||||||
buttons.getChildren().add(nextButton.createRegion());
|
|
||||||
return buttons;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String finishKey() {
|
protected String finishKey() {
|
||||||
|
@ -93,6 +105,8 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
|
|
||||||
protected abstract void finish();
|
protected abstract void finish();
|
||||||
|
|
||||||
|
protected abstract void discard();
|
||||||
|
|
||||||
public abstract Comp<?> content();
|
public abstract Comp<?> content();
|
||||||
|
|
||||||
protected Comp<?> pane(Comp<?> content) {
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
|
|
|
@ -26,6 +26,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
|
|
||||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
|
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
|
||||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
|
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
|
||||||
|
private static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
|
||||||
|
private static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
|
||||||
|
|
||||||
private final ObservableList<T> shown;
|
private final ObservableList<T> shown;
|
||||||
private final ObservableList<T> all;
|
private final ObservableList<T> all;
|
||||||
|
@ -114,9 +116,10 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
|
|
||||||
for (int i = 0; i < newShown.size(); i++) {
|
for (int i = 0; i < newShown.size(); i++) {
|
||||||
var r = newShown.get(i);
|
var r = newShown.get(i);
|
||||||
r.pseudoClassStateChanged(ODD, false);
|
r.pseudoClassStateChanged(ODD, i % 2 != 0);
|
||||||
r.pseudoClassStateChanged(EVEN, false);
|
r.pseudoClassStateChanged(EVEN, i % 2 == 0);
|
||||||
r.pseudoClassStateChanged(i % 2 == 0 ? EVEN : ODD, true);
|
r.pseudoClassStateChanged(FIRST, i == 0);
|
||||||
|
r.pseudoClassStateChanged(LAST, i == newShown.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.AppResources;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
import io.xpipe.app.util.MarkdownHelper;
|
import io.xpipe.app.util.MarkdownHelper;
|
||||||
import io.xpipe.app.util.ShellTemp;
|
import io.xpipe.app.util.ShellTemp;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.core.AppResources;
|
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.process.OsNameState;
|
import io.xpipe.core.process.OsNameState;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
|
@ -22,8 +22,7 @@ import java.util.Map;
|
||||||
public class OsLogoComp extends SimpleComp {
|
public class OsLogoComp extends SimpleComp {
|
||||||
|
|
||||||
private static final Map<String, String> ICONS = new HashMap<>();
|
private static final Map<String, String> ICONS = new HashMap<>();
|
||||||
private static final String LINUX_DEFAULT = "linux-24.png";
|
private static final String LINUX_DEFAULT_24 = "linux-24.png";
|
||||||
private static final String LINUX_DEFAULT_SVG = "linux.svg";
|
|
||||||
private final StoreEntryWrapper wrapper;
|
private final StoreEntryWrapper wrapper;
|
||||||
private final ObservableValue<SystemStateComp.State> state;
|
private final ObservableValue<SystemStateComp.State> state;
|
||||||
|
|
||||||
|
@ -54,8 +53,9 @@ public class OsLogoComp extends SimpleComp {
|
||||||
wrapper.getPersistentState(),
|
wrapper.getPersistentState(),
|
||||||
state);
|
state);
|
||||||
var hide = BindingsHelper.map(img, s -> s != null);
|
var hide = BindingsHelper.map(img, s -> s != null);
|
||||||
return new StackComp(
|
return new StackComp(List.of(
|
||||||
List.of(new SystemStateComp(state).hide(hide), new PrettyImageComp(img, 24, 24).visible(hide)))
|
new SystemStateComp(state).hide(hide),
|
||||||
|
PrettyImageHelper.ofFixedSize(img, 24, 24).visible(hide)))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,11 +67,12 @@ public class OsLogoComp extends SimpleComp {
|
||||||
if (ICONS.isEmpty()) {
|
if (ICONS.isEmpty()) {
|
||||||
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
|
AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> {
|
||||||
try (var list = Files.list(file)) {
|
try (var list = Files.list(file)) {
|
||||||
list.filter(path -> path.toString().endsWith(".svg")
|
list.filter(path -> path.toString().endsWith(".png")
|
||||||
&& !path.toString().endsWith(LINUX_DEFAULT_SVG))
|
&& !path.toString().endsWith(LINUX_DEFAULT_24)
|
||||||
|
&& !path.toString().endsWith("-40.png"))
|
||||||
.map(path -> FileNames.getFileName(path.toString()))
|
.map(path -> FileNames.getFileName(path.toString()))
|
||||||
.forEach(path -> {
|
.forEach(path -> {
|
||||||
var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png";
|
var base = path.replace("-dark", "").replace("-24.png", ".svg");
|
||||||
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
|
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -82,6 +83,6 @@ public class OsLogoComp extends SimpleComp {
|
||||||
.filter(e -> name.toLowerCase().contains(e.getKey()))
|
.filter(e -> name.toLowerCase().contains(e.getKey()))
|
||||||
.findAny()
|
.findAny()
|
||||||
.map(e -> e.getValue())
|
.map(e -> e.getValue())
|
||||||
.orElse("os/" + LINUX_DEFAULT);
|
.orElse("os/linux.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.window.AppMainWindow;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.util.TerminalView;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
|
||||||
|
public class TerminalViewDockComp extends SimpleComp {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var label = new Label();
|
||||||
|
label.textProperty().bind(AppI18n.observable("clickToDock"));
|
||||||
|
var stack = new StackPane(label);
|
||||||
|
stack.setAlignment(Pos.CENTER);
|
||||||
|
stack.setCursor(Cursor.HAND);
|
||||||
|
stack.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update(stack);
|
||||||
|
});
|
||||||
|
var s = AppMainWindow.getInstance().getStage();
|
||||||
|
s.xProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update(stack);
|
||||||
|
});
|
||||||
|
s.yProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update(stack);
|
||||||
|
});
|
||||||
|
s.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update(stack);
|
||||||
|
});
|
||||||
|
s.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
update(stack);
|
||||||
|
});
|
||||||
|
s.iconifiedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
TerminalView.get().onMinimize();
|
||||||
|
} else {
|
||||||
|
TerminalView.get().onFocusGain();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
s.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
TerminalView.get().onFocusGain();
|
||||||
|
} else {
|
||||||
|
TerminalView.get().onFocusLost();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
s.addEventFilter(WindowEvent.WINDOW_HIDDEN,event -> {
|
||||||
|
TerminalView.get().onClose();
|
||||||
|
});
|
||||||
|
stack.setOnMouseClicked(event -> {
|
||||||
|
TerminalView.get().clickView();
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(Region region) {
|
||||||
|
var bounds = region.localToScreen(region.getBoundsInLocal());
|
||||||
|
TerminalView.get().resizeView((int) bounds.getMinX(), (int) bounds.getMinY(),(int) bounds.getWidth(), (int) bounds.getHeight());
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.*;
|
import io.xpipe.app.util.*;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.store.ValidationContext;
|
||||||
import io.xpipe.core.util.ValidationException;
|
import io.xpipe.core.util.ValidationException;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -42,14 +43,13 @@ import net.synedra.validatorfx.GraphicDecorationStackPane;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
public class StoreCreationComp extends DialogComp {
|
public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
Stage window;
|
Stage window;
|
||||||
BiConsumer<DataStoreEntry, Boolean> consumer;
|
CreationConsumer consumer;
|
||||||
Property<DataStoreProvider> provider;
|
Property<DataStoreProvider> provider;
|
||||||
ObjectProperty<DataStore> store;
|
ObjectProperty<DataStore> store;
|
||||||
Predicate<DataStoreProvider> filter;
|
Predicate<DataStoreProvider> filter;
|
||||||
|
@ -67,7 +67,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
public StoreCreationComp(
|
public StoreCreationComp(
|
||||||
Stage window,
|
Stage window,
|
||||||
BiConsumer<DataStoreEntry, Boolean> consumer,
|
CreationConsumer consumer,
|
||||||
Property<DataStoreProvider> provider,
|
Property<DataStoreProvider> provider,
|
||||||
ObjectProperty<DataStore> store,
|
ObjectProperty<DataStore> store,
|
||||||
Predicate<DataStoreProvider> filter,
|
Predicate<DataStoreProvider> filter,
|
||||||
|
@ -165,8 +165,11 @@ public class StoreCreationComp extends DialogComp {
|
||||||
e.getProvider(),
|
e.getProvider(),
|
||||||
e.getStore(),
|
e.getStore(),
|
||||||
v -> true,
|
v -> true,
|
||||||
(newE, validated) -> {
|
(newE, context, validated) -> {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
|
if (context != null) {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
if (!DataStorage.get().getStoreEntries().contains(e)) {
|
if (!DataStorage.get().getStoreEntries().contains(e)) {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(newE);
|
DataStorage.get().addStoreEntryIfNotPresent(newE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -193,15 +196,16 @@ public class StoreCreationComp extends DialogComp {
|
||||||
base != null ? DataStoreProviders.byStore(base) : null,
|
base != null ? DataStoreProviders.byStore(base) : null,
|
||||||
base,
|
base,
|
||||||
dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()),
|
dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()),
|
||||||
(e, validated) -> {
|
(e, context, validated) -> {
|
||||||
try {
|
try {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||||
if (validated
|
if (context != null
|
||||||
|
&& validated
|
||||||
&& e.getProvider().shouldShowScan()
|
&& e.getProvider().shouldShowScan()
|
||||||
&& AppPrefs.get()
|
&& AppPrefs.get()
|
||||||
.openConnectionSearchWindowOnConnectionCreation()
|
.openConnectionSearchWindowOnConnectionCreation()
|
||||||
.get()) {
|
.get()) {
|
||||||
ScanAlert.showAsync(e);
|
ScanAlert.showAsync(e, context);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
@ -211,12 +215,17 @@ public class StoreCreationComp extends DialogComp {
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface CreationConsumer {
|
||||||
|
|
||||||
|
void consume(DataStoreEntry entry, ValidationContext<?> validationContext, boolean validated);
|
||||||
|
}
|
||||||
|
|
||||||
private static void show(
|
private static void show(
|
||||||
String initialName,
|
String initialName,
|
||||||
DataStoreProvider provider,
|
DataStoreProvider provider,
|
||||||
DataStore s,
|
DataStore s,
|
||||||
Predicate<DataStoreProvider> filter,
|
Predicate<DataStoreProvider> filter,
|
||||||
BiConsumer<DataStoreEntry, Boolean> con,
|
CreationConsumer con,
|
||||||
boolean staticDisplay,
|
boolean staticDisplay,
|
||||||
DataStoreEntry existingEntry) {
|
DataStoreEntry existingEntry) {
|
||||||
var prop = new SimpleObjectProperty<>(provider);
|
var prop = new SimpleObjectProperty<>(provider);
|
||||||
|
@ -247,7 +256,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
return List.of(
|
return List.of(
|
||||||
new ButtonComp(AppI18n.observable("skip"), null, () -> {
|
new ButtonComp(AppI18n.observable("skip"), null, () -> {
|
||||||
if (showInvalidConfirmAlert()) {
|
if (showInvalidConfirmAlert()) {
|
||||||
commit(false);
|
commit(null, false);
|
||||||
} else {
|
} else {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -275,6 +284,9 @@ public class StoreCreationComp extends DialogComp {
|
||||||
return busy;
|
return busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void discard() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finish() {
|
protected void finish() {
|
||||||
if (finished.get()) {
|
if (finished.get()) {
|
||||||
|
@ -287,7 +299,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
// We didn't change anything
|
// We didn't change anything
|
||||||
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
||||||
commit(false);
|
commit(null, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,10 +327,10 @@ public class StoreCreationComp extends DialogComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var b = new BooleanScope(busy).start()) {
|
try (var ignored = new BooleanScope(busy).start()) {
|
||||||
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
||||||
entry.getValue().validateOrThrow();
|
var context = entry.getValue().validateAndKeepOpenOrThrowAndClose(null);
|
||||||
commit(true);
|
commit(context, true);
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
if (ex instanceof ValidationException) {
|
if (ex instanceof ValidationException) {
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
|
@ -403,14 +415,14 @@ public class StoreCreationComp extends DialogComp {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commit(boolean validated) {
|
private void commit(ValidationContext<?> validationContext, boolean validated) {
|
||||||
if (finished.get()) {
|
if (finished.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
finished.setValue(true);
|
finished.setValue(true);
|
||||||
|
|
||||||
if (entry.getValue() != null) {
|
if (entry.getValue() != null) {
|
||||||
consumer.accept(entry.getValue(), validated);
|
consumer.consume(entry.getValue(), validationContext, validated);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class StoreCreationMenu {
|
||||||
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
||||||
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
||||||
automatically.setOnAction(event -> {
|
automatically.setOnAction(event -> {
|
||||||
ScanAlert.showAsync(null);
|
ScanAlert.showAsync(null, null);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(automatically);
|
menu.getItems().add(automatically);
|
||||||
|
@ -32,17 +32,21 @@ public class StoreCreationMenu {
|
||||||
|
|
||||||
menu.getItems().add(category("addDesktop", "mdi2c-camera-plus", DataStoreCreationCategory.DESKTOP, null));
|
menu.getItems().add(category("addDesktop", "mdi2c-camera-plus", DataStoreCreationCategory.DESKTOP, null));
|
||||||
|
|
||||||
menu.getItems().add(category("addShell", "mdi2t-text-box-multiple", DataStoreCreationCategory.SHELL, "shellEnvironment"));
|
menu.getItems()
|
||||||
|
.add(category(
|
||||||
|
"addShell", "mdi2t-text-box-multiple", DataStoreCreationCategory.SHELL, "shellEnvironment"));
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category("addScript", "mdi2s-script-text-outline", DataStoreCreationCategory.SCRIPT, "script"));
|
.add(category("addScript", "mdi2s-script-text-outline", DataStoreCreationCategory.SCRIPT, "script"));
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category("addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "customService"));
|
.add(category(
|
||||||
|
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "customService"));
|
||||||
|
|
||||||
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
|
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
|
||||||
|
|
||||||
// menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE, null));
|
// menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE,
|
||||||
|
// null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MenuItem category(
|
private static MenuItem category(
|
||||||
|
@ -85,8 +89,7 @@ public class StoreCreationMenu {
|
||||||
.sorted(Comparator.comparingInt(dataStoreProvider -> dataStoreProvider.getOrderPriority()))
|
.sorted(Comparator.comparingInt(dataStoreProvider -> dataStoreProvider.getOrderPriority()))
|
||||||
.toList();
|
.toList();
|
||||||
int lastOrder = providers.getFirst().getOrderPriority();
|
int lastOrder = providers.getFirst().getOrderPriority();
|
||||||
for (int i = 0; i < providers.size(); i++) {
|
for (io.xpipe.app.ext.DataStoreProvider dataStoreProvider : providers) {
|
||||||
var dataStoreProvider = providers.get(i);
|
|
||||||
if (dataStoreProvider.getOrderPriority() != lastOrder) {
|
if (dataStoreProvider.getOrderPriority() != lastOrder) {
|
||||||
menu.getItems().add(new SeparatorMenuItem());
|
menu.getItems().add(new SeparatorMenuItem());
|
||||||
lastOrder = dataStoreProvider.getOrderPriority();
|
lastOrder = dataStoreProvider.getOrderPriority();
|
||||||
|
|
|
@ -10,12 +10,12 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
|
||||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
@ -33,7 +33,6 @@ import javafx.scene.control.*;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
|
|
||||||
import atlantafx.base.layout.InputGroup;
|
import atlantafx.base.layout.InputGroup;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
@ -192,26 +191,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Node createIcon(int w, int h) {
|
protected Node createIcon(int w, int h) {
|
||||||
var img = getWrapper().disabledProperty().get()
|
return new StoreIconComp(getWrapper(), w, h).createRegion();
|
||||||
? "disabled_icon.png"
|
|
||||||
: getWrapper()
|
|
||||||
.getEntry()
|
|
||||||
.getProvider()
|
|
||||||
.getDisplayIconFileName(getWrapper().getEntry().getStore());
|
|
||||||
var imageComp = PrettyImageHelper.ofFixedSize(img, w, h);
|
|
||||||
var storeIcon = imageComp.createRegion();
|
|
||||||
if (getWrapper().getValidity().getValue().isUsable()) {
|
|
||||||
new TooltipAugment<>(getWrapper().getEntry().getProvider().displayName(), null).augment(storeIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stack = new StackPane(storeIcon);
|
|
||||||
stack.setMinHeight(w + 7);
|
|
||||||
stack.setMinWidth(w + 7);
|
|
||||||
stack.setMaxHeight(w + 7);
|
|
||||||
stack.setMaxWidth(w + 7);
|
|
||||||
stack.getStyleClass().add("icon");
|
|
||||||
stack.setAlignment(Pos.CENTER);
|
|
||||||
return stack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Region createButtonBar() {
|
protected Region createButtonBar() {
|
||||||
|
@ -265,12 +245,14 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
button.apply(new ContextMenuAugment<>(
|
button.apply(new ContextMenuAugment<>(
|
||||||
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
|
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
|
||||||
var cm = ContextMenuHelper.create();
|
var cm = ContextMenuHelper.create();
|
||||||
branch.getChildren(getWrapper().getEntry().ref()).forEach(childProvider -> {
|
branch.getChildren(getWrapper().getEntry().ref()).stream()
|
||||||
var menu = buildMenuItemForAction(childProvider);
|
.filter(actionProvider -> getWrapper().showActionProvider(actionProvider))
|
||||||
if (menu != null) {
|
.forEach(childProvider -> {
|
||||||
cm.getItems().add(menu);
|
var menu = buildMenuItemForAction(childProvider);
|
||||||
}
|
if (menu != null) {
|
||||||
});
|
cm.getItems().add(menu);
|
||||||
|
}
|
||||||
|
});
|
||||||
return cm;
|
return cm;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -341,14 +323,16 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
if (DataStorage.get().isRootEntry(getWrapper().getEntry())) {
|
if (DataStorage.get().isRootEntry(getWrapper().getEntry())) {
|
||||||
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
||||||
var none = new MenuItem("None");
|
var none = new MenuItem();
|
||||||
|
none.textProperty().bind(AppI18n.observable("none"));
|
||||||
none.setOnAction(event -> {
|
none.setOnAction(event -> {
|
||||||
getWrapper().getEntry().setColor(null);
|
getWrapper().getEntry().setColor(null);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
color.getItems().add(none);
|
color.getItems().add(none);
|
||||||
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
|
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
|
||||||
MenuItem m = new MenuItem(DataStoreFormatter.capitalize(dataStoreColor.getId()));
|
MenuItem m = new MenuItem();
|
||||||
|
m.textProperty().bind(AppI18n.observable(dataStoreColor.getId()));
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
getWrapper().getEntry().setColor(dataStoreColor);
|
getWrapper().getEntry().setColor(dataStoreColor);
|
||||||
event.consume();
|
event.consume();
|
||||||
|
@ -463,6 +447,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
if (branch != null) {
|
if (branch != null) {
|
||||||
var items = branch.getChildren(getWrapper().getEntry().ref()).stream()
|
var items = branch.getChildren(getWrapper().getEntry().ref()).stream()
|
||||||
|
.filter(actionProvider -> getWrapper().showActionProvider(actionProvider))
|
||||||
.map(c -> buildMenuItemForAction(c))
|
.map(c -> buildMenuItemForAction(c))
|
||||||
.toList();
|
.toList();
|
||||||
menu.getItems().addAll(items);
|
menu.getItems().addAll(items);
|
||||||
|
@ -475,6 +460,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
getWrapper()
|
getWrapper()
|
||||||
.runAction(leaf.createAction(getWrapper().getEntry().ref()), leaf.showBusy());
|
.runAction(leaf.createAction(getWrapper().getEntry().ref()), leaf.showBusy());
|
||||||
});
|
});
|
||||||
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(run);
|
menu.getItems().add(run);
|
||||||
|
|
||||||
|
@ -493,6 +479,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
.getName(getWrapper().getEntry().ref())
|
.getName(getWrapper().getEntry().ref())
|
||||||
.getValue() + ")");
|
.getValue() + ")");
|
||||||
});
|
});
|
||||||
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(sc);
|
menu.getItems().add(sc);
|
||||||
|
|
||||||
|
@ -504,6 +491,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
AppActionLinkDetector.setLastDetectedAction(url);
|
AppActionLinkDetector.setLastDetectedAction(url);
|
||||||
ClipboardHelper.copyUrl(url);
|
ClipboardHelper.copyUrl(url);
|
||||||
});
|
});
|
||||||
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(l);
|
menu.getItems().add(l);
|
||||||
}
|
}
|
||||||
|
@ -518,10 +506,13 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.consume();
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
getWrapper().runAction(leaf.createAction(getWrapper().getEntry().ref()), leaf.showBusy());
|
getWrapper().runAction(leaf.createAction(getWrapper().getEntry().ref()), leaf.showBusy());
|
||||||
});
|
});
|
||||||
|
event.consume();
|
||||||
|
if (event.getTarget() instanceof Menu m) {
|
||||||
|
m.getParentPopup().hide();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.MultiContentComp;
|
import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
|
||||||
|
@ -34,6 +35,12 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
struc.get().setVvalue(0);
|
struc.get().setVvalue(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
content.apply(struc -> {
|
||||||
|
// Reset scroll
|
||||||
|
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||||
|
struc.get().setVvalue(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
return content.styleClass("store-list-comp");
|
return content.styleClass("store-list-comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +51,8 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
() -> {
|
() -> {
|
||||||
var allCat = StoreViewState.get().getAllConnectionsCategory();
|
var allCat = StoreViewState.get().getAllConnectionsCategory();
|
||||||
var connections = StoreViewState.get().getAllEntries().getList().stream()
|
var connections = StoreViewState.get().getAllEntries().getList().stream()
|
||||||
.filter(wrapper -> allCat.equals(wrapper.getCategory().getValue().getRoot()))
|
.filter(wrapper -> allCat.equals(
|
||||||
|
wrapper.getCategory().getValue().getRoot()))
|
||||||
.toList();
|
.toList();
|
||||||
return initialCount == connections.size()
|
return initialCount == connections.size()
|
||||||
&& StoreViewState.get()
|
&& StoreViewState.get()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
@ -72,8 +73,13 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||||
// But it is good enough.
|
// But it is good enough.
|
||||||
var showProvider = true;
|
var showProvider = true;
|
||||||
try {
|
try {
|
||||||
showProvider = storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
|
showProvider = storeEntryWrapper.getEntry().getProvider() == null
|
||||||
} catch (Exception ignored) {}
|
|| storeEntryWrapper
|
||||||
|
.getEntry()
|
||||||
|
.getProvider()
|
||||||
|
.shouldShow(storeEntryWrapper);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
return inRootCategory && showProvider;
|
return inRootCategory && showProvider;
|
||||||
},
|
},
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
|
@ -143,15 +149,15 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comp<?> createAlphabeticalSortButton() {
|
private Comp<?> createAlphabeticalSortButton() {
|
||||||
var icon = Bindings.createStringBinding(
|
var icon = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
|
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
|
||||||
return "mdi2s-sort-alphabetical-descending";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-alphabetical-descending");
|
||||||
}
|
}
|
||||||
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
|
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
|
||||||
return "mdi2s-sort-alphabetical-ascending";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-alphabetical-ascending");
|
||||||
}
|
}
|
||||||
return "mdi2s-sort-alphabetical-descending";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-alphabetical-descending");
|
||||||
},
|
},
|
||||||
sortMode);
|
sortMode);
|
||||||
var alphabetical = new IconButtonComp(icon, () -> {
|
var alphabetical = new IconButtonComp(icon, () -> {
|
||||||
|
@ -184,15 +190,15 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comp<?> createDateSortButton() {
|
private Comp<?> createDateSortButton() {
|
||||||
var icon = Bindings.createStringBinding(
|
var icon = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
|
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
|
||||||
return "mdi2s-sort-clock-ascending-outline";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-clock-ascending-outline");
|
||||||
}
|
}
|
||||||
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
|
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
|
||||||
return "mdi2s-sort-clock-descending-outline";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-clock-descending-outline");
|
||||||
}
|
}
|
||||||
return "mdi2s-sort-clock-ascending-outline";
|
return new LabelGraphic.IconGraphic("mdi2s-sort-clock-ascending-outline");
|
||||||
},
|
},
|
||||||
sortMode);
|
sortMode);
|
||||||
var date = new IconButtonComp(icon, () -> {
|
var date = new IconButtonComp(icon, () -> {
|
||||||
|
|
|
@ -17,7 +17,9 @@ import lombok.Getter;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class StoreEntryWrapper {
|
public class StoreEntryWrapper {
|
||||||
|
@ -40,6 +42,8 @@ public class StoreEntryWrapper {
|
||||||
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
|
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
|
||||||
private final Property<String> summary = new SimpleObjectProperty<>();
|
private final Property<String> summary = new SimpleObjectProperty<>();
|
||||||
private final Property<StoreNotes> notes;
|
private final Property<StoreNotes> notes;
|
||||||
|
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
||||||
|
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public StoreEntryWrapper(DataStoreEntry entry) {
|
public StoreEntryWrapper(DataStoreEntry entry) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
|
@ -137,6 +141,8 @@ public class StoreEntryWrapper {
|
||||||
}
|
}
|
||||||
color.setValue(entry.getColor());
|
color.setValue(entry.getColor());
|
||||||
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
||||||
|
customIcon.setValue(entry.getIcon());
|
||||||
|
iconFile.setValue(entry.getEffectiveIconFile());
|
||||||
|
|
||||||
busy.setValue(entry.getBusyCounter().get() != 0);
|
busy.setValue(entry.getBusyCounter().get() != 0);
|
||||||
deletable.setValue(entry.getConfiguration().isDeletable()
|
deletable.setValue(entry.getConfiguration().isDeletable()
|
||||||
|
@ -191,7 +197,7 @@ public class StoreEntryWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean showActionProvider(ActionProvider p) {
|
public boolean showActionProvider(ActionProvider p) {
|
||||||
var leaf = p.getLeafDataStoreCallSite();
|
var leaf = p.getLeafDataStoreCallSite();
|
||||||
if (leaf != null) {
|
if (leaf != null) {
|
||||||
return (entry.getValidity().isUsable() || (!leaf.requiresValidStore() && entry.getProvider() != null))
|
return (entry.getValidity().isUsable() || (!leaf.requiresValidStore() && entry.getProvider() != null))
|
||||||
|
@ -214,7 +220,7 @@ public class StoreEntryWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshChildren() {
|
public void refreshChildren() {
|
||||||
var hasChildren = DataStorage.get().refreshChildren(entry);
|
var hasChildren = DataStorage.get().refreshChildren(entry, null);
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
expanded.set(hasChildren);
|
expanded.set(hasChildren);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
|
import io.xpipe.app.resources.SystemIcon;
|
||||||
|
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Tweaks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static atlantafx.base.theme.Styles.TEXT_SMALL;
|
||||||
|
|
||||||
|
public class StoreIconChoiceComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final Property<SystemIcon> selected;
|
||||||
|
private final List<SystemIcon> icons;
|
||||||
|
private final int columns;
|
||||||
|
private final SimpleStringProperty filter;
|
||||||
|
private final Runnable doubleClick;
|
||||||
|
|
||||||
|
public StoreIconChoiceComp(
|
||||||
|
Property<SystemIcon> selected,
|
||||||
|
List<SystemIcon> icons,
|
||||||
|
int columns,
|
||||||
|
SimpleStringProperty filter,
|
||||||
|
Runnable doubleClick) {
|
||||||
|
this.selected = selected;
|
||||||
|
this.icons = icons;
|
||||||
|
this.columns = columns;
|
||||||
|
this.filter = filter;
|
||||||
|
this.doubleClick = doubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var table = new TableView<List<SystemIcon>>();
|
||||||
|
initTable(table);
|
||||||
|
updateData(table, null);
|
||||||
|
filter.addListener((observable, oldValue, newValue) -> updateData(table, newValue));
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTable(TableView<List<SystemIcon>> table) {
|
||||||
|
for (int i = 0; i < columns; i++) {
|
||||||
|
var col = new TableColumn<List<SystemIcon>, SystemIcon>("col" + i);
|
||||||
|
final int colIndex = i;
|
||||||
|
col.setCellValueFactory(cb -> {
|
||||||
|
var row = cb.getValue();
|
||||||
|
var item = row.size() > colIndex ? row.get(colIndex) : null;
|
||||||
|
return new SimpleObjectProperty<>(item);
|
||||||
|
});
|
||||||
|
col.setCellFactory(cb -> new IconCell());
|
||||||
|
col.getStyleClass().add(Tweaks.ALIGN_CENTER);
|
||||||
|
table.getColumns().add(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||||
|
table.getSelectionModel().setCellSelectionEnabled(true);
|
||||||
|
table.getStyleClass().add("icon-browser");
|
||||||
|
table.setPlaceholder(new Region());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateData(TableView<List<SystemIcon>> table, String filterString) {
|
||||||
|
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < 2
|
||||||
|
? icons
|
||||||
|
: icons.stream()
|
||||||
|
.filter(icon -> containsString(icon.getDisplayName(), filterString))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
var data = partitionList(displayedIcons, columns);
|
||||||
|
table.getItems().setAll(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
|
||||||
|
List<List<T>> partitions = new ArrayList<>();
|
||||||
|
if (list.size() == 0) {
|
||||||
|
return partitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = list.size();
|
||||||
|
int numOfPartitions = length / size + ((length % size == 0) ? 0 : 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < numOfPartitions; i++) {
|
||||||
|
int from = i * size;
|
||||||
|
int to = Math.min((i * size + size), length);
|
||||||
|
partitions.add(list.subList(from, to));
|
||||||
|
}
|
||||||
|
return partitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsString(String s1, String s2) {
|
||||||
|
return s1.toLowerCase(Locale.ROOT).contains(s2.toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IconCell extends TableCell<List<SystemIcon>, SystemIcon> {
|
||||||
|
|
||||||
|
private final Label root = new Label();
|
||||||
|
private final StringProperty image = new SimpleStringProperty();
|
||||||
|
|
||||||
|
public IconCell() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
root.setContentDisplay(ContentDisplay.TOP);
|
||||||
|
Region imageView = PrettyImageHelper.ofFixedSize(image, 40, 40).createRegion();
|
||||||
|
root.setGraphic(imageView);
|
||||||
|
root.setGraphicTextGap(10);
|
||||||
|
root.getStyleClass().addAll("icon-label", TEXT_SMALL);
|
||||||
|
|
||||||
|
setOnMouseClicked(event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY) {
|
||||||
|
selected.setValue(getItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getClickCount() > 1) {
|
||||||
|
doubleClick.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(SystemIcon icon, boolean empty) {
|
||||||
|
super.updateItem(icon, empty);
|
||||||
|
|
||||||
|
if (icon == null) {
|
||||||
|
setGraphic(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setText(icon.getDisplayName());
|
||||||
|
image.set("app:system/" + icon.getIconName() + ".svg");
|
||||||
|
setGraphic(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
|
import io.xpipe.app.comp.base.DialogComp;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
|
import io.xpipe.app.resources.SystemIcon;
|
||||||
|
import io.xpipe.app.resources.SystemIcons;
|
||||||
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StoreIconChoiceDialogComp extends SimpleComp {
|
||||||
|
|
||||||
|
public static void show(DataStoreEntry entry) {
|
||||||
|
var window = AppWindowHelper.sideWindow(
|
||||||
|
AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(entry, stage), false, null);
|
||||||
|
window.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
window.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObjectProperty<SystemIcon> selected = new SimpleObjectProperty<>();
|
||||||
|
private final DataStoreEntry entry;
|
||||||
|
private final Stage dialogStage;
|
||||||
|
|
||||||
|
public StoreIconChoiceDialogComp(DataStoreEntry entry, Stage dialogStage) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.dialogStage = dialogStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var filterText = new SimpleStringProperty();
|
||||||
|
var filter = new FilterComp(filterText).apply(struc -> {
|
||||||
|
dialogStage.setOnShowing(event -> {
|
||||||
|
struc.get().requestFocus();
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var github = new ButtonComp(null, new FontIcon("mdi2g-github"), () -> {
|
||||||
|
Hyperlinks.open(Hyperlinks.SELFHST_ICONS);
|
||||||
|
})
|
||||||
|
.grow(false, true);
|
||||||
|
var dialog = new DialogComp() {
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null, true);
|
||||||
|
dialogStage.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void discard() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> content() {
|
||||||
|
return new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText, () -> {
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> bottom() {
|
||||||
|
var clear = new ButtonComp(AppI18n.observable("clear"), () -> {
|
||||||
|
selected.setValue(null);
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.grow(false, true);
|
||||||
|
return new HorizontalComp(List.of(github, filter.hgrow(), clear)).spacing(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> finishButton() {
|
||||||
|
return super.finishButton().disable(selected.isNull());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dialog.prefWidth(600);
|
||||||
|
dialog.prefHeight(600);
|
||||||
|
return dialog.createRegion();
|
||||||
|
}
|
||||||
|
}
|
64
app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java
Normal file
64
app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class StoreIconComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final StoreEntryWrapper wrapper;
|
||||||
|
private final int w;
|
||||||
|
private final int h;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var imageComp = PrettyImageHelper.ofFixedSize(wrapper.getIconFile(), w, h);
|
||||||
|
var storeIcon = imageComp.createRegion();
|
||||||
|
if (wrapper.getValidity().getValue().isUsable()) {
|
||||||
|
new TooltipAugment<>(wrapper.getEntry().getProvider().displayName(), null).augment(storeIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
var background = new Region();
|
||||||
|
background.getStyleClass().add("background");
|
||||||
|
|
||||||
|
var dots = new FontIcon("mdi2d-dots-horizontal");
|
||||||
|
dots.setIconSize((int) (h * 1.3));
|
||||||
|
|
||||||
|
var stack = new StackPane(background, storeIcon, dots);
|
||||||
|
stack.setMinHeight(w + 7);
|
||||||
|
stack.setMinWidth(w + 7);
|
||||||
|
stack.setMaxHeight(w + 7);
|
||||||
|
stack.setMaxWidth(w + 7);
|
||||||
|
stack.getStyleClass().add("icon");
|
||||||
|
stack.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
dots.visibleProperty().bind(stack.hoverProperty());
|
||||||
|
storeIcon
|
||||||
|
.opacityProperty()
|
||||||
|
.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> {
|
||||||
|
return stack.isHover() ? 0.5 : 1.0;
|
||||||
|
},
|
||||||
|
stack.hoverProperty()));
|
||||||
|
|
||||||
|
stack.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY) {
|
||||||
|
StoreIconChoiceDialogComp.show(wrapper.getEntry());
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,12 +39,12 @@ public class StoreIntroComp extends SimpleComp {
|
||||||
|
|
||||||
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
||||||
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
||||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local(), null));
|
||||||
scanButton.setDefaultButton(true);
|
scanButton.setDefaultButton(true);
|
||||||
var scanPane = new StackPane(scanButton);
|
var scanPane = new StackPane(scanButton);
|
||||||
scanPane.setAlignment(Pos.CENTER);
|
scanPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
var img = new PrettySvgComp(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion();
|
var img = new PrettySvgComp(new SimpleStringProperty("graphics/Wave.svg"), 80, 150).createRegion();
|
||||||
var text = new VBox(title, introDesc);
|
var text = new VBox(title, introDesc);
|
||||||
text.setSpacing(5);
|
text.setSpacing(5);
|
||||||
text.setAlignment(Pos.CENTER_LEFT);
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
|
@ -96,6 +96,9 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
|
||||||
ref.get().hide();
|
ref.get().hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void discard() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String finishKey() {
|
protected String finishKey() {
|
||||||
return "apply";
|
return "apply";
|
||||||
|
|
|
@ -41,8 +41,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
||||||
private MenuItem recurse(ContextMenu contextMenu, StoreSection section) {
|
private MenuItem recurse(ContextMenu contextMenu, StoreSection section) {
|
||||||
var c = section.getShownChildren();
|
var c = section.getShownChildren();
|
||||||
var w = section.getWrapper();
|
var w = section.getWrapper();
|
||||||
var graphic =
|
var graphic = w.getEntry().getEffectiveIconFile();
|
||||||
w.getEntry().getProvider().getDisplayIconFileName(w.getEntry().getStore());
|
|
||||||
if (c.getList().isEmpty()) {
|
if (c.getList().isEmpty()) {
|
||||||
var item = ContextMenuHelper.item(
|
var item = ContextMenuHelper.item(
|
||||||
new LabelGraphic.ImageGraphic(graphic, 16), w.getName().getValue());
|
new LabelGraphic.ImageGraphic(graphic, 16), w.getName().getValue());
|
||||||
|
|
|
@ -176,7 +176,8 @@ public class StoreSection {
|
||||||
var showProvider = true;
|
var showProvider = true;
|
||||||
try {
|
try {
|
||||||
showProvider = other.getEntry().getProvider().shouldShow(other);
|
showProvider = other.getEntry().getProvider().shouldShow(other);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
return showProvider;
|
return showProvider;
|
||||||
},
|
},
|
||||||
e.getPersistentState(),
|
e.getPersistentState(),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
|
||||||
|
@ -68,11 +69,15 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||||
|
|
||||||
private Comp<CompStructure<Button>> createExpandButton() {
|
private Comp<CompStructure<Button>> createExpandButton() {
|
||||||
var expandButton = new IconButtonComp(
|
var expandButton = new IconButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createObjectBinding(
|
||||||
() -> section.getWrapper().getExpanded().get()
|
() -> new LabelGraphic.IconGraphic(
|
||||||
&& section.getShownChildren().getList().size() > 0
|
section.getWrapper().getExpanded().get()
|
||||||
? "mdal-keyboard_arrow_down"
|
&& section.getShownChildren()
|
||||||
: "mdal-keyboard_arrow_right",
|
.getList()
|
||||||
|
.size()
|
||||||
|
> 0
|
||||||
|
? "mdal-keyboard_arrow_down"
|
||||||
|
: "mdal-keyboard_arrow_right"),
|
||||||
section.getWrapper().getExpanded(),
|
section.getWrapper().getExpanded(),
|
||||||
section.getShownChildren().getList()),
|
section.getShownChildren().getList()),
|
||||||
() -> {
|
() -> {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -52,15 +53,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
if (section.getWrapper() != null) {
|
if (section.getWrapper() != null) {
|
||||||
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
var provider = section.getWrapper().getEntry().getProvider();
|
|
||||||
struc.get()
|
struc.get()
|
||||||
.setGraphic(PrettyImageHelper.ofFixedSizeSquare(
|
.setGraphic(PrettyImageHelper.ofFixedSize(
|
||||||
provider != null
|
section.getWrapper().getIconFile(), 16, 16)
|
||||||
? provider.getDisplayIconFileName(section.getWrapper()
|
|
||||||
.getEntry()
|
|
||||||
.getStore())
|
|
||||||
: null,
|
|
||||||
16)
|
|
||||||
.createRegion());
|
.createRegion());
|
||||||
})
|
})
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
|
@ -81,8 +76,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
||||||
&& section.getShownChildren().getList().size() > 0);
|
&& section.getShownChildren().getList().size() > 0);
|
||||||
var button = new IconButtonComp(
|
var button = new IconButtonComp(
|
||||||
Bindings.createStringBinding(
|
Bindings.createObjectBinding(
|
||||||
() -> expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right",
|
() -> new LabelGraphic.IconGraphic(
|
||||||
|
expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right"),
|
||||||
expanded),
|
expanded),
|
||||||
() -> {
|
() -> {
|
||||||
expanded.set(!expanded.get());
|
expanded.set(!expanded.get());
|
||||||
|
|
|
@ -98,10 +98,11 @@ public class StoreViewState {
|
||||||
private void initFilterJump() {
|
private void initFilterJump() {
|
||||||
var all = getAllConnectionsCategory();
|
var all = getAllConnectionsCategory();
|
||||||
filter.addListener((observable, oldValue, newValue) -> {
|
filter.addListener((observable, oldValue, newValue) -> {
|
||||||
var matchingCats = categories.getList().stream().filter(storeCategoryWrapper -> storeCategoryWrapper.getRoot().equals(all))
|
var matchingCats = categories.getList().stream()
|
||||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getDirectContainedEntries()
|
.filter(storeCategoryWrapper ->
|
||||||
.stream()
|
storeCategoryWrapper.getRoot().equals(all))
|
||||||
.anyMatch(wrapper -> wrapper.matchesFilter(newValue)))
|
.filter(storeCategoryWrapper -> storeCategoryWrapper.getDirectContainedEntries().stream()
|
||||||
|
.anyMatch(wrapper -> wrapper.matchesFilter(newValue)))
|
||||||
.toList();
|
.toList();
|
||||||
if (matchingCats.size() == 1) {
|
if (matchingCats.size() == 1) {
|
||||||
activeCategory.setValue(matchingCats.getFirst());
|
activeCategory.setValue(matchingCats.getFirst());
|
||||||
|
|
|
@ -10,6 +10,8 @@ import io.xpipe.app.util.LicenseProvider;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -63,4 +65,12 @@ public class App extends Application {
|
||||||
stage.requestFocus();
|
stage.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableDoubleValue displayScale() {
|
||||||
|
if (getStage() == null) {
|
||||||
|
return new SimpleDoubleProperty(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getStage().outputScaleXProperty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import io.xpipe.app.util.PlatformState;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.desktop.*;
|
import java.awt.desktop.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public class AppDesktopIntegration {
|
public class AppDesktopIntegration {
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ public class AppDesktopIntegration {
|
||||||
ThreadHelper.sleep(1000);
|
ThreadHelper.sleep(1000);
|
||||||
OperationMode.close();
|
OperationMode.close();
|
||||||
});
|
});
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.ext.ExtensionException;
|
import io.xpipe.app.ext.ExtensionException;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
|
||||||
import io.xpipe.core.util.ModuleHelper;
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
import io.xpipe.core.util.ModuleLayerLoader;
|
import io.xpipe.core.util.ModuleLayerLoader;
|
||||||
import io.xpipe.core.util.XPipeInstallation;
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
@ -55,8 +56,8 @@ public class AppExtensionManager {
|
||||||
ErrorEvent.fromThrowable(t).handle();
|
ErrorEvent.fromThrowable(t).handle();
|
||||||
});
|
});
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new ExtensionException(
|
throw ExtensionException.corrupt(
|
||||||
"Service provider initialization failed. Is the installation data corrupt?", t);
|
"Service provider initialization failed", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ public class AppExtensionManager {
|
||||||
private void loadBaseExtension() {
|
private void loadBaseExtension() {
|
||||||
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
|
var baseModule = findAndParseExtension("base", ModuleLayer.boot());
|
||||||
if (baseModule.isEmpty()) {
|
if (baseModule.isEmpty()) {
|
||||||
throw new ExtensionException("Missing base module. Is the installation data corrupt?");
|
throw ExtensionException.corrupt("Missing base module");
|
||||||
}
|
}
|
||||||
|
|
||||||
baseLayer = baseModule.get().getModule().getLayer();
|
baseLayer = baseModule.get().getModule().getLayer();
|
||||||
|
@ -205,8 +206,8 @@ public class AppExtensionManager {
|
||||||
var ext = getExtensionFromDir(layer, dir);
|
var ext = getExtensionFromDir(layer, dir);
|
||||||
if (ext.isEmpty()) {
|
if (ext.isEmpty()) {
|
||||||
if (AppProperties.get().isFullVersion()) {
|
if (AppProperties.get().isFullVersion()) {
|
||||||
throw new ExtensionException(
|
throw ExtensionException.corrupt(
|
||||||
"Unable to load extension from directory " + dir + ". Is the installation corrupted?");
|
"Unable to load extension from directory " + dir);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loadedExtensions.stream()
|
if (loadedExtensions.stream()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
package io.xpipe.app.core;
|
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
|
||||||
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.WritableImage;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AppImages {
|
|
||||||
|
|
||||||
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
|
|
||||||
private static final Map<String, Image> images = new HashMap<>();
|
|
||||||
private static final Map<String, String> svgImages = new HashMap<>();
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
if (images.size() > 0 || svgImages.size() > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackEvent.info("Loading images ...");
|
|
||||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
|
||||||
loadDirectory(module.getName(), "img", true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
|
|
||||||
AppResources.with(module, dir, basePath -> {
|
|
||||||
if (!Files.exists(basePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var simpleName = FilenameUtils.getExtension(module);
|
|
||||||
String defaultPrefix = simpleName + ":";
|
|
||||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
|
||||||
var relativeFileName = FilenameUtils.separatorsToUnix(
|
|
||||||
basePath.relativize(file).toString());
|
|
||||||
try {
|
|
||||||
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
|
|
||||||
var s = Files.readString(file);
|
|
||||||
svgImages.put(defaultPrefix + relativeFileName, s);
|
|
||||||
} else if (loadImages) {
|
|
||||||
images.put(defaultPrefix + relativeFileName, loadImage(file));
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String svgImage(String file) {
|
|
||||||
if (file == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = file.contains(":") ? file : "app:" + file;
|
|
||||||
|
|
||||||
if (svgImages.containsKey(key)) {
|
|
||||||
return svgImages.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackEvent.warn("Svg image " + key + " not found");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasNormalImage(String file) {
|
|
||||||
if (file == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = file.contains(":") ? file : "app:" + file;
|
|
||||||
return images.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasSvgImage(String file) {
|
|
||||||
if (file == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = file.contains(":") ? file : "app:" + file;
|
|
||||||
return svgImages.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Image image(String file) {
|
|
||||||
if (file == null) {
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = file.contains(":") ? file : "app:" + file;
|
|
||||||
|
|
||||||
if (images.containsKey(key)) {
|
|
||||||
return images.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackEvent.warn("Normal image " + key + " not found");
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BufferedImage toAwtImage(Image fxImage) {
|
|
||||||
BufferedImage img =
|
|
||||||
new BufferedImage((int) fxImage.getWidth(), (int) fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
|
||||||
for (int x = 0; x < fxImage.getWidth(); x++) {
|
|
||||||
for (int y = 0; y < fxImage.getHeight(); y++) {
|
|
||||||
int rgb = fxImage.getPixelReader().getArgb(x, y);
|
|
||||||
img.setRGB(x, y, rgb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Image loadImage(Path p) {
|
|
||||||
if (p == null) {
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Files.isRegularFile(p)) {
|
|
||||||
TrackEvent.error("Image file " + p + " not found.");
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var in = Files.newInputStream(p)) {
|
|
||||||
return new Image(in, -1, -1, true, true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
return DEFAULT_IMAGE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,14 +3,17 @@ package io.xpipe.app.core;
|
||||||
import io.xpipe.app.beacon.AppBeaconServer;
|
import io.xpipe.app.beacon.AppBeaconServer;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionComp;
|
import io.xpipe.app.browser.session.BrowserSessionComp;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||||
|
import io.xpipe.app.comp.base.TerminalViewDockComp;
|
||||||
import io.xpipe.app.comp.store.StoreLayoutComp;
|
import io.xpipe.app.comp.store.StoreLayoutComp;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.prefs.AppPrefsComp;
|
import io.xpipe.app.prefs.AppPrefsComp;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyCodeCombination;
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
@ -21,6 +24,7 @@ import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
import java.time.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -38,7 +42,7 @@ public class AppLayoutModel {
|
||||||
public AppLayoutModel(SavedState savedState) {
|
public AppLayoutModel(SavedState savedState) {
|
||||||
this.savedState = savedState;
|
this.savedState = savedState;
|
||||||
this.entries = createEntryList();
|
this.entries = createEntryList();
|
||||||
this.selected = new SimpleObjectProperty<>(entries.get(1));
|
this.selected = new SimpleObjectProperty<>(entries.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppLayoutModel get() {
|
public static AppLayoutModel get() {
|
||||||
|
@ -56,66 +60,100 @@ public class AppLayoutModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectBrowser() {
|
public void selectBrowser() {
|
||||||
selected.setValue(entries.getFirst());
|
selected.setValue(entries.get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectSettings() {
|
public void selectTerminal() {
|
||||||
selected.setValue(entries.get(2));
|
selected.setValue(entries.get(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectLicense() {
|
public void selectSettings() {
|
||||||
selected.setValue(entries.get(3));
|
selected.setValue(entries.get(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void selectLicense() {
|
||||||
|
selected.setValue(entries.get(4));
|
||||||
|
}
|
||||||
|
|
||||||
public void selectConnections() {
|
public void selectConnections() {
|
||||||
selected.setValue(entries.get(1));
|
selected.setValue(entries.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Entry> createEntryList() {
|
private List<Entry> createEntryList() {
|
||||||
var l = new ArrayList<>(List.of(
|
var l = new ArrayList<>(List.of(
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("browser"),
|
AppI18n.observable("connections"),
|
||||||
"mdi2f-file-cabinet",
|
new LabelGraphic.IconGraphic("mdi2c-connection"),
|
||||||
new BrowserSessionComp(BrowserSessionModel.DEFAULT),
|
new StoreLayoutComp(),
|
||||||
null,
|
null,
|
||||||
new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)),
|
new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("connections"),
|
AppI18n.observable("browser"),
|
||||||
"mdi2c-connection",
|
new LabelGraphic.IconGraphic("mdi2f-file-cabinet"),
|
||||||
new StoreLayoutComp(),
|
new BrowserSessionComp(BrowserSessionModel.DEFAULT),
|
||||||
null,
|
null,
|
||||||
new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)),
|
new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)),
|
||||||
|
new Entry(
|
||||||
|
AppI18n.observable("terminal"),
|
||||||
|
new LabelGraphic.IconGraphic("mdi2m-monitor-screenshot"),
|
||||||
|
new TerminalViewDockComp(),
|
||||||
|
null,
|
||||||
|
new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.SHORTCUT_DOWN)),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("settings"),
|
AppI18n.observable("settings"),
|
||||||
"mdsmz-miscellaneous_services",
|
new LabelGraphic.IconGraphic("mdsmz-miscellaneous_services"),
|
||||||
new AppPrefsComp(),
|
new AppPrefsComp(),
|
||||||
null,
|
null,
|
||||||
new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.SHORTCUT_DOWN)),
|
new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.SHORTCUT_DOWN)),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("explorePlans"),
|
AppI18n.observable("explorePlans"),
|
||||||
"mdi2p-professional-hexagon",
|
new LabelGraphic.IconGraphic("mdi2p-professional-hexagon"),
|
||||||
LicenseProvider.get().overviewPage(),
|
LicenseProvider.get().overviewPage(),
|
||||||
null,
|
null,
|
||||||
null),
|
null),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("visitGithubRepository"),
|
AppI18n.observable("visitGithubRepository"),
|
||||||
"mdi2g-github",
|
new LabelGraphic.IconGraphic("mdi2g-github"),
|
||||||
null,
|
null,
|
||||||
() -> Hyperlinks.open(Hyperlinks.GITHUB),
|
() -> Hyperlinks.open(Hyperlinks.GITHUB),
|
||||||
null),
|
null),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("discord"),
|
AppI18n.observable("discord"),
|
||||||
"mdi2d-discord",
|
new LabelGraphic.IconGraphic("mdi2d-discord"),
|
||||||
null,
|
null,
|
||||||
() -> Hyperlinks.open(Hyperlinks.DISCORD),
|
() -> Hyperlinks.open(Hyperlinks.DISCORD),
|
||||||
null),
|
null),
|
||||||
new Entry(
|
new Entry(
|
||||||
AppI18n.observable("api"),
|
AppI18n.observable("api"),
|
||||||
"mdi2c-code-json",
|
new LabelGraphic.IconGraphic("mdi2c-code-json"),
|
||||||
null,
|
null,
|
||||||
() -> Hyperlinks.open(
|
() -> Hyperlinks.open(
|
||||||
"http://localhost:" + AppBeaconServer.get().getPort()),
|
"http://localhost:" + AppBeaconServer.get().getPort()),
|
||||||
null)));
|
null)
|
||||||
|
// new Entry(
|
||||||
|
// AppI18n.observable("webtop"),
|
||||||
|
// "mdi2d-desktop-mac",
|
||||||
|
// null,
|
||||||
|
// () -> Hyperlinks.open(Hyperlinks.GITHUB_WEBTOP),
|
||||||
|
// null)
|
||||||
|
));
|
||||||
|
|
||||||
|
var now = Instant.now();
|
||||||
|
var zone = ZoneId.of(ZoneId.SHORT_IDS.get("PST"));
|
||||||
|
var phStart = ZonedDateTime.of(2024, 10, 22, 0, 1, 0, 0, zone).toInstant();
|
||||||
|
var clicked = AppCache.get("phClicked",Boolean.class,() -> false);
|
||||||
|
var phShow = now.isAfter(phStart) && !clicked;
|
||||||
|
if (phShow) {
|
||||||
|
l.add(new Entry(
|
||||||
|
new SimpleStringProperty("Product Hunt"),
|
||||||
|
new LabelGraphic.ImageGraphic("app:producthunt-color.png", 24),
|
||||||
|
null,
|
||||||
|
() -> {
|
||||||
|
AppCache.update("phClicked", true);
|
||||||
|
Hyperlinks.open(Hyperlinks.PRODUCT_HUNT);
|
||||||
|
},
|
||||||
|
null));
|
||||||
|
}
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,5 +167,9 @@ public class AppLayoutModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Entry(
|
public record Entry(
|
||||||
ObservableValue<String> name, String icon, Comp<?> comp, Runnable action, KeyCombination combination) {}
|
ObservableValue<String> name,
|
||||||
|
LabelGraphic icon,
|
||||||
|
Comp<?> comp,
|
||||||
|
Runnable action,
|
||||||
|
KeyCombination combination) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
package io.xpipe.app.core;
|
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.core.util.FailableConsumer;
|
|
||||||
import io.xpipe.modulefs.ModuleFileSystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.JarURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class AppResources {
|
|
||||||
|
|
||||||
public static final String XPIPE_MODULE = "io.xpipe.app";
|
|
||||||
|
|
||||||
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static void reset() {
|
|
||||||
fileSystems.forEach((s, moduleFileSystem) -> {
|
|
||||||
try {
|
|
||||||
moduleFileSystem.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// Usually when updating, a SIGTERM is sent to this application.
|
|
||||||
// However, it takes a while to shut down but the installer is deleting files meanwhile.
|
|
||||||
// It can happen that the jar it does not exist anymore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fileSystems.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
|
|
||||||
var layer = AppExtensionManager.getInstance() != null
|
|
||||||
? AppExtensionManager.getInstance().getExtendedLayer()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Only cache file systems with extended layer
|
|
||||||
if (layer != null && fileSystems.containsKey(module)) {
|
|
||||||
return fileSystems.get(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layer == null) {
|
|
||||||
layer = ModuleLayer.boot();
|
|
||||||
}
|
|
||||||
|
|
||||||
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
|
|
||||||
if (AppExtensionManager.getInstance() != null) {
|
|
||||||
fileSystems.put(module, fs);
|
|
||||||
}
|
|
||||||
return fs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<URL> getResourceURL(String module, String file) {
|
|
||||||
try {
|
|
||||||
var fs = openFileSystemIfNeeded(module);
|
|
||||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
|
||||||
var url = f.getWrappedPath().toUri().toURL();
|
|
||||||
return Optional.of(url);
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void with(String module, String file, FailableConsumer<Path, IOException> con) {
|
|
||||||
if (AppProperties.get() != null
|
|
||||||
&& !AppProperties.get().isImage()
|
|
||||||
&& AppProperties.get().isDeveloperMode()) {
|
|
||||||
// Check if resource was found. If we use external processed resources, we can't use local dev resources
|
|
||||||
if (withLocalDevResource(module, file, con)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withResource(module, file, con);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void withResourceInLayer(
|
|
||||||
String module, String file, ModuleLayer layer, FailableConsumer<Path, IOException> con) {
|
|
||||||
try (var fs = FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer))) {
|
|
||||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
|
||||||
con.accept(f);
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
|
||||||
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
|
|
||||||
try {
|
|
||||||
var fs = openFileSystemIfNeeded(module);
|
|
||||||
var f = fs.getPath(path);
|
|
||||||
con.accept(f);
|
|
||||||
} catch (IOException e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
|
||||||
try {
|
|
||||||
var fs = openFileSystemIfNeeded(module);
|
|
||||||
var url = fs.getPath("").getWrappedPath().toUri().toURL();
|
|
||||||
if (!url.getProtocol().equals("jar")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
|
||||||
URL fileUrl = connection.getJarFileURL();
|
|
||||||
var jarFile = Path.of(fileUrl.toURI());
|
|
||||||
var resDir = jarFile.getParent()
|
|
||||||
.getParent()
|
|
||||||
.getParent()
|
|
||||||
.resolve("src")
|
|
||||||
.resolve("main")
|
|
||||||
.resolve("resources");
|
|
||||||
var f = resDir.resolve(module.replace('.', '/') + "/resources/" + file);
|
|
||||||
if (!Files.exists(f)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
con.accept(f);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.core;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
|
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javafx.animation.Interpolator;
|
import javafx.animation.Interpolator;
|
||||||
|
|
|
@ -2,6 +2,8 @@ package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package io.xpipe.app.core.check;
|
package io.xpipe.app.core.check;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.AppProperties;
|
||||||
|
import io.xpipe.app.core.AppState;
|
||||||
|
import io.xpipe.app.core.AppStyle;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.app.util.PlatformState;
|
import io.xpipe.app.util.PlatformState;
|
||||||
import io.xpipe.app.util.WindowsRegistry;
|
import io.xpipe.app.util.WindowsRegistry;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
@ -42,7 +46,6 @@ public class AppAvCheck {
|
||||||
|
|
||||||
PlatformState.initPlatformOrThrow();
|
PlatformState.initPlatformOrThrow();
|
||||||
AppStyle.init();
|
AppStyle.init();
|
||||||
AppImages.init();
|
|
||||||
|
|
||||||
var a = AppWindowHelper.showBlockingAlert(alert -> {
|
var a = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
||||||
|
|
|
@ -8,7 +8,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
public class AppBundledToolsCheck {
|
public class AppBundledToolsCheck {
|
||||||
|
|
||||||
private static boolean getResult() {
|
private static boolean getResult() {
|
||||||
var fc = new ProcessBuilder("where", "ssh").redirectErrorStream(true).redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
var fc = new ProcessBuilder("where", "ssh")
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||||
try {
|
try {
|
||||||
var proc = fc.start();
|
var proc = fc.start();
|
||||||
proc.waitFor(2, TimeUnit.SECONDS);
|
proc.waitFor(2, TimeUnit.SECONDS);
|
||||||
|
|
22
app/src/main/java/io/xpipe/app/core/check/AppGpuCheck.java
Normal file
22
app/src/main/java/io/xpipe/app/core/check/AppGpuCheck.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package io.xpipe.app.core.check;
|
||||||
|
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.util.PlatformState;
|
||||||
|
|
||||||
|
import javafx.application.ConditionalFeature;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
|
||||||
|
public class AppGpuCheck {
|
||||||
|
|
||||||
|
public static void check() {
|
||||||
|
if (PlatformState.getCurrent() != PlatformState.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.isSupported(ConditionalFeature.SCENE3D)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppPrefs.get().performanceMode.setValue(true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package io.xpipe.app.core.check;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
|
||||||
|
public class AppJavaOptionsCheck {
|
||||||
|
|
||||||
|
public static void check() {
|
||||||
|
if (AppCache.get("javaOptionsWarningShown", Boolean.class,() -> false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var env = System.getenv("_JAVA_OPTIONS");
|
||||||
|
if (env == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorEvent.fromMessage(
|
||||||
|
"You have configured the global environment variable _JAVA_OPTIONS=%s on your system."
|
||||||
|
.formatted(env)
|
||||||
|
+ " This will forcefully apply all custom JVM options to XPipe and can cause a variety of different issues."
|
||||||
|
+ " Please remove this global environment variable and use local configuration instead for your other JVM programs.")
|
||||||
|
.noDefaultActions()
|
||||||
|
.expected()
|
||||||
|
.handle();
|
||||||
|
AppCache.update("javaOptionsWarningShown", true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,8 +25,11 @@ public class AppRosettaCheck {
|
||||||
|
|
||||||
if (ret.get().equals("1")) {
|
if (ret.get().equals("1")) {
|
||||||
ErrorEvent.fromMessage("You are running the Intel version of XPipe on an Apple Silicon system."
|
ErrorEvent.fromMessage("You are running the Intel version of XPipe on an Apple Silicon system."
|
||||||
+ " There is a native build available that comes with much better performance."
|
+ " There is a native build available that comes with much better performance."
|
||||||
+ " Please install that one instead.");
|
+ " Please install that one instead.")
|
||||||
|
.noDefaultActions()
|
||||||
|
.expected()
|
||||||
|
.handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package io.xpipe.app.core.check;
|
package io.xpipe.app.core.check;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
|
||||||
import io.xpipe.core.process.ProcessOutputException;
|
import io.xpipe.core.process.ProcessOutputException;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
|
@ -8,8 +8,11 @@ import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.check.*;
|
import io.xpipe.app.core.check.*;
|
||||||
import io.xpipe.app.ext.ActionProvider;
|
import io.xpipe.app.ext.ActionProvider;
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
|
import io.xpipe.app.resources.SystemIcons;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStorageSyncHandler;
|
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
|
@ -44,6 +47,7 @@ public class BaseMode extends OperationMode {
|
||||||
AppCertutilCheck.check();
|
AppCertutilCheck.check();
|
||||||
AppBundledToolsCheck.check();
|
AppBundledToolsCheck.check();
|
||||||
AppAvCheck.check();
|
AppAvCheck.check();
|
||||||
|
AppJavaOptionsCheck.check();
|
||||||
AppSid.init();
|
AppSid.init();
|
||||||
LocalShell.init();
|
LocalShell.init();
|
||||||
AppShellCheck.check();
|
AppShellCheck.check();
|
||||||
|
@ -56,12 +60,14 @@ public class BaseMode extends OperationMode {
|
||||||
DataStorageSyncHandler.getInstance().retrieveSyncedData();
|
DataStorageSyncHandler.getInstance().retrieveSyncedData();
|
||||||
AppPrefs.initSharedRemote();
|
AppPrefs.initSharedRemote();
|
||||||
UnlockAlert.showIfNeeded();
|
UnlockAlert.showIfNeeded();
|
||||||
|
SystemIcons.init();
|
||||||
DataStorage.init();
|
DataStorage.init();
|
||||||
DataStoreProviders.init();
|
DataStoreProviders.init();
|
||||||
AppFileWatcher.init();
|
AppFileWatcher.init();
|
||||||
FileBridge.init();
|
FileBridge.init();
|
||||||
BlobManager.init();
|
BlobManager.init();
|
||||||
ActionProvider.initProviders();
|
ActionProvider.initProviders();
|
||||||
|
TerminalView.init();
|
||||||
TrackEvent.info("Finished base components initialization");
|
TrackEvent.info("Finished base components initialization");
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +76,7 @@ public class BaseMode extends OperationMode {
|
||||||
public void onSwitchFrom() {}
|
public void onSwitchFrom() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finalTeardown() {
|
public void finalTeardown() throws Exception {
|
||||||
TrackEvent.info("Background mode shutdown started");
|
TrackEvent.info("Background mode shutdown started");
|
||||||
BrowserSessionModel.DEFAULT.reset();
|
BrowserSessionModel.DEFAULT.reset();
|
||||||
SshLocalBridge.reset();
|
SshLocalBridge.reset();
|
||||||
|
@ -78,12 +84,14 @@ public class BaseMode extends OperationMode {
|
||||||
DataStoreProviders.reset();
|
DataStoreProviders.reset();
|
||||||
DataStorage.reset();
|
DataStorage.reset();
|
||||||
AppPrefs.reset();
|
AppPrefs.reset();
|
||||||
|
DataStorageSyncHandler.getInstance().reset();
|
||||||
|
LocalShell.reset();
|
||||||
|
ProcessControlProvider.get().reset();
|
||||||
AppResources.reset();
|
AppResources.reset();
|
||||||
AppExtensionManager.reset();
|
AppExtensionManager.reset();
|
||||||
AppDataLock.unlock();
|
AppDataLock.unlock();
|
||||||
BlobManager.reset();
|
BlobManager.reset();
|
||||||
FileBridge.reset();
|
FileBridge.reset();
|
||||||
// Shut down server last to keep a non-daemon thread running
|
|
||||||
AppBeaconServer.reset();
|
AppBeaconServer.reset();
|
||||||
TrackEvent.info("Background mode shutdown finished");
|
TrackEvent.info("Background mode shutdown finished");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.browser.file.LocalFileSystem;
|
||||||
import io.xpipe.app.browser.icon.FileIconManager;
|
import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
import io.xpipe.app.core.App;
|
import io.xpipe.app.core.App;
|
||||||
import io.xpipe.app.core.AppGreetings;
|
import io.xpipe.app.core.AppGreetings;
|
||||||
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.core.check.AppPtbCheck;
|
import io.xpipe.app.core.check.AppPtbCheck;
|
||||||
import io.xpipe.app.core.window.AppMainWindow;
|
import io.xpipe.app.core.window.AppMainWindow;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
@ -39,6 +40,7 @@ public class GuiMode extends PlatformMode {
|
||||||
AppGreetings.showIfNeeded();
|
AppGreetings.showIfNeeded();
|
||||||
AppPtbCheck.check();
|
AppPtbCheck.check();
|
||||||
NativeBridge.init();
|
NativeBridge.init();
|
||||||
|
AppLayoutModel.init();
|
||||||
|
|
||||||
TrackEvent.info("Waiting for window setup completion ...");
|
TrackEvent.info("Waiting for window setup completion ...");
|
||||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||||
|
@ -63,4 +65,10 @@ public class GuiMode extends PlatformMode {
|
||||||
|
|
||||||
UpdateChangelogAlert.showIfNeeded();
|
UpdateChangelogAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finalTeardown() throws Throwable {
|
||||||
|
LocalFileSystem.reset();
|
||||||
|
super.finalTeardown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,8 @@ public abstract class OperationMode {
|
||||||
CURRENT.finalTeardown();
|
CURRENT.finalTeardown();
|
||||||
}
|
}
|
||||||
CURRENT = null;
|
CURRENT = null;
|
||||||
|
// Restart local shell
|
||||||
|
LocalShell.init();
|
||||||
r.run();
|
r.run();
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
@ -293,17 +295,27 @@ public abstract class OperationMode {
|
||||||
|
|
||||||
inShutdown = true;
|
inShutdown = true;
|
||||||
OperationMode.inShutdownHook = inShutdownHook;
|
OperationMode.inShutdownHook = inShutdownHook;
|
||||||
try {
|
// Keep a non-daemon thread running
|
||||||
if (CURRENT != null) {
|
var thread = ThreadHelper.createPlatformThread("shutdown", false, () -> {
|
||||||
CURRENT.finalTeardown();
|
try {
|
||||||
|
if (CURRENT != null) {
|
||||||
|
CURRENT.finalTeardown();
|
||||||
|
}
|
||||||
|
CURRENT = null;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ErrorEvent.fromThrowable(t).term().handle();
|
||||||
|
OperationMode.halt(1);
|
||||||
}
|
}
|
||||||
CURRENT = null;
|
|
||||||
} catch (Throwable t) {
|
OperationMode.halt(hasError ? 1 : 0);
|
||||||
ErrorEvent.fromThrowable(t).term().handle();
|
});
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
OperationMode.halt(1);
|
OperationMode.halt(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationMode.halt(hasError ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static synchronized void reload() {
|
// public static synchronized void reload() {
|
||||||
|
|
|
@ -3,8 +3,10 @@ package io.xpipe.app.core.mode;
|
||||||
import io.xpipe.app.comp.store.StoreViewState;
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.check.AppFontLoadingCheck;
|
import io.xpipe.app.core.check.AppFontLoadingCheck;
|
||||||
|
import io.xpipe.app.core.check.AppGpuCheck;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||||
import io.xpipe.app.util.PlatformState;
|
import io.xpipe.app.util.PlatformState;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
@ -29,11 +31,14 @@ public abstract class PlatformMode extends OperationMode {
|
||||||
PlatformState.initPlatformOrThrow();
|
PlatformState.initPlatformOrThrow();
|
||||||
// Check if we can load system fonts or fail
|
// Check if we can load system fonts or fail
|
||||||
AppFontLoadingCheck.check();
|
AppFontLoadingCheck.check();
|
||||||
|
// Can be loaded async
|
||||||
|
var imageThread = ThreadHelper.runFailableAsync(() -> {
|
||||||
|
AppImages.init();
|
||||||
|
});
|
||||||
|
AppGpuCheck.check();
|
||||||
AppFont.init();
|
AppFont.init();
|
||||||
AppTheme.init();
|
AppTheme.init();
|
||||||
AppStyle.init();
|
AppStyle.init();
|
||||||
AppImages.init();
|
|
||||||
AppLayoutModel.init();
|
|
||||||
TrackEvent.info("Finished essential component initialization before platform");
|
TrackEvent.info("Finished essential component initialization before platform");
|
||||||
|
|
||||||
TrackEvent.info("Launching application ...");
|
TrackEvent.info("Launching application ...");
|
||||||
|
@ -56,6 +61,7 @@ public abstract class PlatformMode extends OperationMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
StoreViewState.init();
|
StoreViewState.init();
|
||||||
|
imageThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.xpipe.app.core.window;
|
package io.xpipe.app.core.window;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.core.AppImages;
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.AppTheme;
|
import io.xpipe.app.core.AppTheme;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
|
@ -10,8 +9,10 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.prefs.CloseBehaviourAlert;
|
import io.xpipe.app.prefs.CloseBehaviourAlert;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Rectangle2D;
|
import javafx.geometry.Rectangle2D;
|
||||||
|
@ -24,17 +25,18 @@ import javafx.scene.layout.Region;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Screen;
|
import javafx.stage.Screen;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public class AppMainWindow {
|
public class AppMainWindow {
|
||||||
|
|
||||||
|
@ -262,6 +264,9 @@ public class AppMainWindow {
|
||||||
|
|
||||||
public void show() {
|
public void show() {
|
||||||
stage.show();
|
stage.show();
|
||||||
|
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||||
|
NativeWinWindowControl.MAIN_WINDOW = new NativeWinWindowControl(stage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupContent(Comp<?> content) {
|
private void setupContent(Comp<?> content) {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
import io.xpipe.app.util.InputHelper;
|
import io.xpipe.app.util.InputHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package io.xpipe.app.core.window;
|
package io.xpipe.app.core.window;
|
||||||
|
|
||||||
import com.sun.jna.NativeLong;
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.NativeBridge;
|
import io.xpipe.app.util.NativeBridge;
|
||||||
import io.xpipe.core.util.ModuleHelper;
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
|
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import com.sun.jna.NativeLong;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package io.xpipe.app.core.window;
|
package io.xpipe.app.core.window;
|
||||||
|
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import io.xpipe.app.util.Rect;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
|
||||||
import com.sun.jna.Library;
|
import com.sun.jna.Library;
|
||||||
|
@ -13,10 +15,29 @@ import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class NativeWinWindowControl {
|
public class NativeWinWindowControl {
|
||||||
|
|
||||||
|
public static Optional<NativeWinWindowControl> byPid(long pid) {
|
||||||
|
var ref = new AtomicReference<NativeWinWindowControl>();
|
||||||
|
User32.INSTANCE.EnumWindows((hWnd, data) -> {
|
||||||
|
var wpid = new IntByReference();
|
||||||
|
User32.INSTANCE.GetWindowThreadProcessId(hWnd, wpid);
|
||||||
|
if (wpid.getValue() == pid) {
|
||||||
|
ref.set(new NativeWinWindowControl(hWnd));
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return Optional.ofNullable(ref.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NativeWinWindowControl MAIN_WINDOW;
|
||||||
|
|
||||||
private final WinDef.HWND windowHandle;
|
private final WinDef.HWND windowHandle;
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
@ -38,8 +59,28 @@ public class NativeWinWindowControl {
|
||||||
this.windowHandle = windowHandle;
|
this.windowHandle = windowHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(int x, int y, int w, int h) {
|
public void alwaysInFront() {
|
||||||
User32.INSTANCE.SetWindowPos(windowHandle, new WinDef.HWND(), x, y, w, h, 0);
|
orderRelative(new WinDef.HWND(new Pointer( 0xFFFFFFFFFFFFFFFFL)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void orderRelative(WinDef.HWND predecessor) {
|
||||||
|
User32.INSTANCE.SetWindowPos(windowHandle, predecessor, 0, 0, 0, 0, User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOSIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
User32.INSTANCE.ShowWindow(windowHandle,User32.SW_RESTORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
User32.INSTANCE.CloseWindow(windowHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void minimize() {
|
||||||
|
User32.INSTANCE.ShowWindow(windowHandle,User32.SW_MINIMIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(Rect bounds) {
|
||||||
|
User32.INSTANCE.SetWindowPos(windowHandle, null, bounds.getX(), bounds.getY(), bounds.getW(), bounds.getH(), User32.SWP_NOACTIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setWindowAttribute(int attribute, boolean attributeValue) {
|
public boolean setWindowAttribute(int attribute, boolean attributeValue) {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
public interface ContainerImageStore {
|
||||||
|
|
||||||
|
String getImageName();
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.comp.store.StoreSection;
|
import io.xpipe.app.comp.store.StoreSection;
|
||||||
import io.xpipe.app.comp.store.StoreSectionComp;
|
import io.xpipe.app.comp.store.StoreSectionComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppImages;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.util.JacksonizedValue;
|
import io.xpipe.core.util.JacksonizedValue;
|
||||||
|
@ -57,12 +57,12 @@ public interface DataStoreProvider {
|
||||||
default void validate() {
|
default void validate() {
|
||||||
for (Class<?> storeClass : getStoreClasses()) {
|
for (Class<?> storeClass : getStoreClasses()) {
|
||||||
if (!JacksonizedValue.class.isAssignableFrom(storeClass)) {
|
if (!JacksonizedValue.class.isAssignableFrom(storeClass)) {
|
||||||
throw new ExtensionException(
|
throw ExtensionException.corrupt(
|
||||||
String.format("Store class %s is not a Jacksonized value", storeClass.getSimpleName()));
|
String.format("Store class %s is not a Jacksonized value", storeClass.getSimpleName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getUsageCategory() == null) {
|
if (getUsageCategory() == null) {
|
||||||
throw new ExtensionException("Provider %s does not have the usage category".formatted(getId()));
|
throw ExtensionException.corrupt("Provider %s does not have the usage category".formatted(getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package io.xpipe.app.ext;
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
|
||||||
public class ExtensionException extends RuntimeException {
|
public class ExtensionException extends RuntimeException {
|
||||||
|
|
||||||
public ExtensionException() {}
|
public ExtensionException() {}
|
||||||
|
|
||||||
public ExtensionException(String message) {
|
private ExtensionException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtensionException(String message, Throwable cause) {
|
private ExtensionException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +22,18 @@ public class ExtensionException extends RuntimeException {
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ExtensionException corrupt(String message, Throwable cause) {
|
||||||
|
try {
|
||||||
|
var loc = XPipeInstallation.getCurrentInstallationBasePath();
|
||||||
|
var full = message + ".\n\n" + "Please check whether the XPipe installation data at " + loc + " is corrupted.";
|
||||||
|
return new ExtensionException(full, cause);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
var full = message + ".\n\n" + "Please check whether the XPipe installation data is corrupted.";
|
||||||
|
return new ExtensionException(full, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ExtensionException corrupt(String message) {
|
public static ExtensionException corrupt(String message) {
|
||||||
return new ExtensionException(message + ". Is the installation data corrupt?");
|
return corrupt(message, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class LocalStore extends JacksonizedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShellControl control() {
|
public ShellControl parentControl() {
|
||||||
var pc = ProcessControlProvider.get().createLocalProcessControl(true);
|
var pc = ProcessControlProvider.get().createLocalProcessControl(true);
|
||||||
pc.withSourceStore(this);
|
pc.withSourceStore(this);
|
||||||
pc.withShellStateInit(this);
|
pc.withShellStateInit(this);
|
||||||
|
@ -28,6 +28,11 @@ public class LocalStore extends JacksonizedValue
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl control(ShellControl parent) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataStore getNetworkParent() {
|
public DataStore getNetworkParent() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.ext;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.core.process.*;
|
import io.xpipe.core.process.*;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
@ -22,6 +23,8 @@ public abstract class ProcessControlProvider {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void reset();
|
||||||
|
|
||||||
public abstract ShellControl withDefaultScripts(ShellControl pc);
|
public abstract ShellControl withDefaultScripts(ShellControl pc);
|
||||||
|
|
||||||
public abstract ShellControl sub(
|
public abstract ShellControl sub(
|
||||||
|
|
|
@ -31,11 +31,11 @@ public abstract class ScanProvider {
|
||||||
String nameKey;
|
String nameKey;
|
||||||
boolean disabled;
|
boolean disabled;
|
||||||
boolean defaultSelected;
|
boolean defaultSelected;
|
||||||
FailableRunnable<Exception> scanner;
|
FailableRunnable<Throwable> scanner;
|
||||||
String licenseFeatureId;
|
String licenseFeatureId;
|
||||||
|
|
||||||
public ScanOperation(
|
public ScanOperation(
|
||||||
String nameKey, boolean disabled, boolean defaultSelected, FailableRunnable<Exception> scanner) {
|
String nameKey, boolean disabled, boolean defaultSelected, FailableRunnable<Throwable> scanner) {
|
||||||
this.nameKey = nameKey;
|
this.nameKey = nameKey;
|
||||||
this.disabled = disabled;
|
this.disabled = disabled;
|
||||||
this.defaultSelected = defaultSelected;
|
this.defaultSelected = defaultSelected;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.comp.store.*;
|
import io.xpipe.app.comp.store.*;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.ext.LocalStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -11,7 +12,6 @@ import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.app.ext.LocalStore;
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -200,18 +200,10 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
||||||
button.apply(struc -> {
|
button.apply(struc -> {
|
||||||
struc.get().setMaxWidth(2000);
|
struc.get().setMaxWidth(2000);
|
||||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||||
Comp<?> graphic = new PrettySvgComp(
|
Comp<?> graphic = PrettyImageHelper.ofFixedSize(
|
||||||
Bindings.createStringBinding(
|
Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (selected.getValue() == null) {
|
return selected.getValue().get().getEffectiveIconFile();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected.getValue()
|
|
||||||
.get()
|
|
||||||
.getProvider()
|
|
||||||
.getDisplayIconFileName(
|
|
||||||
selected.getValue().getStore());
|
|
||||||
},
|
},
|
||||||
selected),
|
selected),
|
||||||
16,
|
16,
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
|
||||||
|
|
||||||
var label = new LabelComp(t.get().getName()).apply(struc -> struc.get()
|
var label = new LabelComp(t.get().getName()).apply(struc -> struc.get()
|
||||||
.setGraphic(PrettyImageHelper.ofFixedSizeSquare(
|
.setGraphic(PrettyImageHelper.ofFixedSizeSquare(
|
||||||
t.get().getProvider().getDisplayIconFileName(t.getStore()), 16)
|
t.get().getEffectiveIconFile(), 16)
|
||||||
.createRegion()));
|
.createRegion()));
|
||||||
var delete = new IconButtonComp("mdal-delete_outline", () -> {
|
var delete = new IconButtonComp("mdal-delete_outline", () -> {
|
||||||
selectedList.remove(t);
|
selectedList.remove(t);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.fxcomps.impl;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
@ -16,23 +17,31 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
public class IconButtonComp extends Comp<CompStructure<Button>> {
|
public class IconButtonComp extends Comp<CompStructure<Button>> {
|
||||||
|
|
||||||
private final ObservableValue<String> icon;
|
private final ObservableValue<? extends LabelGraphic> icon;
|
||||||
private final Runnable listener;
|
private final Runnable listener;
|
||||||
|
|
||||||
public IconButtonComp(String defaultVal) {
|
public IconButtonComp(String defaultVal) {
|
||||||
|
this(new SimpleObjectProperty<>(new LabelGraphic.IconGraphic(defaultVal)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconButtonComp(String defaultVal, Runnable listener) {
|
||||||
|
this(new SimpleObjectProperty<>(new LabelGraphic.IconGraphic(defaultVal)), listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconButtonComp(LabelGraphic defaultVal) {
|
||||||
this(new SimpleObjectProperty<>(defaultVal), null);
|
this(new SimpleObjectProperty<>(defaultVal), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IconButtonComp(ObservableValue<String> icon) {
|
public IconButtonComp(ObservableValue<? extends LabelGraphic> icon) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.listener = null;
|
this.listener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IconButtonComp(String defaultVal, Runnable listener) {
|
public IconButtonComp(LabelGraphic defaultVal, Runnable listener) {
|
||||||
this(new SimpleObjectProperty<>(defaultVal), listener);
|
this(new SimpleObjectProperty<>(defaultVal), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IconButtonComp(ObservableValue<String> icon, Runnable listener) {
|
public IconButtonComp(ObservableValue<? extends LabelGraphic> icon, Runnable listener) {
|
||||||
this.icon = PlatformThread.sync(icon);
|
this.icon = PlatformThread.sync(icon);
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
@ -41,18 +50,17 @@ public class IconButtonComp extends Comp<CompStructure<Button>> {
|
||||||
public CompStructure<Button> createBase() {
|
public CompStructure<Button> createBase() {
|
||||||
var button = new Button();
|
var button = new Button();
|
||||||
button.getStyleClass().add(Styles.FLAT);
|
button.getStyleClass().add(Styles.FLAT);
|
||||||
|
icon.subscribe(labelGraphic -> {
|
||||||
var fi = new FontIcon(icon.getValue());
|
button.setGraphic(labelGraphic.createGraphicNode());
|
||||||
fi.setFocusTraversable(false);
|
if (button.getGraphic() instanceof FontIcon fi) {
|
||||||
icon.addListener((c, o, n) -> {
|
fi.setIconSize((int) new Size(button.getFont().getSize(), SizeUnits.PT).pixels());
|
||||||
fi.setIconLiteral(n);
|
}
|
||||||
});
|
});
|
||||||
fi.setIconSize((int) new Size(fi.getFont().getSize(), SizeUnits.PT).pixels());
|
button.fontProperty().subscribe((n) -> {
|
||||||
button.fontProperty().addListener((c, o, n) -> {
|
if (button.getGraphic() instanceof FontIcon fi) {
|
||||||
fi.setIconSize((int) new Size(n.getSize(), SizeUnits.PT).pixels());
|
fi.setIconSize((int) new Size(n.getSize(), SizeUnits.PT).pixels());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// fi.iconColorProperty().bind(button.textFillProperty());
|
|
||||||
button.setGraphic(fi);
|
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
button.setOnAction(e -> {
|
button.setOnAction(e -> {
|
||||||
listener.run();
|
listener.run();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.app.fxcomps.impl;
|
package io.xpipe.app.fxcomps.impl;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppImages;
|
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
|
|
@ -1,55 +1,70 @@
|
||||||
package io.xpipe.app.fxcomps.impl;
|
package io.xpipe.app.fxcomps.impl;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppImages;
|
import io.xpipe.app.core.App;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class PrettyImageHelper {
|
public class PrettyImageHelper {
|
||||||
|
|
||||||
private static Optional<String> rasterizedImageIfExists(String img, int width, int height) {
|
private static Optional<String> rasterizedImageIfExists(String img, int height) {
|
||||||
if (img != null && img.endsWith(".svg")) {
|
if (img != null && img.endsWith(".svg")) {
|
||||||
var base = FileNames.getBaseName(img);
|
var base = FileNames.getBaseName(img);
|
||||||
var renderedName = base + "-" + height + ".png";
|
var renderedName = base + "-" + height + ".png";
|
||||||
if (AppImages.hasNormalImage(base + "-" + height + ".png")) {
|
if (AppImages.hasNormalImage(renderedName)) {
|
||||||
return Optional.of(renderedName);
|
return Optional.of(renderedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (img != null && img.endsWith(".png")) {
|
||||||
|
if (AppImages.hasNormalImage(img)) {
|
||||||
|
return Optional.of(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObservableValue<String> rasterizedImageIfExistsScaled(String img, int height) {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> {
|
||||||
|
if (img == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!img.endsWith(".svg")) {
|
||||||
|
return rasterizedImageIfExists(img, height).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizes = List.of(16, 24, 40, 80);
|
||||||
|
var mult = Math.round(App.getApp().displayScale().get() * height);
|
||||||
|
var base = FileNames.getBaseName(img);
|
||||||
|
var available = sizes.stream()
|
||||||
|
.filter(integer -> AppImages.hasNormalImage(base + "-" + integer + ".png"))
|
||||||
|
.toList();
|
||||||
|
var closest = available.stream()
|
||||||
|
.filter(integer -> integer >= mult)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(available.size() > 0 ? available.getLast() : 0);
|
||||||
|
return rasterizedImageIfExists(img, closest).orElse(null);
|
||||||
|
},
|
||||||
|
App.getApp().displayScale());
|
||||||
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
||||||
return ofFixedSize(img, size, size);
|
return ofFixedSize(img, size, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedRasterized(String img, int w, int h) {
|
|
||||||
if (img == null) {
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rasterized = rasterizedImageIfExists(img, w, h);
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(rasterized.orElse(null)), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Comp<?> ofFixedSize(String img, int w, int h) {
|
public static Comp<?> ofFixedSize(String img, int w, int h) {
|
||||||
if (img == null) {
|
return ofFixedSize(new SimpleStringProperty(img), w, h);
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rasterized = rasterizedImageIfExists(img, w, h);
|
|
||||||
if (rasterized.isPresent()) {
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(rasterized.get()), w, h);
|
|
||||||
} else {
|
|
||||||
return img.endsWith(".svg")
|
|
||||||
? new PrettySvgComp(new SimpleStringProperty(img), w, h)
|
|
||||||
: new PrettyImageComp(new SimpleStringProperty(img), w, h);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedSize(ObservableValue<String> img, int w, int h) {
|
public static Comp<?> ofFixedSize(ObservableValue<String> img, int w, int h) {
|
||||||
|
@ -57,8 +72,8 @@ public class PrettyImageHelper {
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
var binding = BindingsHelper.map(img, s -> {
|
var binding = BindingsHelper.flatMap(img, s -> {
|
||||||
return rasterizedImageIfExists(s, w, h).orElse(s);
|
return rasterizedImageIfExistsScaled(s, h);
|
||||||
});
|
});
|
||||||
return new PrettyImageComp(binding, w, h);
|
return new PrettyImageComp(binding, w, h);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package io.xpipe.app.fxcomps.impl;
|
package io.xpipe.app.fxcomps.impl;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppImages;
|
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
|
|
@ -12,11 +12,11 @@ import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||||
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.storage.DataColor;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
import io.xpipe.app.util.DataStoreFormatter;
|
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -57,11 +57,11 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
var showing = new SimpleBooleanProperty();
|
var showing = new SimpleBooleanProperty();
|
||||||
|
|
||||||
var expandIcon = Bindings.createStringBinding(
|
var expandIcon = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var exp = category.getExpanded().get()
|
var exp = category.getExpanded().get()
|
||||||
&& category.getChildren().size() > 0;
|
&& category.getChildren().size() > 0;
|
||||||
return exp ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right";
|
return new LabelGraphic.IconGraphic(exp ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right");
|
||||||
},
|
},
|
||||||
category.getExpanded(),
|
category.getExpanded(),
|
||||||
category.getChildren());
|
category.getChildren());
|
||||||
|
@ -78,18 +78,18 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
|
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
|
||||||
|
|
||||||
var hover = new SimpleBooleanProperty();
|
var hover = new SimpleBooleanProperty();
|
||||||
var statusIcon = Bindings.createStringBinding(
|
var statusIcon = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (hover.get()) {
|
if (hover.get()) {
|
||||||
return "mdomz-settings";
|
return new LabelGraphic.IconGraphic("mdomz-settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DataStorage.get().supportsSharing()
|
if (!DataStorage.get().supportsSharing()
|
||||||
|| !category.getCategory().canShare()) {
|
|| !category.getCategory().canShare()) {
|
||||||
return "mdi2g-git";
|
return new LabelGraphic.IconGraphic("mdi2g-git");
|
||||||
}
|
}
|
||||||
|
|
||||||
return category.getSync().getValue() ? "mdi2g-git" : "mdi2c-cancel";
|
return new LabelGraphic.IconGraphic(category.getSync().getValue() ? "mdi2g-git" : "mdi2c-cancel");
|
||||||
},
|
},
|
||||||
category.getSync(),
|
category.getSync(),
|
||||||
hover);
|
hover);
|
||||||
|
@ -196,14 +196,16 @@ public class StoreCategoryComp extends SimpleComp {
|
||||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||||
|
|
||||||
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
|
||||||
var none = new MenuItem("None");
|
var none = new MenuItem();
|
||||||
|
none.textProperty().bind(AppI18n.observable("none"));
|
||||||
none.setOnAction(event -> {
|
none.setOnAction(event -> {
|
||||||
category.getCategory().setColor(null);
|
category.getCategory().setColor(null);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
color.getItems().add(none);
|
color.getItems().add(none);
|
||||||
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
|
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
|
||||||
MenuItem m = new MenuItem(DataStoreFormatter.capitalize(dataStoreColor.getId()));
|
MenuItem m = new MenuItem();
|
||||||
|
m.textProperty().bind(AppI18n.observable(dataStoreColor.getId()));
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
category.getCategory().setColor(dataStoreColor);
|
category.getCategory().setColor(dataStoreColor);
|
||||||
event.consume();
|
event.consume();
|
||||||
|
|
|
@ -5,6 +5,26 @@ import io.xpipe.app.util.Hyperlinks;
|
||||||
|
|
||||||
public interface ErrorAction {
|
public interface ErrorAction {
|
||||||
|
|
||||||
|
static ErrorAction openDocumentation(String link) {
|
||||||
|
return new ErrorAction() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return AppI18n.get("openDocumentation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return AppI18n.get("openDocumentationDescription");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ErrorEvent event) {
|
||||||
|
Hyperlinks.open(link);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static ErrorAction reportOnGithub() {
|
static ErrorAction reportOnGithub() {
|
||||||
return new ErrorAction() {
|
return new ErrorAction() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -254,14 +254,19 @@ public class ErrorHandlerComp extends SimpleComp {
|
||||||
actionBox.getChildren().add(ac);
|
actionBox.getChildren().add(ac);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.isDisableDefaultActions() || event.getCustomActions().isEmpty()) {
|
if (!event.isDisableDefaultActions()) {
|
||||||
for (var action :
|
for (var action :
|
||||||
List.of(ErrorAction.automaticallyReport(), ErrorAction.reportOnGithub(), ErrorAction.ignore())) {
|
List.of(ErrorAction.automaticallyReport(), ErrorAction.reportOnGithub(), ErrorAction.ignore())) {
|
||||||
var ac = createActionComp(action);
|
var ac = createActionComp(action);
|
||||||
actionBox.getChildren().add(ac);
|
actionBox.getChildren().add(ac);
|
||||||
}
|
}
|
||||||
actionBox.getChildren().get(1).getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
} else if (event.getCustomActions().isEmpty()) {
|
||||||
|
for (var action : List.of(ErrorAction.ignore())) {
|
||||||
|
var ac = createActionComp(action);
|
||||||
|
actionBox.getChildren().add(ac);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
actionBox.getChildren().get(1).getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
||||||
|
|
||||||
content.getChildren().addAll(actionBox);
|
content.getChildren().addAll(actionBox);
|
||||||
content.getStyleClass().add("top");
|
content.getStyleClass().add("top");
|
||||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.resources.AppResources;
|
||||||
|
|
||||||
import javafx.beans.property.ListProperty;
|
import javafx.beans.property.ListProperty;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
|
|
@ -112,17 +112,21 @@ public class LauncherCommand implements Callable<Integer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client.get().performRequest(DaemonFocusExchange.Request.builder()
|
client.get()
|
||||||
.mode(getEffectiveMode())
|
.performRequest(DaemonFocusExchange.Request.builder()
|
||||||
.build());
|
.mode(getEffectiveMode())
|
||||||
|
.build());
|
||||||
if (!inputs.isEmpty()) {
|
if (!inputs.isEmpty()) {
|
||||||
client.get().performRequest(DaemonOpenExchange.Request.builder()
|
client.get()
|
||||||
.arguments(inputs)
|
.performRequest(DaemonOpenExchange.Request.builder()
|
||||||
.build());
|
.arguments(inputs)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// Wait until shutdown has completed
|
// Wait until shutdown has completed
|
||||||
if (ex.getMessage() != null && ex.getMessage().contains("Daemon is currently in shutdown") && attemptCounter < 10) {
|
if (ex.getMessage() != null
|
||||||
|
&& ex.getMessage().contains("Daemon is currently in shutdown")
|
||||||
|
&& attemptCounter < 10) {
|
||||||
ThreadHelper.sleep(1000);
|
ThreadHelper.sleep(1000);
|
||||||
checkStart(++attemptCounter);
|
checkStart(++attemptCounter);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -115,7 +115,7 @@ public class AboutCategory extends AppPrefsCategory {
|
||||||
AppI18n.observable("xPipeClient"),
|
AppI18n.observable("xPipeClient"),
|
||||||
new SimpleStringProperty("Version " + AppProperties.get().getVersion() + " ("
|
new SimpleStringProperty("Version " + AppProperties.get().getVersion() + " ("
|
||||||
+ AppProperties.get().getArch() + ")"),
|
+ AppProperties.get().getArch() + ")"),
|
||||||
"logo.png");
|
"logo/logo.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (OsType.getLocal() != OsType.MACOS) {
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package io.xpipe.app.prefs;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
|
||||||
import io.xpipe.app.core.AppTheme;
|
|
||||||
import io.xpipe.app.ext.PrefsHandler;
|
import io.xpipe.app.ext.PrefsHandler;
|
||||||
import io.xpipe.app.ext.PrefsProvider;
|
import io.xpipe.app.ext.PrefsProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
@ -11,6 +8,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||||
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.PasswordLockSecretValue;
|
import io.xpipe.app.util.PasswordLockSecretValue;
|
||||||
import io.xpipe.core.util.InPlaceSecretValue;
|
import io.xpipe.core.util.InPlaceSecretValue;
|
||||||
import io.xpipe.core.util.ModuleHelper;
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
|
@ -38,14 +36,17 @@ public class AppPrefs {
|
||||||
private static AppPrefs INSTANCE;
|
private static AppPrefs INSTANCE;
|
||||||
private final List<Mapping<?>> mapping = new ArrayList<>();
|
private final List<Mapping<?>> mapping = new ArrayList<>();
|
||||||
|
|
||||||
|
final BooleanProperty dontAllowTerminalRestart =
|
||||||
|
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAllowTerminalRestart", Boolean.class);
|
||||||
final BooleanProperty enableHttpApi =
|
final BooleanProperty enableHttpApi =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class);
|
mapVaultSpecific(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class);
|
||||||
final BooleanProperty dontAutomaticallyStartVmSshServer =
|
final BooleanProperty dontAutomaticallyStartVmSshServer =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class);
|
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class);
|
||||||
final BooleanProperty dontAcceptNewHostKeys =
|
final BooleanProperty dontAcceptNewHostKeys =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class);
|
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class);
|
||||||
final BooleanProperty performanceMode = map(new SimpleBooleanProperty(false), "performanceMode", Boolean.class);
|
public final BooleanProperty performanceMode = map(new SimpleBooleanProperty(), "performanceMode", Boolean.class);
|
||||||
public final BooleanProperty useBundledTools = map(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class);
|
public final BooleanProperty useBundledTools =
|
||||||
|
map(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class);
|
||||||
public final ObjectProperty<AppTheme.Theme> theme =
|
public final ObjectProperty<AppTheme.Theme> theme =
|
||||||
map(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class);
|
map(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class);
|
||||||
final BooleanProperty useSystemFont = map(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class);
|
final BooleanProperty useSystemFont = map(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class);
|
||||||
|
@ -75,6 +76,8 @@ public class AppPrefs {
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class);
|
mapVaultSpecific(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class);
|
||||||
public final BooleanProperty denyTempScriptCreation =
|
public final BooleanProperty denyTempScriptCreation =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class);
|
mapVaultSpecific(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class);
|
||||||
|
final Property<ExternalPasswordManager> passwordManager =
|
||||||
|
mapVaultSpecific(new SimpleObjectProperty<>(), "passwordManager", ExternalPasswordManager.class);
|
||||||
final StringProperty passwordManagerCommand =
|
final StringProperty passwordManagerCommand =
|
||||||
map(new SimpleStringProperty(""), "passwordManagerCommand", String.class);
|
map(new SimpleStringProperty(""), "passwordManagerCommand", String.class);
|
||||||
final ObjectProperty<StartupBehaviour> startupBehaviour =
|
final ObjectProperty<StartupBehaviour> startupBehaviour =
|
||||||
|
@ -104,6 +107,8 @@ public class AppPrefs {
|
||||||
map(new SimpleBooleanProperty(true), "openConnectionSearchWindowOnConnectionCreation", Boolean.class);
|
map(new SimpleBooleanProperty(true), "openConnectionSearchWindowOnConnectionCreation", Boolean.class);
|
||||||
final ObjectProperty<Path> storageDirectory =
|
final ObjectProperty<Path> storageDirectory =
|
||||||
map(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class);
|
map(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class);
|
||||||
|
final BooleanProperty confirmAllDeletions =
|
||||||
|
map(new SimpleBooleanProperty(false), "confirmAllDeletions", Boolean.class);
|
||||||
final BooleanProperty developerMode = map(new SimpleBooleanProperty(false), "developerMode", Boolean.class);
|
final BooleanProperty developerMode = map(new SimpleBooleanProperty(false), "developerMode", Boolean.class);
|
||||||
final BooleanProperty developerDisableUpdateVersionCheck =
|
final BooleanProperty developerDisableUpdateVersionCheck =
|
||||||
map(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class);
|
map(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class);
|
||||||
|
@ -150,6 +155,10 @@ public class AppPrefs {
|
||||||
return enableHttpApi;
|
return enableHttpApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableBooleanValue dontAllowTerminalRestart() {
|
||||||
|
return dontAllowTerminalRestart;
|
||||||
|
}
|
||||||
|
|
||||||
private final IntegerProperty editorReloadTimeout =
|
private final IntegerProperty editorReloadTimeout =
|
||||||
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
|
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
|
||||||
private final BooleanProperty confirmDeletions =
|
private final BooleanProperty confirmDeletions =
|
||||||
|
@ -253,6 +262,10 @@ public class AppPrefs {
|
||||||
developerMode());
|
developerMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<ExternalPasswordManager> externalPasswordManager() {
|
||||||
|
return passwordManager;
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableValue<SupportedLocale> language() {
|
public ObservableValue<SupportedLocale> language() {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
@ -476,6 +489,9 @@ public class AppPrefs {
|
||||||
if (rdpClientType.get() == null) {
|
if (rdpClientType.get() == null) {
|
||||||
rdpClientType.setValue(ExternalRdpClientType.determineDefault());
|
rdpClientType.setValue(ExternalRdpClientType.determineDefault());
|
||||||
}
|
}
|
||||||
|
if (AppState.get().isInitialLaunch()) {
|
||||||
|
performanceMode.setValue(XPipeDistributionType.get() == XPipeDistributionType.WEBTOP);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<?> getCustomComp(String id) {
|
public Comp<?> getCustomComp(String id) {
|
||||||
|
|
|
@ -75,7 +75,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||||
|
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
try (ShellControl pc = LocalShell.getShell()) {
|
try (ShellControl pc = LocalShell.getShell()) {
|
||||||
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable));
|
return CommandSupport.findProgram(pc, executable).isPresent();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e).omit().handle();
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
return false;
|
return false;
|
||||||
|
@ -115,14 +115,9 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||||
protected Optional<Path> determineFromPath() {
|
protected Optional<Path> determineFromPath() {
|
||||||
// Try to locate if it is in the Path
|
// Try to locate if it is in the Path
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
var out = sc.command(CommandBuilder.ofFunction(
|
var out = CommandSupport.findProgram(sc, executable);
|
||||||
var1 -> var1.getShellDialect().getWhichCommand(executable)))
|
|
||||||
.readStdoutIfPossible();
|
|
||||||
if (out.isPresent()) {
|
if (out.isPresent()) {
|
||||||
var first = out.get().lines().findFirst();
|
return out.map(Path::of);
|
||||||
if (first.isPresent()) {
|
|
||||||
return first.map(String::trim).map(Path::of);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue