mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-23 08:00:23 +00:00
Squash merge xpipe 13 feature branches into master
This commit is contained in:
parent
6dabe53011
commit
5868fcfd33
482 changed files with 7425 additions and 5709 deletions
|
@ -27,7 +27,7 @@ You should therefore always check out the matching version tag for your local re
|
|||
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
||||
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 22 installed to compile the project.
|
||||
If you are on Linux or macOS, you can easily accomplish that by running
|
||||
```bash
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
|
@ -57,7 +57,7 @@ to connect to that debugger through [AttachMe](https://plugins.jetbrains.com/plu
|
|||
|
||||
## Modularity and IDEs
|
||||
|
||||
All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/21/) and make full use of the Java Module System (JPMS).
|
||||
All XPipe components target [Java 22](https://openjdk.java.net/projects/jdk/22/) and make full use of the Java Module System (JPMS).
|
||||
All components are modularized, including all their dependencies.
|
||||
In case a dependency is (sadly) not modularized yet, module information is manually added using [extra-java-module-info](https://github.com/gradlex-org/extra-java-module-info).
|
||||
Further, note that as this is a pretty complicated Java project that fully utilizes modularity,
|
||||
|
@ -65,7 +65,7 @@ many IDEs still have problems building this project properly.
|
|||
|
||||
For example, you can't build this project in eclipse or vscode as it will complain about missing modules.
|
||||
The tested and recommended IDE is IntelliJ.
|
||||
When setting up the project in IntelliJ, make sure that the correct JDK (Java 21)
|
||||
When setting up the project in IntelliJ, make sure that the correct JDK (Java 22)
|
||||
is selected both for the project and for gradle itself.
|
||||
|
||||
## Contributing guide
|
||||
|
|
|
@ -23,8 +23,8 @@ dependencies {
|
|||
api project(':beacon')
|
||||
|
||||
compileOnly 'org.hamcrest:hamcrest:3.0'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.0'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.0'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.3'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.3'
|
||||
|
||||
api 'com.vladsch.flexmark:flexmark:0.64.8'
|
||||
api 'com.vladsch.flexmark:flexmark-util:0.64.8'
|
||||
|
@ -58,8 +58,8 @@ dependencies {
|
|||
api 'org.apache.commons:commons-lang3:3.17.0'
|
||||
api 'io.sentry:sentry:7.14.0'
|
||||
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.datatype', name: 'jackson-datatype-jsr310', version: "2.17.2"
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.18.1"
|
||||
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.18.1"
|
||||
api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
|
||||
api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
||||
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.terminal.TerminalView;
|
||||
import io.xpipe.app.util.AskpassAlert;
|
||||
import io.xpipe.app.util.SecretManager;
|
||||
import io.xpipe.app.util.SecretQueryState;
|
||||
|
@ -34,9 +35,30 @@ public class AskpassExchangeImpl extends AskpassExchange {
|
|||
if (p.getState() != SecretQueryState.NORMAL) {
|
||||
throw new BeaconClientException(SecretQueryState.toErrorMessage(p.getState()));
|
||||
}
|
||||
focusTerminalIfNeeded(msg.getPid());
|
||||
return Response.builder().value(secret.inPlace()).build();
|
||||
}
|
||||
|
||||
private void focusTerminalIfNeeded(long pid) {
|
||||
var found = TerminalView.get().findSession(pid);
|
||||
if (found.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var term = TerminalView.get().getTerminalInstances().stream()
|
||||
.filter(instance ->
|
||||
instance.getTerminalProcess().equals(found.get().getTerminal()))
|
||||
.findFirst();
|
||||
if (term.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var control = term.get().controllable();
|
||||
control.ifPresent(controllableTerminalSession -> {
|
||||
controllableTerminalSession.focus();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresEnabledApi() {
|
||||
return false;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
|
@ -19,8 +19,8 @@ public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange {
|
|||
if (!(e.getStore() instanceof FileSystemStore)) {
|
||||
throw new BeaconClientException("Not a file system connection");
|
||||
}
|
||||
BrowserSessionModel.DEFAULT.openFileSystemSync(
|
||||
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null);
|
||||
BrowserFullSessionModel.DEFAULT.openFileSystemSync(
|
||||
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null, true);
|
||||
AppLayoutModel.get().selectBrowser();
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange {
|
|||
.getStoreEntryIfPresent(msg.getConnection())
|
||||
.orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection()));
|
||||
if (e.getStore() instanceof FixedHierarchyStore) {
|
||||
DataStorage.get().refreshChildren(e, null, true);
|
||||
DataStorage.get().refreshChildren(e, true);
|
||||
} else {
|
||||
e.validateOrThrowAndClose(null);
|
||||
e.validateOrThrow();
|
||||
}
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.TerminalLauncher;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.ConnectionTerminalExchange;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
|
@ -18,9 +18,8 @@ public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange {
|
|||
if (!(e.getStore() instanceof ShellStore shellStore)) {
|
||||
throw new BeaconClientException("Not a shell connection");
|
||||
}
|
||||
try (var sc = shellStore.control().start()) {
|
||||
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
||||
}
|
||||
var sc = shellStore.getOrStartSession();
|
||||
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.core.launcher.LauncherInput;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.launcher.LauncherInput;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.DaemonOpenExchange;
|
||||
|
|
|
@ -2,10 +2,10 @@ package io.xpipe.app.beacon.impl;
|
|||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.beacon.BeaconShellSession;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.ShellStartExchange;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
@ -25,7 +25,9 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
|
|||
var existing = AppBeaconServer.get().getCache().getShellSessions().stream()
|
||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||
.findFirst();
|
||||
var control = (existing.isPresent() ? existing.get().getControl() : s.control());
|
||||
var control = (existing.isPresent()
|
||||
? existing.get().getControl()
|
||||
: s.standaloneControl().start());
|
||||
control.setNonInteractive();
|
||||
control.start();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.util.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.beacon.api.SshLaunchExchange;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.util.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.TerminalLaunchExchange;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.util.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalView;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.TerminalWaitExchange;
|
||||
|
@ -10,7 +11,8 @@ import com.sun.net.httpserver.HttpExchange;
|
|||
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
|
||||
TerminalLauncherManager.waitExchange(msg.getRequest());
|
||||
TerminalView.get().open(msg.getRequest(), msg.getPid());
|
||||
TerminalLauncherManager.waitExchange(msg.getRequest(), msg.getPid());
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -13,13 +13,13 @@ import javafx.collections.ObservableList;
|
|||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BrowserAbstractSessionModel<T extends BrowserSessionTab<?>> {
|
||||
public class BrowserAbstractSessionModel<T extends BrowserSessionTab> {
|
||||
|
||||
protected final ObservableList<T> sessionEntries = FXCollections.observableArrayList();
|
||||
protected final Property<T> selectedEntry = new SimpleObjectProperty<>();
|
||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
|
||||
public void closeAsync(BrowserSessionTab<?> e) {
|
||||
public void closeAsync(BrowserSessionTab e) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
closeSync(e);
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ public class BrowserAbstractSessionModel<T extends BrowserSessionTab<?>> {
|
|||
}
|
||||
}
|
||||
|
||||
public void closeSync(BrowserSessionTab<?> e) {
|
||||
public void closeSync(BrowserSessionTab e) {
|
||||
e.close();
|
||||
synchronized (BrowserAbstractSessionModel.this) {
|
||||
this.sessionEntries.remove(e);
|
|
@ -1,25 +1,25 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
|
||||
import io.xpipe.app.browser.file.BrowserConnectionListComp;
|
||||
import io.xpipe.app.browser.file.BrowserConnectionListFilterComp;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabComp;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.StackComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
@ -37,12 +37,12 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BrowserChooserComp extends DialogComp {
|
||||
public class BrowserFileChooserSessionComp extends DialogComp {
|
||||
|
||||
private final Stage stage;
|
||||
private final BrowserFileChooserModel model;
|
||||
private final BrowserFileChooserSessionModel model;
|
||||
|
||||
public BrowserChooserComp(Stage stage, BrowserFileChooserModel model) {
|
||||
public BrowserFileChooserSessionComp(Stage stage, BrowserFileChooserSessionModel model) {
|
||||
this.stage = stage;
|
||||
this.model = model;
|
||||
}
|
||||
|
@ -50,9 +50,9 @@ public class BrowserChooserComp extends DialogComp {
|
|||
public static void openSingleFile(
|
||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
||||
var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE);
|
||||
DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> {
|
||||
var comp = new BrowserChooserComp(stage, model);
|
||||
var comp = new BrowserFileChooserSessionComp(stage, model);
|
||||
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()))
|
||||
.styleClass("browser")
|
||||
|
@ -114,8 +114,8 @@ public class BrowserChooserComp extends DialogComp {
|
|||
});
|
||||
};
|
||||
|
||||
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
|
||||
var bookmarksList = new BrowserBookmarkComp(
|
||||
var bookmarkTopBar = new BrowserConnectionListFilterComp();
|
||||
var bookmarksList = new BrowserConnectionListComp(
|
||||
BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()),
|
||||
applicable,
|
||||
action,
|
||||
|
@ -138,7 +138,7 @@ public class BrowserChooserComp extends DialogComp {
|
|||
model.getSelectedEntry().subscribe(selected -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (selected != null) {
|
||||
s.getChildren().setAll(new OpenFileSystemComp(selected, false).createRegion());
|
||||
s.getChildren().setAll(new BrowserFileSystemTabComp(selected, false).createRegion());
|
||||
} else {
|
||||
s.getChildren().clear();
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ public class BrowserChooserComp extends DialogComp {
|
|||
});
|
||||
|
||||
var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer)).styleClass("left");
|
||||
var splitPane = new SideSplitPaneComp(vertical, stack)
|
||||
var splitPane = new LeftSplitPaneComp(vertical, stack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.styleClass("background")
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
@ -24,15 +24,15 @@ import java.util.List;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
@Getter
|
||||
public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
|
||||
public class BrowserFileChooserSessionModel extends BrowserAbstractSessionModel<BrowserFileSystemTabModel> {
|
||||
|
||||
private final OpenFileSystemModel.SelectionMode selectionMode;
|
||||
private final BrowserFileSystemTabModel.SelectionMode selectionMode;
|
||||
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
|
||||
|
||||
@Setter
|
||||
private Consumer<List<FileReference>> onFinish;
|
||||
|
||||
public BrowserFileChooserModel(OpenFileSystemModel.SelectionMode selectionMode) {
|
||||
public BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode selectionMode) {
|
||||
this.selectionMode = selectionMode;
|
||||
selectedEntry.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
|
@ -48,7 +48,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
|||
public void finishChooser() {
|
||||
var chosen = new ArrayList<>(fileSelection);
|
||||
|
||||
synchronized (BrowserFileChooserModel.this) {
|
||||
synchronized (BrowserFileChooserSessionModel.this) {
|
||||
var open = selectedEntry.getValue();
|
||||
if (open != null) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
|
@ -66,7 +66,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
|||
}
|
||||
|
||||
public void finishWithoutChoice() {
|
||||
synchronized (BrowserFileChooserModel.this) {
|
||||
synchronized (BrowserFileChooserSessionModel.this) {
|
||||
var open = selectedEntry.getValue();
|
||||
if (open != null) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
|
@ -78,20 +78,20 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
|||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
FailableFunction<BrowserFileSystemTabModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
OpenFileSystemModel model;
|
||||
BrowserFileSystemTabModel model;
|
||||
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
model = new OpenFileSystemModel(this, store, selectionMode);
|
||||
model = new BrowserFileSystemTabModel(this, store, selectionMode);
|
||||
model.init();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserFileChooserModel.this) {
|
||||
synchronized (BrowserFileChooserSessionModel.this) {
|
||||
selectedEntry.setValue(model);
|
||||
sessionEntries.add(model);
|
||||
}
|
|
@ -1,44 +1,108 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
|
||||
import io.xpipe.app.browser.BrowserTransferComp;
|
||||
import io.xpipe.app.browser.file.BrowserConnectionListComp;
|
||||
import io.xpipe.app.browser.file.BrowserConnectionListFilterComp;
|
||||
import io.xpipe.app.browser.file.BrowserTransferComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.AnchorComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.StackComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.AnchorComp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class BrowserSessionComp extends SimpleComp {
|
||||
public class BrowserFullSessionComp extends SimpleComp {
|
||||
|
||||
private final BrowserSessionModel model;
|
||||
private final BrowserFullSessionModel model;
|
||||
|
||||
public BrowserSessionComp(BrowserSessionModel model) {
|
||||
public BrowserFullSessionComp(BrowserFullSessionModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var vertical = createLeftSide();
|
||||
|
||||
var leftSplit = new SimpleDoubleProperty();
|
||||
var rightSplit = new SimpleDoubleProperty();
|
||||
var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit);
|
||||
tabs.apply(struc -> {
|
||||
struc.get().setViewOrder(1);
|
||||
struc.get().setPickOnBounds(false);
|
||||
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setBottomAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setLeftAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
});
|
||||
|
||||
vertical.apply(struc -> {
|
||||
struc.get()
|
||||
.paddingProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> new Insets(tabs.getHeaderHeight().get(), 0, 0, 0), tabs.getHeaderHeight()));
|
||||
});
|
||||
var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy())
|
||||
.apply(struc -> {
|
||||
AnchorPane.setTopAnchor(struc.get(), 3.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
})
|
||||
.styleClass("tab-loading-indicator");
|
||||
|
||||
var pinnedStack = createSplitStack(rightSplit, tabs);
|
||||
|
||||
var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator));
|
||||
var splitPane = new LeftSplitPaneComp(vertical, loadingStack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(d -> {
|
||||
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
|
||||
leftSplit.set(d);
|
||||
});
|
||||
splitPane.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
struc.get().setPickOnBounds(false);
|
||||
});
|
||||
|
||||
splitPane.apply(struc -> {
|
||||
struc.get().skinProperty().subscribe(newValue -> {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(() -> {
|
||||
struc.get().getChildrenUnmodifiable().forEach(node -> {
|
||||
node.setClip(null);
|
||||
node.setPickOnBounds(false);
|
||||
});
|
||||
struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
splitPane.styleClass("browser");
|
||||
return splitPane.createRegion();
|
||||
}
|
||||
|
||||
private Comp<CompStructure<VBox>> createLeftSide() {
|
||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
if (!storeEntryWrapper.getEntry().getValidity().isUsable()) {
|
||||
return false;
|
||||
|
@ -65,9 +129,13 @@ public class BrowserSessionComp extends SimpleComp {
|
|||
});
|
||||
};
|
||||
|
||||
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
|
||||
var bookmarksList = new BrowserBookmarkComp(
|
||||
BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()),
|
||||
var bookmarkTopBar = new BrowserConnectionListFilterComp();
|
||||
var bookmarksList = new BrowserConnectionListComp(
|
||||
BindingsHelper.map(
|
||||
model.getSelectedEntry(),
|
||||
v -> v instanceof BrowserStoreSessionTab<?> st
|
||||
? st.getEntry().get()
|
||||
: null),
|
||||
applicable,
|
||||
action,
|
||||
bookmarkTopBar.getCategory(),
|
||||
|
@ -98,51 +166,49 @@ public class BrowserSessionComp extends SimpleComp {
|
|||
localDownloadStage.maxHeight(200);
|
||||
var vertical =
|
||||
new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
|
||||
return vertical;
|
||||
}
|
||||
|
||||
var split = new SimpleDoubleProperty();
|
||||
var tabs = new BrowserSessionTabsComp(model, split).apply(struc -> {
|
||||
struc.get().setViewOrder(1);
|
||||
struc.get().setPickOnBounds(false);
|
||||
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setBottomAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setLeftAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
});
|
||||
var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy())
|
||||
.apply(struc -> {
|
||||
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
})
|
||||
.styleClass("tab-loading-indicator");
|
||||
var loadingStack = new AnchorComp(List.of(tabs, loadingIndicator));
|
||||
var splitPane = new SideSplitPaneComp(vertical, loadingStack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(d -> {
|
||||
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
|
||||
split.set(d);
|
||||
})
|
||||
.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
struc.get().setPickOnBounds(false);
|
||||
private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) {
|
||||
var cache = new HashMap<BrowserSessionTab, Region>();
|
||||
var splitStack = new StackComp(List.of());
|
||||
splitStack.apply(struc -> {
|
||||
model.getEffectiveRightTab().subscribe((newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var all = model.getAllTabs();
|
||||
cache.keySet().removeIf(browserSessionTab -> !all.contains(browserSessionTab));
|
||||
|
||||
if (newValue == null) {
|
||||
struc.get().getChildren().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var cached = cache.containsKey(newValue);
|
||||
if (!cached) {
|
||||
cache.put(newValue, newValue.comp().createRegion());
|
||||
}
|
||||
var r = cache.get(newValue);
|
||||
struc.get().getChildren().clear();
|
||||
struc.get().getChildren().add(r);
|
||||
|
||||
struc.get().setMinWidth(rightSplit.get());
|
||||
struc.get().setMaxWidth(rightSplit.get());
|
||||
struc.get().setPrefWidth(rightSplit.get());
|
||||
});
|
||||
});
|
||||
|
||||
splitPane.apply(struc -> {
|
||||
struc.get().skinProperty().subscribe(newValue -> {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(() -> {
|
||||
struc.get().getChildrenUnmodifiable().forEach(node -> {
|
||||
node.setClip(null);
|
||||
node.setPickOnBounds(false);
|
||||
});
|
||||
struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1));
|
||||
});
|
||||
}
|
||||
rightSplit.addListener((observable, oldValue, newValue) -> {
|
||||
struc.get().setMinWidth(newValue.doubleValue());
|
||||
struc.get().setMaxWidth(newValue.doubleValue());
|
||||
struc.get().setPrefWidth(newValue.doubleValue());
|
||||
});
|
||||
|
||||
AnchorPane.setBottomAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
tabs.getHeaderHeight().subscribe(number -> {
|
||||
AnchorPane.setTopAnchor(struc.get(), number.doubleValue());
|
||||
});
|
||||
});
|
||||
|
||||
var r = splitPane.createRegion();
|
||||
r.getStyleClass().add("browser");
|
||||
return r;
|
||||
return splitStack;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.browser.file.BrowserHistorySavedState;
|
||||
import io.xpipe.app.browser.file.BrowserHistorySavedStateImpl;
|
||||
import io.xpipe.app.browser.file.BrowserHistoryTabModel;
|
||||
import io.xpipe.app.browser.file.BrowserTransferModel;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Getter
|
||||
public class BrowserFullSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab> {
|
||||
|
||||
public static final BrowserFullSessionModel DEFAULT = new BrowserFullSessionModel();
|
||||
|
||||
static {
|
||||
DEFAULT.getSessionEntries().add(new BrowserHistoryTabModel(DEFAULT));
|
||||
}
|
||||
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final Property<Boolean> draggingFiles = new SimpleBooleanProperty();
|
||||
private final Property<BrowserSessionTab> globalPinnedTab = new SimpleObjectProperty<>();
|
||||
private final ObservableMap<BrowserSessionTab, BrowserSessionTab> splits = FXCollections.observableHashMap();
|
||||
private final ObservableValue<BrowserSessionTab> effectiveRightTab = createEffectiveRightTab();
|
||||
|
||||
private ObservableValue<BrowserSessionTab> createEffectiveRightTab() {
|
||||
return Bindings.createObjectBinding(
|
||||
() -> {
|
||||
var current = selectedEntry.getValue();
|
||||
if (!current.isCloseable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var split = splits.get(current);
|
||||
if (split != null) {
|
||||
return split;
|
||||
}
|
||||
|
||||
var global = globalPinnedTab.getValue();
|
||||
if (global == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (global == selectedEntry.getValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return global;
|
||||
},
|
||||
globalPinnedTab,
|
||||
selectedEntry,
|
||||
splits);
|
||||
}
|
||||
|
||||
public BrowserFullSessionModel() {
|
||||
sessionEntries.addListener((ListChangeListener<? super BrowserSessionTab>) c -> {
|
||||
var v = globalPinnedTab.getValue();
|
||||
if (v != null && !c.getList().contains(v)) {
|
||||
globalPinnedTab.setValue(null);
|
||||
}
|
||||
|
||||
splits.keySet().removeIf(browserSessionTab -> !c.getList().contains(browserSessionTab));
|
||||
});
|
||||
}
|
||||
|
||||
public Set<BrowserSessionTab> getAllTabs() {
|
||||
var set = new HashSet<BrowserSessionTab>();
|
||||
set.addAll(sessionEntries);
|
||||
set.addAll(splits.values());
|
||||
if (globalPinnedTab.getValue() != null) {
|
||||
set.add(globalPinnedTab.getValue());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) {
|
||||
if (splits.containsKey(tab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
splits.put(tab, split);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
split.init();
|
||||
});
|
||||
}
|
||||
|
||||
public void unsplitTab(BrowserSessionTab tab) {
|
||||
if (splits.values().remove(tab)) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
tab.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void pinTab(BrowserSessionTab tab) {
|
||||
if (tab.equals(globalPinnedTab.getValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
globalPinnedTab.setValue(tab);
|
||||
|
||||
var nextIndex = getSessionEntries().indexOf(tab) + 1;
|
||||
if (nextIndex < getSessionEntries().size()) {
|
||||
getSelectedEntry().setValue(getSessionEntries().get(nextIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public void unpinTab(BrowserSessionTab tab) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
globalPinnedTab.setValue(null);
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreState(BrowserHistorySavedState state) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var l = new ArrayList<>(state.getEntries());
|
||||
l.forEach(e -> {
|
||||
restoreStateAsync(e, null);
|
||||
// Don't try to run everything in parallel as that can be taxing
|
||||
ThreadHelper.sleep(1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreStateAsync(BrowserHistorySavedState.Entry e, BooleanProperty busy) {
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
storageEntry.ifPresent(entry -> {
|
||||
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
synchronized (BrowserFullSessionModel.this) {
|
||||
var all = new ArrayList<>(sessionEntries);
|
||||
for (var o : all) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (!o.canImmediatelyClose()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent blocking of shutdown
|
||||
closeAsync(o);
|
||||
}
|
||||
BrowserHistorySavedStateImpl.get().save();
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
localTransfersStage.clear(true);
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<BrowserFileSystemTabModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
openFileSystemSync(store, path, externalBusy, true);
|
||||
});
|
||||
}
|
||||
|
||||
public BrowserFileSystemTabModel openFileSystemSync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<BrowserFileSystemTabModel, String, Exception> path,
|
||||
BooleanProperty externalBusy,
|
||||
boolean select)
|
||||
throws Exception {
|
||||
BrowserFileSystemTabModel model;
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
try (var sessionBusy = new BooleanScope(busy).exclusive().start()) {
|
||||
model = new BrowserFileSystemTabModel(this, store, BrowserFileSystemTabModel.SelectionMode.ALL);
|
||||
model.init();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserFullSessionModel.this) {
|
||||
sessionEntries.add(model);
|
||||
if (select) {
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selectedEntry.setValue(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||
} else {
|
||||
model.initWithDefaultDirectory();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeSync(BrowserSessionTab e) {
|
||||
var split = splits.get(e);
|
||||
if (split != null) {
|
||||
split.close();
|
||||
}
|
||||
|
||||
super.closeSync(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class BrowserSessionTab {
|
||||
|
||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||
protected final String name;
|
||||
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
|
||||
|
||||
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name) {
|
||||
this.browserModel = browserModel;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract Comp<?> comp();
|
||||
|
||||
public abstract boolean canImmediatelyClose();
|
||||
|
||||
public abstract void init() throws Exception;
|
||||
|
||||
public abstract void close();
|
||||
|
||||
public abstract String getIcon();
|
||||
|
||||
public abstract DataColor getColor();
|
||||
|
||||
public boolean isCloseable() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,35 +1,34 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserWelcomeComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ObservableDoubleValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.skin.TabPaneSkin;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import atlantafx.base.controls.RingProgressIndicator;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
@ -39,28 +38,33 @@ import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
|
|||
|
||||
public class BrowserSessionTabsComp extends SimpleComp {
|
||||
|
||||
private final BrowserSessionModel model;
|
||||
private final BrowserFullSessionModel model;
|
||||
private final ObservableDoubleValue leftPadding;
|
||||
private final DoubleProperty rightPadding;
|
||||
|
||||
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding) {
|
||||
@Getter
|
||||
private final DoubleProperty headerHeight;
|
||||
|
||||
public BrowserSessionTabsComp(
|
||||
BrowserFullSessionModel model, ObservableDoubleValue leftPadding, DoubleProperty rightPadding) {
|
||||
this.model = model;
|
||||
this.leftPadding = leftPadding;
|
||||
this.rightPadding = rightPadding;
|
||||
this.headerHeight = new SimpleDoubleProperty();
|
||||
}
|
||||
|
||||
public Region createSimple() {
|
||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(Comp.hspacer().styleClass("top-spacer"), new SimpleBooleanProperty(true));
|
||||
map.put(Comp.of(() -> createTabPane()), Bindings.isNotEmpty(model.getSessionEntries()));
|
||||
map.put(
|
||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
||||
Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return model.getSessionEntries().size() == 0;
|
||||
},
|
||||
model.getSessionEntries()));
|
||||
var multi = new MultiContentComp(map);
|
||||
multi.apply(struc -> ((StackPane) struc.get()).setAlignment(Pos.TOP_CENTER));
|
||||
return multi.createRegion();
|
||||
var tabs = createTabPane();
|
||||
var topBackground = Comp.hspacer().styleClass("top-spacer").createRegion();
|
||||
leftPadding.subscribe(number -> {
|
||||
StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 6));
|
||||
});
|
||||
var stack = new StackPane(topBackground, tabs);
|
||||
stack.setAlignment(Pos.TOP_CENTER);
|
||||
topBackground.prefHeightProperty().bind(headerHeight);
|
||||
topBackground.maxHeightProperty().bind(topBackground.prefHeightProperty());
|
||||
topBackground.prefWidthProperty().bind(tabs.widthProperty());
|
||||
return stack;
|
||||
}
|
||||
|
||||
private TabPane createTabPane() {
|
||||
|
@ -69,6 +73,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
tabs.setTabMinWidth(Region.USE_PREF_SIZE);
|
||||
tabs.setTabMaxWidth(400);
|
||||
tabs.setTabClosingPolicy(ALL_TABS);
|
||||
tabs.setSkin(new TabPaneSkin(tabs));
|
||||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||
toggleStyleClass(tabs, DENSE);
|
||||
|
||||
|
@ -80,22 +85,31 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
tabs.lookupAll(".tab-header-area").forEach(node -> {
|
||||
node.setClip(null);
|
||||
node.setPickOnBounds(false);
|
||||
|
||||
var r = (Region) node;
|
||||
r.prefHeightProperty().bind(r.maxHeightProperty());
|
||||
r.setMinHeight(Region.USE_PREF_SIZE);
|
||||
});
|
||||
tabs.lookupAll(".headers-region").forEach(node -> {
|
||||
node.setClip(null);
|
||||
node.setPickOnBounds(false);
|
||||
|
||||
var r = (Region) node;
|
||||
r.prefHeightProperty().bind(r.maxHeightProperty());
|
||||
r.setMinHeight(Region.USE_PREF_SIZE);
|
||||
});
|
||||
|
||||
Region headerArea = (Region) tabs.lookup(".tab-header-area");
|
||||
headerArea
|
||||
.paddingProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> new Insets(0, 0, 0, -leftPadding.get() + 2), leftPadding));
|
||||
() -> new Insets(2, 0, 4, -leftPadding.get() + 2), leftPadding));
|
||||
headerHeight.bind(headerArea.heightProperty());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var map = new HashMap<BrowserSessionTab<?>, Tab>();
|
||||
var map = new HashMap<BrowserSessionTab, Tab>();
|
||||
|
||||
// Restore state
|
||||
model.getSessionEntries().forEach(v -> {
|
||||
|
@ -156,7 +170,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab<?>>) c -> {
|
||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab>) c -> {
|
||||
while (c.next()) {
|
||||
for (var r : c.getRemoved()) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
@ -245,9 +259,38 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
return tabs;
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab) {
|
||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab, BrowserSessionTab tabModel) {
|
||||
var cm = ContextMenuHelper.create();
|
||||
|
||||
if (tabModel.isCloseable()) {
|
||||
var unpin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("unpinTab"));
|
||||
unpin.visibleProperty()
|
||||
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return model.getGlobalPinnedTab().getValue() != null
|
||||
&& model.getGlobalPinnedTab().getValue().equals(tabModel);
|
||||
},
|
||||
model.getGlobalPinnedTab())));
|
||||
unpin.setOnAction(event -> {
|
||||
model.unpinTab(tabModel);
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(unpin);
|
||||
|
||||
var pin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("pinTab"));
|
||||
pin.visibleProperty()
|
||||
.bind(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return model.getGlobalPinnedTab().getValue() == null;
|
||||
},
|
||||
model.getGlobalPinnedTab())));
|
||||
pin.setOnAction(event -> {
|
||||
model.pinTab(tabModel);
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(pin);
|
||||
}
|
||||
|
||||
var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab"));
|
||||
select.acceleratorProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
|
@ -272,7 +315,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
var close = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeTab"));
|
||||
close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
|
||||
close.setOnAction(event -> {
|
||||
tabs.getTabs().remove(tab);
|
||||
if (tab.isClosable()) {
|
||||
tabs.getTabs().remove(tab);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(close);
|
||||
|
@ -280,7 +325,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeOtherTabs"));
|
||||
closeOthers.setOnAction(event -> {
|
||||
tabs.getTabs()
|
||||
.removeAll(tabs.getTabs().stream().filter(t -> t != tab).toList());
|
||||
.removeAll(tabs.getTabs().stream()
|
||||
.filter(t -> t != tab && t.isClosable())
|
||||
.toList());
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(closeOthers);
|
||||
|
@ -290,7 +337,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
var index = tabs.getTabs().indexOf(tab);
|
||||
tabs.getTabs()
|
||||
.removeAll(tabs.getTabs().stream()
|
||||
.filter(t -> tabs.getTabs().indexOf(t) < index)
|
||||
.filter(t -> tabs.getTabs().indexOf(t) < index && t.isClosable())
|
||||
.toList());
|
||||
event.consume();
|
||||
});
|
||||
|
@ -301,7 +348,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
var index = tabs.getTabs().indexOf(tab);
|
||||
tabs.getTabs()
|
||||
.removeAll(tabs.getTabs().stream()
|
||||
.filter(t -> tabs.getTabs().indexOf(t) > index)
|
||||
.filter(t -> tabs.getTabs().indexOf(t) > index && t.isClosable())
|
||||
.toList());
|
||||
event.consume();
|
||||
});
|
||||
|
@ -311,7 +358,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
closeAll.setAccelerator(
|
||||
new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
|
||||
closeAll.setOnAction(event -> {
|
||||
tabs.getTabs().clear();
|
||||
tabs.getTabs()
|
||||
.removeAll(
|
||||
tabs.getTabs().stream().filter(t -> t.isClosable()).toList());
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(closeAll);
|
||||
|
@ -319,36 +368,84 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
return cm;
|
||||
}
|
||||
|
||||
private Tab createTab(TabPane tabs, BrowserSessionTab<?> model) {
|
||||
private Tab createTab(TabPane tabs, BrowserSessionTab tabModel) {
|
||||
var tab = new Tab();
|
||||
tab.setContextMenu(createContextMenu(tabs, tab));
|
||||
if (tabModel.isCloseable()) {
|
||||
tab.setContextMenu(createContextMenu(tabs, tab, tabModel));
|
||||
}
|
||||
|
||||
var ring = new RingProgressIndicator(0, false);
|
||||
ring.setMinSize(16, 16);
|
||||
ring.setPrefSize(16, 16);
|
||||
ring.setMaxSize(16, 16);
|
||||
ring.progressProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> model.getBusy().get()
|
||||
&& !AppPrefs.get().performanceMode().get()
|
||||
? -1d
|
||||
: 0,
|
||||
PlatformThread.sync(model.getBusy()),
|
||||
AppPrefs.get().performanceMode()));
|
||||
tab.setClosable(tabModel.isCloseable());
|
||||
// Prevent closing while busy
|
||||
tab.setOnCloseRequest(event -> {
|
||||
if (!tabModel.canImmediatelyClose()) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
var image = model.getEntry().get().getEffectiveIconFile();
|
||||
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
||||
if (tabModel.getIcon() != null) {
|
||||
var ring = new RingProgressIndicator(0, false);
|
||||
ring.setMinSize(16, 16);
|
||||
ring.setPrefSize(16, 16);
|
||||
ring.setMaxSize(16, 16);
|
||||
ring.progressProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> tabModel.getBusy().get()
|
||||
&& !AppPrefs.get().performanceMode().get()
|
||||
? -1d
|
||||
: 0,
|
||||
PlatformThread.sync(tabModel.getBusy()),
|
||||
AppPrefs.get().performanceMode()));
|
||||
|
||||
tab.graphicProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return model.getBusy().get() ? ring : logo;
|
||||
},
|
||||
PlatformThread.sync(model.getBusy())));
|
||||
tab.setText(model.getName());
|
||||
var image = tabModel.getIcon();
|
||||
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
||||
|
||||
Comp<?> comp = model.comp();
|
||||
tab.setContent(comp.createRegion());
|
||||
tab.graphicProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return tabModel.getBusy().get() ? ring : logo;
|
||||
},
|
||||
PlatformThread.sync(tabModel.getBusy())));
|
||||
}
|
||||
|
||||
if (tabModel.getBrowserModel() instanceof BrowserFullSessionModel sessionModel) {
|
||||
var global = PlatformThread.sync(sessionModel.getGlobalPinnedTab());
|
||||
tab.textProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return tabModel.getName()
|
||||
+ (global.getValue() == tabModel ? " (" + AppI18n.get("pinned") + ")" : "");
|
||||
},
|
||||
global,
|
||||
AppPrefs.get().language()));
|
||||
} else {
|
||||
tab.setText(tabModel.getName());
|
||||
}
|
||||
|
||||
Comp<?> comp = tabModel.comp();
|
||||
var compRegion = comp.createRegion();
|
||||
var empty = new StackPane();
|
||||
empty.setMinWidth(100);
|
||||
empty.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (tabModel.isCloseable() && tabs.getSelectionModel().getSelectedItem() == tab) {
|
||||
rightPadding.setValue(newValue.doubleValue());
|
||||
}
|
||||
});
|
||||
var split = new SplitPane(compRegion);
|
||||
if (tabModel.isCloseable()) {
|
||||
split.getItems().add(empty);
|
||||
}
|
||||
model.getEffectiveRightTab().subscribe(browserSessionTab -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (browserSessionTab != null && split.getItems().size() > 1) {
|
||||
split.getItems().set(1, empty);
|
||||
} else if (browserSessionTab != null && split.getItems().size() == 1) {
|
||||
split.getItems().add(empty);
|
||||
} else if (browserSessionTab == null && split.getItems().size() > 1) {
|
||||
split.getItems().remove(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
tab.setContent(split);
|
||||
|
||||
var id = UUID.randomUUID().toString();
|
||||
tab.setId(id);
|
||||
|
@ -360,18 +457,19 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
var w = l.maxWidthProperty();
|
||||
l.minWidthProperty().bind(w);
|
||||
l.prefWidthProperty().bind(w);
|
||||
if (!tabModel.isCloseable()) {
|
||||
l.pseudoClassStateChanged(PseudoClass.getPseudoClass("static"), true);
|
||||
}
|
||||
|
||||
var close = (StackPane) tabs.lookup("#" + id + " .tab-close-button");
|
||||
close.setPrefWidth(30);
|
||||
|
||||
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
||||
c.getStyleClass().add("color-box");
|
||||
var color =
|
||||
DataStorage.get().getEffectiveColor(model.getEntry().get());
|
||||
var color = tabModel.getColor();
|
||||
if (color != null) {
|
||||
c.getStyleClass().add(color.getId());
|
||||
}
|
||||
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip()), null).augment(c);
|
||||
c.addEventHandler(
|
||||
DragEvent.DRAG_ENTERED,
|
||||
mouseEvent -> Platform.runLater(
|
|
@ -0,0 +1,38 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class BrowserStoreSessionTab<T extends DataStore> extends BrowserSessionTab {
|
||||
|
||||
protected final DataStoreEntryRef<? extends T> entry;
|
||||
|
||||
public BrowserStoreSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
|
||||
super(browserModel, DataStorage.get().getStoreEntryDisplayName(entry.get()));
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public abstract Comp<?> comp();
|
||||
|
||||
public abstract boolean canImmediatelyClose();
|
||||
|
||||
public abstract void init() throws Exception;
|
||||
|
||||
public abstract void close();
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return entry.get().getEffectiveIconFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataColor getColor() {
|
||||
return DataStorage.get().getEffectiveColor(entry.get());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
|
||||
|
@ -18,25 +18,25 @@ public interface BrowserAction {
|
|||
|
||||
List<BrowserAction> ALL = new ArrayList<>();
|
||||
|
||||
static List<LeafAction> getFlattened(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
static List<BrowserLeafAction> getFlattened(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return ALL.stream()
|
||||
.map(browserAction -> getFlattened(browserAction, model, entries))
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static List<LeafAction> getFlattened(
|
||||
BrowserAction browserAction, OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return browserAction instanceof LeafAction
|
||||
? List.of((LeafAction) browserAction)
|
||||
: ((BranchAction) browserAction)
|
||||
static List<BrowserLeafAction> getFlattened(
|
||||
BrowserAction browserAction, BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return browserAction instanceof BrowserLeafAction
|
||||
? List.of((BrowserLeafAction) browserAction)
|
||||
: ((BrowserBranchAction) browserAction)
|
||||
.getBranchingActions(model, entries).stream()
|
||||
.map(action -> getFlattened(action, model, entries))
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static LeafAction byId(String id, OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
static BrowserLeafAction byId(String id, BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return getFlattened(model, entries).stream()
|
||||
.filter(browserAction -> id.equals(browserAction.getId()))
|
||||
.findAny()
|
||||
|
@ -52,15 +52,15 @@ public interface BrowserAction {
|
|||
: selected;
|
||||
}
|
||||
|
||||
MenuItem toMenuItem(OpenFileSystemModel model, List<BrowserEntry> selected);
|
||||
MenuItem toMenuItem(BrowserFileSystemTabModel model, List<BrowserEntry> selected);
|
||||
|
||||
default void init(OpenFileSystemModel model) throws Exception {}
|
||||
default void init(BrowserFileSystemTabModel model) throws Exception {}
|
||||
|
||||
default String getProFeatureId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
default Node getIcon(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -76,9 +76,9 @@ public interface BrowserAction {
|
|||
return false;
|
||||
}
|
||||
|
||||
ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries);
|
||||
ObservableValue<String> getName(BrowserFileSystemTabModel model, List<BrowserEntry> entries);
|
||||
|
||||
default boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
default boolean isApplicable(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ public interface BrowserAction {
|
|||
return true;
|
||||
}
|
||||
|
||||
default boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
default boolean isActive(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApplicationPathAction extends BrowserAction {
|
||||
public interface BrowserApplicationPathAction extends BrowserAction {
|
||||
|
||||
String getExecutable();
|
||||
|
||||
@Override
|
||||
default void init(OpenFileSystemModel model) {
|
||||
default void init(BrowserFileSystemTabModel model) {
|
||||
// Cache result for later calls
|
||||
model.getCache().isApplicationInPath(getExecutable());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
default boolean isActive(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return model.getCache().isApplicationInPath(getExecutable());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.util.LicenseProvider;
|
||||
|
||||
import javafx.scene.control.Menu;
|
||||
|
@ -11,9 +11,9 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public interface BranchAction extends BrowserAction {
|
||||
public interface BrowserBranchAction extends BrowserAction {
|
||||
|
||||
default MenuItem toMenuItem(OpenFileSystemModel model, List<BrowserEntry> selected) {
|
||||
default MenuItem toMenuItem(BrowserFileSystemTabModel model, List<BrowserEntry> selected) {
|
||||
var m = new Menu(getName(model, selected).getValue() + " ...");
|
||||
for (var sub : getBranchingActions(model, selected)) {
|
||||
var subselected = resolveFilesIfNeeded(selected);
|
||||
|
@ -37,5 +37,5 @@ public interface BranchAction extends BrowserAction {
|
|||
return m;
|
||||
}
|
||||
|
||||
List<? extends BrowserAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries);
|
||||
List<? extends BrowserAction> getBranchingActions(BrowserFileSystemTabModel model, List<BrowserEntry> entries);
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.LicenseProvider;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -17,18 +17,13 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public interface LeafAction extends BrowserAction {
|
||||
public interface BrowserLeafAction extends BrowserAction {
|
||||
|
||||
void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception;
|
||||
void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) throws Exception;
|
||||
|
||||
default Button toButton(Region root, OpenFileSystemModel model, List<BrowserEntry> selected) {
|
||||
default Button toButton(Region root, BrowserFileSystemTabModel model, List<BrowserEntry> selected) {
|
||||
var b = new Button();
|
||||
b.setOnAction(event -> {
|
||||
// Only accept shortcut actions in the current tab
|
||||
if (!model.equals(model.getBrowserModel().getSelectedEntry().getValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||
if (model.getFileSystem() == null) {
|
||||
|
@ -71,13 +66,12 @@ public interface LeafAction extends BrowserAction {
|
|||
return b;
|
||||
}
|
||||
|
||||
default MenuItem toMenuItem(OpenFileSystemModel model, List<BrowserEntry> selected) {
|
||||
default MenuItem toMenuItem(BrowserFileSystemTabModel model, List<BrowserEntry> selected) {
|
||||
var name = getName(model, selected);
|
||||
var mi = new MenuItem();
|
||||
mi.textProperty().bind(BindingsHelper.map(name, s -> {
|
||||
if (getProFeatureId() != null
|
||||
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||
return s + " (Pro)";
|
||||
if (getProFeatureId() != null) {
|
||||
return LicenseProvider.get().getFeature(getProFeatureId()).suffix(s);
|
||||
}
|
||||
return s;
|
||||
}));
|
|
@ -1,8 +1,7 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
@ -18,9 +17,9 @@ import java.util.ArrayList;
|
|||
|
||||
public class BrowserBreadcrumbBar extends SimpleComp {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
|
||||
public BrowserBreadcrumbBar(OpenFileSystemModel model) {
|
||||
public BrowserBreadcrumbBar(BrowserFileSystemTabModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||
import io.xpipe.app.browser.file.LocalFileSystem;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -55,7 +52,7 @@ public class BrowserClipboard {
|
|||
|
||||
var entries = new ArrayList<BrowserEntry>();
|
||||
for (Path file : files) {
|
||||
entries.add(LocalFileSystem.getLocalBrowserEntry(file));
|
||||
entries.add(BrowserLocalFileSystem.getLocalBrowserEntry(file));
|
||||
}
|
||||
|
||||
currentCopyClipboard.setValue(
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.store.*;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
|
@ -19,7 +19,7 @@ import java.util.HashSet;
|
|||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class BrowserBookmarkComp extends SimpleComp {
|
||||
public final class BrowserConnectionListComp extends SimpleComp {
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private final ObservableValue<DataStoreEntry> selected;
|
||||
|
@ -28,7 +28,7 @@ public final class BrowserBookmarkComp extends SimpleComp {
|
|||
private final Property<StoreCategoryWrapper> category;
|
||||
private final Property<String> filter;
|
||||
|
||||
public BrowserBookmarkComp(
|
||||
public BrowserConnectionListComp(
|
||||
ObservableValue<DataStoreEntry> selected,
|
||||
Predicate<StoreEntryWrapper> applicable,
|
||||
BiConsumer<StoreEntryWrapper, BooleanProperty> action,
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.FilterComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.store.StoreCategoryWrapper;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -19,7 +19,7 @@ import lombok.Getter;
|
|||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public final class BrowserBookmarkHeaderComp extends SimpleComp {
|
||||
public final class BrowserConnectionListFilterComp extends SimpleComp {
|
||||
|
||||
private final Property<StoreCategoryWrapper> category =
|
||||
new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
|
||||
|
@ -13,11 +12,11 @@ import java.util.List;
|
|||
|
||||
public final class BrowserContextMenu extends ContextMenu {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
private final BrowserEntry source;
|
||||
private final boolean quickAccess;
|
||||
|
||||
public BrowserContextMenu(OpenFileSystemModel model, BrowserEntry source, boolean quickAccess) {
|
||||
public BrowserContextMenu(BrowserFileSystemTabModel model, BrowserEntry source, boolean quickAccess) {
|
||||
this.model = model;
|
||||
this.source = source;
|
||||
this.quickAccess = quickAccess;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileInfo;
|
||||
|
@ -16,30 +14,23 @@ import io.xpipe.core.store.FileNames;
|
|||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.AccessibleRole;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.skin.TableViewSkin;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -81,7 +72,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
: null));
|
||||
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
|
||||
filenameCol.setSortType(ASCENDING);
|
||||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing(), col.getTableView()));
|
||||
filenameCol.setCellFactory(col ->
|
||||
new BrowserFileListNameCell(fileList, typedSelection, fileList.getEditing(), col.getTableView()));
|
||||
filenameCol.setReorderable(false);
|
||||
filenameCol.setResizable(false);
|
||||
|
||||
|
@ -130,7 +122,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
table.setAccessibleText("Directory contents");
|
||||
table.setPlaceholder(new Region());
|
||||
table.getStyleClass().add(Styles.STRIPED);
|
||||
table.getColumns().setAll(filenameCol, sizeCol, modeCol, ownerCol, mtimeCol);
|
||||
table.getColumns().setAll(filenameCol, mtimeCol, modeCol, ownerCol, sizeCol);
|
||||
table.getSortOrder().add(filenameCol);
|
||||
table.setFocusTraversable(true);
|
||||
table.setSortPolicy(param -> {
|
||||
|
@ -138,6 +130,34 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
return true;
|
||||
});
|
||||
table.setFixedCellSize(32.0);
|
||||
|
||||
prepareColumnVisibility(table, ownerCol, filenameCol);
|
||||
prepareTableScrollFix(table);
|
||||
prepareTableSelectionModel(table);
|
||||
prepareTableShortcuts(table);
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol);
|
||||
prepareTypedSelectionModel(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static void prepareTableScrollFix(TableView<BrowserEntry> table) {
|
||||
table.lookupAll(".scroll-bar").stream()
|
||||
.filter(node -> node.getPseudoClassStates().contains(PseudoClass.getPseudoClass("horizontal")))
|
||||
.findFirst()
|
||||
.ifPresent(node -> {
|
||||
Region region = (Region) node;
|
||||
region.setMinHeight(0);
|
||||
region.setPrefHeight(0);
|
||||
region.setMaxHeight(0);
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareColumnVisibility(
|
||||
TableView<BrowserEntry> table,
|
||||
TableColumn<BrowserEntry, String> ownerCol,
|
||||
TableColumn<BrowserEntry, String> filenameCol) {
|
||||
var os = fileList.getFileSystemModel()
|
||||
.getFileSystem()
|
||||
.getShell()
|
||||
|
@ -150,24 +170,6 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
var width = getFilenameWidth(table);
|
||||
filenameCol.setPrefWidth(width);
|
||||
});
|
||||
|
||||
table.lookupAll(".scroll-bar").stream()
|
||||
.filter(node -> node.getPseudoClassStates().contains(PseudoClass.getPseudoClass("horizontal")))
|
||||
.findFirst()
|
||||
.ifPresent(node -> {
|
||||
Region region = (Region) node;
|
||||
region.setMinHeight(0);
|
||||
region.setPrefHeight(0);
|
||||
region.setMaxHeight(0);
|
||||
});
|
||||
|
||||
prepareTableSelectionModel(table);
|
||||
prepareTableShortcuts(table);
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol);
|
||||
prepareTypedSelectionModel(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private double getFilenameWidth(TableView<?> tableView) {
|
||||
|
@ -274,8 +276,15 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
table.getSelectionModel().setCellSelectionEnabled(false);
|
||||
|
||||
var updateFromModel = new BooleanScope(new SimpleBooleanProperty());
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
fileList.getSelection().setAll(c.getList());
|
||||
if (updateFromModel.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var ignored = updateFromModel) {
|
||||
fileList.getSelection().setAll(c.getList());
|
||||
}
|
||||
});
|
||||
|
||||
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
|
@ -284,16 +293,27 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (c.getList().isEmpty()) {
|
||||
var tableIndices = table.getSelectionModel().getSelectedItems().stream()
|
||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||
.toArray();
|
||||
var indices = c.getList().stream()
|
||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||
.toArray();
|
||||
if (Arrays.equals(indices, tableIndices)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indices.length == 0) {
|
||||
table.getSelectionModel().clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
var indices = c.getList().stream()
|
||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||
.toArray();
|
||||
table.getSelectionModel()
|
||||
.selectIndices(table.getItems().indexOf(c.getList().getFirst()), indices);
|
||||
if (indices.length == 1) {
|
||||
table.getSelectionModel().clearAndSelect(indices[0]);
|
||||
} else {
|
||||
table.getSelectionModel().clearSelection();
|
||||
table.getSelectionModel().selectIndices(indices[0], indices);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -313,8 +333,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
.filter(browserAction -> browserAction.getShortcut().match(event))
|
||||
.findAny();
|
||||
action.ifPresent(browserAction -> {
|
||||
// Prevent concurrent modification by creating copy on platform thread
|
||||
var selectionCopy = new ArrayList<>(selected);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
browserAction.execute(fileList.getFileSystemModel(), selected);
|
||||
browserAction.execute(fileList.getFileSystemModel(), selectionCopy);
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
|
@ -610,157 +632,4 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FilenameCell extends TableCell<BrowserEntry, String> {
|
||||
|
||||
private final StringProperty img = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
|
||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||
|
||||
public FilenameCell(Property<BrowserEntry> editing, TableView<BrowserEntry> tableView) {
|
||||
accessibleTextProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return getItem() != null ? getItem() : null;
|
||||
},
|
||||
itemProperty()));
|
||||
setAccessibleRole(AccessibleRole.TEXT);
|
||||
|
||||
var textField = new LazyTextFieldComp(text)
|
||||
.minWidth(USE_PREF_SIZE)
|
||||
.createStructure()
|
||||
.get();
|
||||
var quickAccess = new BrowserQuickAccessButtonComp(
|
||||
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||
.hide(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (getTableRow() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var item = getTableRow().getItem();
|
||||
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
||||
var isParentLink = item.getRawFileEntry()
|
||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
||||
return notDir || isParentLink;
|
||||
},
|
||||
itemProperty()))
|
||||
.focusTraversable(false)
|
||||
.createRegion();
|
||||
|
||||
editing.addListener((observable, oldValue, newValue) -> {
|
||||
if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
textField.setDisable(false);
|
||||
textField.requestFocus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ChangeListener<String> listener = (observable, oldValue, newValue) -> {
|
||||
if (updating.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getTableRow().requestFocus();
|
||||
var it = getTableRow().getItem();
|
||||
editing.setValue(null);
|
||||
ThreadHelper.runAsync(() -> {
|
||||
if (it == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var r = fileList.rename(it, newValue);
|
||||
Platform.runLater(() -> {
|
||||
updateItem(getItem(), isEmpty());
|
||||
fileList.getSelection().setAll(r);
|
||||
getTableView().scrollTo(r);
|
||||
});
|
||||
});
|
||||
};
|
||||
text.addListener(listener);
|
||||
|
||||
Node imageView = PrettyImageHelper.ofFixedSize(img, 24, 24).createRegion();
|
||||
HBox graphic = new HBox(imageView, new Spacer(5), quickAccess, new Spacer(1), textField);
|
||||
quickAccess.prefHeightProperty().bind(graphic.heightProperty());
|
||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||
graphic.setPrefHeight(34);
|
||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||
setGraphic(graphic);
|
||||
|
||||
InputHelper.onExactKeyCode(tableView, KeyCode.RIGHT, false, event -> {
|
||||
var selected = fileList.getSelection();
|
||||
if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) {
|
||||
((ButtonBase) quickAccess).fire();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
InputHelper.onExactKeyCode(tableView, KeyCode.SPACE, true, event -> {
|
||||
var selection = typedSelection.get() + " ";
|
||||
var found = fileList.getShown().getValue().stream()
|
||||
.filter(browserEntry ->
|
||||
browserEntry.getFileName().toLowerCase().startsWith(selection))
|
||||
.findFirst();
|
||||
// Ugly fix to prevent space from showing the menu when there is a file matching
|
||||
// Due to the table view input map, these events always get sent and consumed, not allowing us to
|
||||
// differentiate between these cases
|
||||
if (found.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = fileList.getSelection();
|
||||
// Only show one menu across all selected entries
|
||||
if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) {
|
||||
var cm = new BrowserContextMenu(
|
||||
fileList.getFileSystemModel(), getTableRow().getItem(), false);
|
||||
ContextMenuHelper.toggleShow(cm, this, Side.RIGHT);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(String newName, boolean empty) {
|
||||
if (updating.get()) {
|
||||
super.updateItem(newName, empty);
|
||||
return;
|
||||
}
|
||||
|
||||
try (var ignored = new BooleanScope(updating).start()) {
|
||||
super.updateItem(newName, empty);
|
||||
if (empty || newName == null || getTableRow().getItem() == null) {
|
||||
// Don't set image as that would trigger image comp update
|
||||
// and cells are emptied on each change, leading to unnecessary changes
|
||||
// img.set(null);
|
||||
|
||||
// Visibility seems to be bugged, so use opacity
|
||||
setOpacity(0.0);
|
||||
} else {
|
||||
img.set(getTableRow().getItem().getIcon());
|
||||
|
||||
var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY;
|
||||
pseudoClassStateChanged(FOLDER, isDirectory);
|
||||
|
||||
var normalName = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.LINK
|
||||
? getTableRow().getItem().getFileName() + " -> "
|
||||
+ getTableRow()
|
||||
.getItem()
|
||||
.getRawFileEntry()
|
||||
.resolved()
|
||||
.getPath()
|
||||
: getTableRow().getItem().getFileName();
|
||||
var fileName = normalName;
|
||||
var hidden =
|
||||
getTableRow().getItem().getRawFileEntry().getInfo().explicitlyHidden()
|
||||
|| fileName.startsWith(".");
|
||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||
text.set(fileName);
|
||||
// Visibility seems to be bugged, so use opacity
|
||||
setOpacity(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserClipboard;
|
||||
import io.xpipe.app.browser.BrowserSelectionListComp;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
|
@ -219,7 +217,7 @@ public class BrowserFileListCompEntry {
|
|||
return;
|
||||
}
|
||||
|
||||
if (model.getFileSystemModel().getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||
if (model.getFileSystemModel().getBrowserModel() instanceof BrowserFullSessionModel sessionModel) {
|
||||
sessionModel.getDraggingFiles().setValue(true);
|
||||
}
|
||||
var selected = model.getSelection();
|
||||
|
@ -229,7 +227,7 @@ public class BrowserFileListCompEntry {
|
|||
selected,
|
||||
event.isAltDown() ? BrowserFileTransferMode.MOVE : BrowserFileTransferMode.NORMAL));
|
||||
|
||||
Image image = BrowserSelectionListComp.snapshot(selected);
|
||||
Image image = BrowserFileSelectionListComp.snapshot(selected);
|
||||
db.setDragView(image, -20, 15);
|
||||
|
||||
event.setDragDetect(true);
|
||||
|
@ -237,7 +235,7 @@ public class BrowserFileListCompEntry {
|
|||
}
|
||||
|
||||
public void onDragDone(DragEvent event) {
|
||||
if (model.getFileSystemModel().getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||
if (model.getFileSystemModel().getBrowserModel() instanceof BrowserFullSessionModel sessionModel) {
|
||||
sessionModel.getDraggingFiles().setValue(false);
|
||||
event.consume();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -20,12 +19,12 @@ import javafx.scene.layout.HBox;
|
|||
import atlantafx.base.theme.Styles;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
||||
public class BrowserFileListFilterComp extends Comp<BrowserFileListFilterComp.Structure> {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
private final Property<String> filterString;
|
||||
|
||||
public BrowserFilterComp(OpenFileSystemModel model, Property<String> filterString) {
|
||||
public BrowserFileListFilterComp(BrowserFileSystemTabModel model, Property<String> filterString) {
|
||||
this.model = model;
|
||||
this.filterString = filterString;
|
||||
}
|
||||
|
@ -38,6 +37,10 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
|
|||
button.minWidthProperty().bind(button.heightProperty());
|
||||
button.setFocusTraversable(true);
|
||||
InputHelper.onExactKeyCode(text, KeyCode.ESCAPE, true, keyEvent -> {
|
||||
if (!expanded.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
text.clear();
|
||||
button.fire();
|
||||
keyEvent.consume();
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
@ -27,9 +26,9 @@ public final class BrowserFileListModel {
|
|||
static final Comparator<BrowserEntry> FILE_TYPE_COMPARATOR =
|
||||
Comparator.comparing(path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
||||
|
||||
private final OpenFileSystemModel.SelectionMode selectionMode;
|
||||
private final BrowserFileSystemTabModel.SelectionMode selectionMode;
|
||||
|
||||
private final OpenFileSystemModel fileSystemModel;
|
||||
private final BrowserFileSystemTabModel fileSystemModel;
|
||||
private final Property<Comparator<BrowserEntry>> comparatorProperty =
|
||||
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
|
||||
private final Property<List<BrowserEntry>> all = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
|
@ -40,7 +39,8 @@ public final class BrowserFileListModel {
|
|||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||
private final Property<BrowserEntry> editing = new SimpleObjectProperty<>();
|
||||
|
||||
public BrowserFileListModel(OpenFileSystemModel.SelectionMode selectionMode, OpenFileSystemModel fileSystemModel) {
|
||||
public BrowserFileListModel(
|
||||
BrowserFileSystemTabModel.SelectionMode selectionMode, BrowserFileSystemTabModel fileSystemModel) {
|
||||
this.selectionMode = selectionMode;
|
||||
this.fileSystemModel = fileSystemModel;
|
||||
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableStringValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.AccessibleRole;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
|
||||
class BrowserFileListNameCell extends TableCell<BrowserEntry, String> {
|
||||
|
||||
private final BrowserFileListModel fileList;
|
||||
private final ObservableStringValue typedSelection;
|
||||
private final StringProperty img = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
|
||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||
|
||||
public BrowserFileListNameCell(
|
||||
BrowserFileListModel fileList,
|
||||
ObservableStringValue typedSelection,
|
||||
Property<BrowserEntry> editing,
|
||||
TableView<BrowserEntry> tableView) {
|
||||
this.fileList = fileList;
|
||||
this.typedSelection = typedSelection;
|
||||
|
||||
accessibleTextProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return getItem() != null ? getItem() : null;
|
||||
},
|
||||
itemProperty()));
|
||||
setAccessibleRole(AccessibleRole.TEXT);
|
||||
|
||||
var textField = new LazyTextFieldComp(text)
|
||||
.minWidth(USE_PREF_SIZE)
|
||||
.createStructure()
|
||||
.get();
|
||||
var quickAccess = createQuickAccessButton();
|
||||
setupShortcuts(tableView, (ButtonBase) quickAccess);
|
||||
setupRename(fileList, textField, editing);
|
||||
|
||||
Node imageView = PrettyImageHelper.ofFixedSize(img, 24, 24).createRegion();
|
||||
HBox graphic = new HBox(imageView, new Spacer(5), quickAccess, new Spacer(1), textField);
|
||||
quickAccess.prefHeightProperty().bind(graphic.heightProperty());
|
||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||
graphic.setPrefHeight(34);
|
||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||
setGraphic(graphic);
|
||||
}
|
||||
|
||||
private Region createQuickAccessButton() {
|
||||
var quickAccess = new BrowserQuickAccessButtonComp(() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||
.hide(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (getTableRow() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var item = getTableRow().getItem();
|
||||
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
||||
var isParentLink = item.getRawFileEntry()
|
||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
||||
return notDir || isParentLink;
|
||||
},
|
||||
itemProperty()))
|
||||
.focusTraversable(false)
|
||||
.createRegion();
|
||||
return quickAccess;
|
||||
}
|
||||
|
||||
private void setupShortcuts(TableView<BrowserEntry> tableView, ButtonBase quickAccess) {
|
||||
InputHelper.onExactKeyCode(tableView, KeyCode.RIGHT, false, event -> {
|
||||
var selected = fileList.getSelection();
|
||||
if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) {
|
||||
quickAccess.fire();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
InputHelper.onExactKeyCode(tableView, KeyCode.SPACE, true, event -> {
|
||||
var selection = typedSelection.get() + " ";
|
||||
var found = fileList.getShown().getValue().stream()
|
||||
.filter(browserEntry ->
|
||||
browserEntry.getFileName().toLowerCase().startsWith(selection))
|
||||
.findFirst();
|
||||
// Ugly fix to prevent space from showing the menu when there is a file matching
|
||||
// Due to the table view input map, these events always get sent and consumed, not allowing us to
|
||||
// differentiate between these cases
|
||||
if (found.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = fileList.getSelection();
|
||||
// Only show one menu across all selected entries
|
||||
if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) {
|
||||
var cm = new BrowserContextMenu(
|
||||
fileList.getFileSystemModel(), getTableRow().getItem(), false);
|
||||
ContextMenuHelper.toggleShow(cm, this, Side.RIGHT);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupRename(BrowserFileListModel fileList, TextField textField, Property<BrowserEntry> editing) {
|
||||
ChangeListener<String> listener = (observable, oldValue, newValue) -> {
|
||||
if (updating.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getTableRow().requestFocus();
|
||||
var it = getTableRow().getItem();
|
||||
editing.setValue(null);
|
||||
ThreadHelper.runAsync(() -> {
|
||||
if (it == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var r = fileList.rename(it, newValue);
|
||||
Platform.runLater(() -> {
|
||||
updateItem(getItem(), isEmpty());
|
||||
fileList.getSelection().setAll(r);
|
||||
getTableView().scrollTo(r);
|
||||
});
|
||||
});
|
||||
};
|
||||
text.addListener(listener);
|
||||
|
||||
editing.addListener((observable, oldValue, newValue) -> {
|
||||
if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
textField.setDisable(false);
|
||||
textField.requestFocus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(String newName, boolean empty) {
|
||||
if (updating.get()) {
|
||||
super.updateItem(newName, empty);
|
||||
return;
|
||||
}
|
||||
|
||||
try (var ignored = new BooleanScope(updating).start()) {
|
||||
super.updateItem(newName, empty);
|
||||
if (empty || newName == null || getTableRow().getItem() == null) {
|
||||
// Don't set image as that would trigger image comp update
|
||||
// and cells are emptied on each change, leading to unnecessary changes
|
||||
// img.set(null);
|
||||
|
||||
// Visibility seems to be bugged, so use opacity
|
||||
setOpacity(0.0);
|
||||
} else {
|
||||
img.set(getTableRow().getItem().getIcon());
|
||||
|
||||
var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY;
|
||||
pseudoClassStateChanged(PseudoClass.getPseudoClass("folder"), isDirectory);
|
||||
|
||||
var normalName = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.LINK
|
||||
? getTableRow().getItem().getFileName() + " -> "
|
||||
+ getTableRow()
|
||||
.getItem()
|
||||
.getRawFileEntry()
|
||||
.resolved()
|
||||
.getPath()
|
||||
: getTableRow().getItem().getFileName();
|
||||
var fileName = normalName;
|
||||
var hidden = getTableRow().getItem().getRawFileEntry().getInfo().explicitlyHidden()
|
||||
|| fileName.startsWith(".");
|
||||
getTableRow().pseudoClassStateChanged(PseudoClass.getPseudoClass("hidden"), hidden);
|
||||
text.set(fileName);
|
||||
// Visibility seems to be bugged, so use opacity
|
||||
setOpacity(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
|
@ -20,7 +19,7 @@ import java.util.Objects;
|
|||
|
||||
public class BrowserFileOpener {
|
||||
|
||||
private static OutputStream openFileOutput(OpenFileSystemModel model, FileEntry file, long totalBytes)
|
||||
private static OutputStream openFileOutput(BrowserFileSystemTabModel model, FileEntry file, long totalBytes)
|
||||
throws Exception {
|
||||
var fileSystem = model.getFileSystem();
|
||||
if (model.isClosed() || fileSystem.getShell().isEmpty()) {
|
||||
|
@ -68,7 +67,7 @@ public class BrowserFileOpener {
|
|||
return Objects.hash(entry.getPath(), entry.getFileSystem(), entry.getKind(), entry.getInfo());
|
||||
}
|
||||
|
||||
public static void openWithAnyApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||
public static void openWithAnyApplication(BrowserFileSystemTabModel model, FileEntry entry) {
|
||||
var file = entry.getPath();
|
||||
var key = calculateKey(entry);
|
||||
FileBridge.get()
|
||||
|
@ -89,7 +88,7 @@ public class BrowserFileOpener {
|
|||
s -> FileOpener.openWithAnyApplication(s));
|
||||
}
|
||||
|
||||
public static void openInDefaultApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||
public static void openInDefaultApplication(BrowserFileSystemTabModel model, FileEntry entry) {
|
||||
var file = entry.getPath();
|
||||
var key = calculateKey(entry);
|
||||
FileBridge.get()
|
||||
|
@ -110,7 +109,7 @@ public class BrowserFileOpener {
|
|||
s -> FileOpener.openInDefaultApplication(s));
|
||||
}
|
||||
|
||||
public static void openInTextEditor(OpenFileSystemModel model, FileEntry entry) {
|
||||
public static void openInTextEditor(BrowserFileSystemTabModel model, FileEntry entry) {
|
||||
var editor = AppPrefs.get().externalEditor().getValue();
|
||||
if (editor == null) {
|
||||
return;
|
|
@ -1,13 +1,12 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIcons;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.VBoxViewComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
@ -25,7 +24,7 @@ import java.util.function.Function;
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BrowserFileOverviewComp extends SimpleComp {
|
||||
|
||||
OpenFileSystemModel model;
|
||||
BrowserFileSystemTabModel model;
|
||||
ObservableList<FileEntry> list;
|
||||
boolean grow;
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppStyle;
|
||||
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.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
@ -31,17 +30,17 @@ import java.util.function.Function;
|
|||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AllArgsConstructor
|
||||
public class BrowserSelectionListComp extends SimpleComp {
|
||||
public class BrowserFileSelectionListComp extends SimpleComp {
|
||||
|
||||
ObservableList<BrowserEntry> list;
|
||||
Function<BrowserEntry, ObservableValue<String>> nameTransformation;
|
||||
|
||||
public BrowserSelectionListComp(ObservableList<BrowserEntry> list) {
|
||||
public BrowserFileSelectionListComp(ObservableList<BrowserEntry> list) {
|
||||
this(list, entry -> new SimpleStringProperty(entry.getFileName()));
|
||||
}
|
||||
|
||||
public static Image snapshot(ObservableList<BrowserEntry> list) {
|
||||
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
||||
var r = new BrowserFileSelectionListComp(list).styleClass("drag").createRegion();
|
||||
var scene = new Scene(r);
|
||||
AppWindowHelper.setupStylesheets(scene);
|
||||
AppStyle.addStylesheets(scene);
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.util.ShellControlCache;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
@ -12,14 +12,14 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class OpenFileSystemCache extends ShellControlCache {
|
||||
public class BrowserFileSystemCache extends ShellControlCache {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
private final String username;
|
||||
private final Map<Integer, String> users = new LinkedHashMap<>();
|
||||
private final Map<Integer, String> groups = new LinkedHashMap<>();
|
||||
|
||||
public OpenFileSystemCache(OpenFileSystemModel model) throws Exception {
|
||||
public BrowserFileSystemCache(BrowserFileSystemTabModel model) throws Exception {
|
||||
super(model.getFileSystem().getShell().orElseThrow());
|
||||
this.model = model;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
@ -11,9 +10,9 @@ import io.xpipe.core.store.FileSystem;
|
|||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public class FileSystemHelper {
|
||||
public class BrowserFileSystemHelper {
|
||||
|
||||
public static String adjustPath(OpenFileSystemModel model, String path) {
|
||||
public static String adjustPath(BrowserFileSystemTabModel model, String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -46,7 +45,7 @@ public class FileSystemHelper {
|
|||
return path;
|
||||
}
|
||||
|
||||
public static String evaluatePath(OpenFileSystemModel model, String path) throws Exception {
|
||||
public static String evaluatePath(BrowserFileSystemTabModel model, String path) throws Exception {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -67,7 +66,7 @@ public class FileSystemHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static String resolveDirectoryPath(OpenFileSystemModel model, String path, boolean allowRewrite)
|
||||
public static String resolveDirectoryPath(BrowserFileSystemTabModel model, String path, boolean allowRewrite)
|
||||
throws Exception {
|
||||
if (path == null) {
|
||||
return null;
|
||||
|
@ -98,7 +97,7 @@ public class FileSystemHelper {
|
|||
return FileNames.toDirectory(resolved);
|
||||
}
|
||||
|
||||
public static void validateDirectoryPath(OpenFileSystemModel model, String path, boolean verifyExists)
|
||||
public static void validateDirectoryPath(BrowserFileSystemTabModel model, String path, boolean verifyExists)
|
||||
throws Exception {
|
||||
if (path == null) {
|
||||
return;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
|
@ -9,7 +9,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class OpenFileSystemHistory {
|
||||
public final class BrowserFileSystemHistory {
|
||||
|
||||
private final IntegerProperty cursor = new SimpleIntegerProperty(-1);
|
||||
private final List<String> history = new ArrayList<>();
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
@ -31,35 +31,36 @@ import java.util.stream.Collectors;
|
|||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@JsonSerialize(using = OpenFileSystemSavedState.Serializer.class)
|
||||
@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class)
|
||||
public class OpenFileSystemSavedState {
|
||||
@JsonSerialize(using = BrowserFileSystemSavedState.Serializer.class)
|
||||
@JsonDeserialize(using = BrowserFileSystemSavedState.Deserializer.class)
|
||||
public class BrowserFileSystemSavedState {
|
||||
|
||||
private static final Timer TIMEOUT_TIMER = new Timer(true);
|
||||
private static final int STORED = 10;
|
||||
private static final int STORED = 15;
|
||||
|
||||
@Setter
|
||||
private OpenFileSystemModel model;
|
||||
private BrowserFileSystemTabModel model;
|
||||
|
||||
private String lastDirectory;
|
||||
|
||||
@NonNull
|
||||
private ObservableList<RecentEntry> recentDirectories;
|
||||
|
||||
public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
|
||||
public BrowserFileSystemSavedState(String lastDirectory, @NonNull ObservableList<RecentEntry> recentDirectories) {
|
||||
this.lastDirectory = lastDirectory;
|
||||
this.recentDirectories = recentDirectories;
|
||||
}
|
||||
|
||||
public OpenFileSystemSavedState() {
|
||||
public BrowserFileSystemSavedState() {
|
||||
lastDirectory = null;
|
||||
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
|
||||
}
|
||||
|
||||
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
||||
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
||||
return new OpenFileSystemSavedState();
|
||||
});
|
||||
static BrowserFileSystemSavedState loadForStore(BrowserFileSystemTabModel model) {
|
||||
var state = AppCache.getNonNull(
|
||||
"fs-state-" + model.getEntry().get().getUuid(), BrowserFileSystemSavedState.class, () -> {
|
||||
return new BrowserFileSystemSavedState();
|
||||
});
|
||||
state.setModel(model);
|
||||
return state;
|
||||
}
|
||||
|
@ -121,14 +122,14 @@ public class OpenFileSystemSavedState {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Serializer extends StdSerializer<OpenFileSystemSavedState> {
|
||||
public static class Serializer extends StdSerializer<BrowserFileSystemSavedState> {
|
||||
|
||||
protected Serializer() {
|
||||
super(OpenFileSystemSavedState.class);
|
||||
super(BrowserFileSystemSavedState.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(OpenFileSystemSavedState value, JsonGenerator gen, SerializerProvider provider)
|
||||
public void serialize(BrowserFileSystemSavedState value, JsonGenerator gen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
var node = JsonNodeFactory.instance.objectNode();
|
||||
node.set("recentDirectories", JacksonMapper.getDefault().valueToTree(value.getRecentDirectories()));
|
||||
|
@ -136,10 +137,10 @@ public class OpenFileSystemSavedState {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Deserializer extends StdDeserializer<OpenFileSystemSavedState> {
|
||||
public static class Deserializer extends StdDeserializer<BrowserFileSystemSavedState> {
|
||||
|
||||
protected Deserializer() {
|
||||
super(OpenFileSystemSavedState.class);
|
||||
super(BrowserFileSystemSavedState.class);
|
||||
}
|
||||
|
||||
private static <T> Predicate<T> distinctBy(Function<? super T, ?> f) {
|
||||
|
@ -149,7 +150,7 @@ public class OpenFileSystemSavedState {
|
|||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public OpenFileSystemSavedState deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
public BrowserFileSystemSavedState deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
||||
JavaType javaType = JacksonMapper.getDefault()
|
||||
.getTypeFactory()
|
||||
|
@ -163,7 +164,7 @@ public class OpenFileSystemSavedState {
|
|||
.map(recentEntry -> new RecentEntry(FileNames.toDirectory(recentEntry.directory), recentEntry.time))
|
||||
.filter(distinctBy(recentEntry -> recentEntry.getDirectory()))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
return new OpenFileSystemSavedState(null, FXCollections.observableList(cleaned));
|
||||
return new BrowserFileSystemSavedState(null, FXCollections.observableList(cleaned));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,15 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserFilterComp;
|
||||
import io.xpipe.app.browser.BrowserNavBar;
|
||||
import io.xpipe.app.browser.BrowserOverviewComp;
|
||||
import io.xpipe.app.browser.BrowserStatusBarComp;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.file.BrowserContextMenu;
|
||||
import io.xpipe.app.browser.file.BrowserFileListComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -37,12 +31,12 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OpenFileSystemComp extends SimpleComp {
|
||||
public class BrowserFileSystemTabComp extends SimpleComp {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
private final boolean showStatusBar;
|
||||
|
||||
public OpenFileSystemComp(OpenFileSystemModel model, boolean showStatusBar) {
|
||||
public BrowserFileSystemTabComp(BrowserFileSystemTabModel model, boolean showStatusBar) {
|
||||
this.model = model;
|
||||
this.showStatusBar = showStatusBar;
|
||||
}
|
||||
|
@ -82,12 +76,12 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
menuButton.disableProperty().bind(model.getInOverview());
|
||||
menuButton.setAccessibleText("Directory options");
|
||||
|
||||
var filter = new BrowserFilterComp(model, model.getFilter()).createStructure();
|
||||
var filter = new BrowserFileListFilterComp(model, model.getFilter()).createStructure();
|
||||
|
||||
var topBar = new HBox();
|
||||
topBar.setAlignment(Pos.CENTER);
|
||||
topBar.getStyleClass().add("top-bar");
|
||||
var navBar = new BrowserNavBar(model).createStructure();
|
||||
var navBar = new BrowserNavBarComp(model).createStructure();
|
||||
filter.textField().prefHeightProperty().bind(navBar.get().heightProperty());
|
||||
AppFont.medium(navBar.get());
|
||||
topBar.getChildren()
|
||||
|
@ -182,7 +176,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
var home = new BrowserOverviewComp(model).styleClass("browser-content");
|
||||
var home = new BrowserOverviewComp(model).styleClass("browser-overview");
|
||||
var stack = new MultiContentComp(Map.of(
|
||||
home,
|
||||
model.getCurrentPath().isNull(),
|
|
@ -1,34 +1,30 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserSavedState;
|
||||
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
||||
import io.xpipe.app.browser.BrowserTransferProgress;
|
||||
import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.browser.BrowserStoreSessionTab;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.file.BrowserFileListModel;
|
||||
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||
import io.xpipe.app.browser.file.BrowserFileTransferOperation;
|
||||
import io.xpipe.app.browser.file.FileSystemHelper;
|
||||
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionTab;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.*;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.TerminalLauncher;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.process.ShellOpenFunction;
|
||||
import io.xpipe.core.process.*;
|
||||
import io.xpipe.core.store.*;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
import io.xpipe.core.util.FailableRunnable;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
@ -38,23 +34,25 @@ import java.nio.file.Path;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore> {
|
||||
public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<FileSystemStore> {
|
||||
|
||||
private final Property<String> filter = new SimpleStringProperty();
|
||||
private final BrowserFileListModel fileList;
|
||||
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
||||
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
||||
private final BrowserFileSystemHistory history = new BrowserFileSystemHistory();
|
||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>();
|
||||
private final ObservableList<UUID> terminalRequests = FXCollections.observableArrayList();
|
||||
private FileSystem fileSystem;
|
||||
private OpenFileSystemSavedState savedState;
|
||||
private OpenFileSystemCache cache;
|
||||
private BrowserFileSystemSavedState savedState;
|
||||
private BrowserFileSystemCache cache;
|
||||
|
||||
public OpenFileSystemModel(
|
||||
public BrowserFileSystemTabModel(
|
||||
BrowserAbstractSessionModel<?> model,
|
||||
DataStoreEntryRef<? extends FileSystemStore> entry,
|
||||
SelectionMode selectionMode) {
|
||||
|
@ -69,7 +67,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
|
||||
@Override
|
||||
public Comp<?> comp() {
|
||||
return new OpenFileSystemComp(this, true);
|
||||
return new BrowserFileSystemTabComp(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,12 +97,12 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
}
|
||||
this.fileSystem = fs;
|
||||
|
||||
this.cache = new OpenFileSystemCache(this);
|
||||
this.cache = new BrowserFileSystemCache(this);
|
||||
for (BrowserAction b : BrowserAction.ALL) {
|
||||
b.init(this);
|
||||
}
|
||||
});
|
||||
this.savedState = OpenFileSystemSavedState.loadForStore(this);
|
||||
this.savedState = BrowserFileSystemSavedState.loadForStore(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -119,8 +117,8 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
&& savedState != null
|
||||
&& current != null) {
|
||||
savedState.cd(current.getPath(), false);
|
||||
BrowserSavedStateImpl.get()
|
||||
.add(new BrowserSavedState.Entry(getEntry().get().getUuid(), current.getPath()));
|
||||
BrowserHistorySavedStateImpl.get()
|
||||
.add(new BrowserHistorySavedState.Entry(getEntry().get().getUuid(), current.getPath()));
|
||||
}
|
||||
try {
|
||||
fileSystem.close();
|
||||
|
@ -208,6 +206,37 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
cdSyncOrRetry(path, false).ifPresent(s -> cdSyncOrRetry(s, false));
|
||||
}
|
||||
|
||||
private boolean shouldLaunchSplitTerminal() {
|
||||
if (!AppPrefs.get().enableTerminalDocking().get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OsType.getLocal() != OsType.WINDOWS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AppMainWindow.getInstance().getStage().getWidth() <= 1280) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var term = AppPrefs.get().terminalType().getValue();
|
||||
if (term == null || term.getOpenFormat() == TerminalOpenFormat.TABBED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(browserModel instanceof BrowserFullSessionModel f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the right side is already occupied
|
||||
var existingSplit = f.getEffectiveRightTab().getValue();
|
||||
if (existingSplit != null && !(existingSplit instanceof BrowserTerminalDockTabModel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Optional<String> cdSyncOrRetry(String path, boolean customInput) {
|
||||
if (Objects.equals(path, currentPath.get())) {
|
||||
return Optional.empty();
|
||||
|
@ -226,7 +255,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
}
|
||||
|
||||
// Fix common issues with paths
|
||||
var adjustedPath = FileSystemHelper.adjustPath(this, path);
|
||||
var adjustedPath = BrowserFileSystemHelper.adjustPath(this, path);
|
||||
if (!Objects.equals(path, adjustedPath)) {
|
||||
return Optional.of(adjustedPath);
|
||||
}
|
||||
|
@ -234,7 +263,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
// Evaluate optional expressions
|
||||
String evaluatedPath;
|
||||
try {
|
||||
evaluatedPath = FileSystemHelper.evaluatePath(this, adjustedPath);
|
||||
evaluatedPath = BrowserFileSystemHelper.evaluatePath(this, adjustedPath);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
return Optional.ofNullable(currentPath.get());
|
||||
|
@ -252,21 +281,14 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath
|
||||
.toLowerCase()
|
||||
.startsWith(dialect.getExecutableName().toLowerCase()))) {
|
||||
TerminalLauncher.open(
|
||||
entry.getEntry(),
|
||||
name,
|
||||
directory,
|
||||
fileSystem
|
||||
.getShell()
|
||||
.get()
|
||||
.singularSubShell(
|
||||
ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false)));
|
||||
var cc = fileSystem
|
||||
.getShell()
|
||||
.get()
|
||||
.singularSubShell(ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false));
|
||||
openTerminalAsync(name, directory, cc, true);
|
||||
} else {
|
||||
TerminalLauncher.open(
|
||||
entry.getEntry(),
|
||||
name,
|
||||
directory,
|
||||
fileSystem.getShell().get().command(adjustedPath));
|
||||
var cc = fileSystem.getShell().get().command(adjustedPath);
|
||||
openTerminalAsync(name, directory, cc, true);
|
||||
}
|
||||
});
|
||||
return Optional.ofNullable(currentPath.get());
|
||||
|
@ -275,7 +297,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
// Evaluate optional links
|
||||
String resolvedPath;
|
||||
try {
|
||||
resolvedPath = FileSystemHelper.resolveDirectoryPath(this, evaluatedPath, customInput);
|
||||
resolvedPath = BrowserFileSystemHelper.resolveDirectoryPath(this, evaluatedPath, customInput);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
return Optional.ofNullable(currentPath.get());
|
||||
|
@ -286,7 +308,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
}
|
||||
|
||||
try {
|
||||
FileSystemHelper.validateDirectoryPath(this, resolvedPath, customInput);
|
||||
BrowserFileSystemHelper.validateDirectoryPath(this, resolvedPath, customInput);
|
||||
cdSyncWithoutCheck(path);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
|
@ -521,7 +543,8 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
history.updateCurrent(null);
|
||||
}
|
||||
|
||||
public void openTerminalAsync(String directory) {
|
||||
public void openTerminalAsync(
|
||||
String name, String directory, ProcessControl processControl, boolean dockIfPossible) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (fileSystem == null) {
|
||||
return;
|
||||
|
@ -529,10 +552,16 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
if (fileSystem.getShell().isPresent()) {
|
||||
var connection = fileSystem.getShell().get();
|
||||
var name = (directory != null ? directory + " - " : "")
|
||||
+ entry.get().getName();
|
||||
TerminalLauncher.open(entry.getEntry(), name, directory, connection);
|
||||
var dock = shouldLaunchSplitTerminal() && dockIfPossible;
|
||||
var uuid = UUID.randomUUID();
|
||||
terminalRequests.add(uuid);
|
||||
if (dock
|
||||
&& browserModel instanceof BrowserFullSessionModel fullSessionModel
|
||||
&& !(fullSessionModel.getSplits().get(this) instanceof BrowserTerminalDockTabModel)) {
|
||||
fullSessionModel.splitTab(
|
||||
this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests));
|
||||
}
|
||||
TerminalLauncher.open(entry.getEntry(), name, directory, processControl, uuid, !dock);
|
||||
|
||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||
startIfNeeded();
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserTransferProgress;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.store.*;
|
||||
|
||||
|
@ -50,7 +49,7 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
|
||||
try {
|
||||
return LocalFileSystem.getLocalFileEntry(path);
|
||||
return BrowserLocalFileSystem.getLocalFileEntry(path);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.scene.control.Label;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
|
@ -9,7 +9,7 @@ import lombok.extern.jackson.Jacksonized;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BrowserSavedState {
|
||||
public interface BrowserHistorySavedState {
|
||||
|
||||
void add(Entry entry);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
@ -19,36 +19,36 @@ import lombok.Value;
|
|||
import java.util.List;
|
||||
|
||||
@Value
|
||||
@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class)
|
||||
public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||
@JsonDeserialize(using = BrowserHistorySavedStateImpl.Deserializer.class)
|
||||
public class BrowserHistorySavedStateImpl implements BrowserHistorySavedState {
|
||||
|
||||
@JsonSerialize(as = List.class)
|
||||
ObservableList<Entry> lastSystems;
|
||||
|
||||
public BrowserSavedStateImpl(List<Entry> lastSystems) {
|
||||
public BrowserHistorySavedStateImpl(List<Entry> lastSystems) {
|
||||
this.lastSystems = FXCollections.observableArrayList(lastSystems);
|
||||
}
|
||||
|
||||
private static BrowserSavedStateImpl INSTANCE;
|
||||
private static BrowserHistorySavedStateImpl INSTANCE;
|
||||
|
||||
public static BrowserSavedState get() {
|
||||
public static BrowserHistorySavedState get() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = load();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static BrowserSavedStateImpl load() {
|
||||
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
||||
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
||||
private static BrowserHistorySavedStateImpl load() {
|
||||
return AppCache.getNonNull("browser-state", BrowserHistorySavedStateImpl.class, () -> {
|
||||
return new BrowserHistorySavedStateImpl(FXCollections.observableArrayList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(BrowserSavedState.Entry entry) {
|
||||
public synchronized void add(BrowserHistorySavedState.Entry entry) {
|
||||
lastSystems.removeIf(s -> s.getUuid().equals(entry.getUuid()));
|
||||
lastSystems.addFirst(entry);
|
||||
if (lastSystems.size() > 10) {
|
||||
if (lastSystems.size() > 15) {
|
||||
lastSystems.removeLast();
|
||||
}
|
||||
}
|
||||
|
@ -63,15 +63,15 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
|||
return lastSystems;
|
||||
}
|
||||
|
||||
public static class Deserializer extends StdDeserializer<BrowserSavedStateImpl> {
|
||||
public static class Deserializer extends StdDeserializer<BrowserHistorySavedStateImpl> {
|
||||
|
||||
protected Deserializer() {
|
||||
super(BrowserSavedStateImpl.class);
|
||||
super(BrowserHistorySavedStateImpl.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
public BrowserHistorySavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
||||
JavaType javaType =
|
||||
JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(List.class, Entry.class);
|
||||
|
@ -79,7 +79,7 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
|||
if (ls == null) {
|
||||
ls = List.of();
|
||||
}
|
||||
return new BrowserSavedStateImpl(ls);
|
||||
return new BrowserHistorySavedStateImpl(ls);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.PrettySvgComp;
|
||||
import io.xpipe.app.comp.base.TileButtonComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -35,17 +35,17 @@ import atlantafx.base.theme.Styles;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public class BrowserWelcomeComp extends SimpleComp {
|
||||
public class BrowserHistoryTabComp extends SimpleComp {
|
||||
|
||||
private final BrowserSessionModel model;
|
||||
private final BrowserFullSessionModel model;
|
||||
|
||||
public BrowserWelcomeComp(BrowserSessionModel model) {
|
||||
public BrowserHistoryTabComp(BrowserFullSessionModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var state = BrowserSavedStateImpl.get();
|
||||
var state = BrowserHistorySavedStateImpl.get();
|
||||
|
||||
var welcome = new BrowserGreetingComp().createSimple();
|
||||
|
||||
|
@ -124,7 +124,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
|
||||
var layout = new VBox();
|
||||
layout.getStyleClass().add("welcome");
|
||||
layout.setPadding(new Insets(60, 40, 40, 50));
|
||||
layout.setPadding(new Insets(25, 40, 40, 40));
|
||||
layout.setSpacing(18);
|
||||
layout.getChildren().add(hbox);
|
||||
layout.getChildren().add(Comp.separator().hide(empty).createRegion());
|
||||
|
@ -140,13 +140,14 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
.hide(empty)
|
||||
.accessibleTextKey("restoreAllSessions");
|
||||
layout.getChildren().add(tile.createRegion());
|
||||
AppFont.medium(layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Comp<?> entryButton(BrowserSavedState.Entry e, BooleanProperty disable) {
|
||||
private Comp<?> entryButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) {
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
var graphic = entry.get().getEffectiveIconFile();
|
||||
var view = PrettyImageHelper.ofFixedSize(graphic, 30, 24);
|
||||
var view = PrettyImageHelper.ofFixedSize(graphic, 22, 16);
|
||||
return new ButtonComp(
|
||||
new SimpleStringProperty(DataStorage.get().getStoreEntryDisplayName(entry.get())),
|
||||
view.createRegion(),
|
||||
|
@ -158,7 +159,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
}
|
||||
});
|
||||
})
|
||||
.minWidth(250)
|
||||
.minWidth(300)
|
||||
.accessibleText(DataStorage.get().getStoreEntryDisplayName(entry.get()))
|
||||
.disable(disable)
|
||||
.styleClass("entry-button")
|
||||
|
@ -166,7 +167,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
}
|
||||
|
||||
private Comp<?> dirButton(BrowserSavedState.Entry e, BooleanProperty disable) {
|
||||
private Comp<?> dirButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) {
|
||||
return new ButtonComp(new SimpleStringProperty(e.getPath()), null, () -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
model.restoreStateAsync(e, disable);
|
||||
|
@ -177,7 +178,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
.styleClass("directory-button")
|
||||
.apply(struc -> struc.get().setMaxWidth(2000))
|
||||
.styleClass(Styles.RIGHT_PILL)
|
||||
.grow(true, false)
|
||||
.hgrow()
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.browser.BrowserSessionTab;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
|
||||
public final class BrowserHistoryTabModel extends BrowserSessionTab {
|
||||
|
||||
public BrowserHistoryTabModel(BrowserAbstractSessionModel<?> browserModel) {
|
||||
super(browserModel, " " + AppI18n.get("history") + " ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comp<?> comp() {
|
||||
return new BrowserHistoryTabComp((BrowserFullSessionModel) browserModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImmediatelyClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataColor getColor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseable() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import io.xpipe.core.store.FileSystem;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LocalFileSystem {
|
||||
public class BrowserLocalFileSystem {
|
||||
|
||||
private static FileSystem localFileSystem;
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserContextMenu;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.browser.icon.BrowserIconManager;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
|
@ -33,58 +31,16 @@ import javafx.scene.shape.Rectangle;
|
|||
import atlantafx.base.theme.Styles;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
|
||||
public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
||||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
var path = new SimpleStringProperty(model.getCurrentPath().get());
|
||||
model.getCurrentPath().subscribe((newValue) -> {
|
||||
path.set(newValue);
|
||||
});
|
||||
path.addListener((observable, oldValue, newValue) -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||
var changed = model.cdSyncOrRetry(newValue, true);
|
||||
changed.ifPresent(s -> Platform.runLater(() -> path.set(s)));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var pathBar = new TextFieldComp(path, true)
|
||||
.styleClass(Styles.CENTER_PILL)
|
||||
.styleClass("path-text")
|
||||
.apply(struc -> {
|
||||
struc.get().focusedProperty().subscribe(val -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
INVISIBLE,
|
||||
!val && !model.getInOverview().get());
|
||||
|
||||
if (val) {
|
||||
Platform.runLater(() -> {
|
||||
struc.get().end();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
model.getInOverview().subscribe(val -> {
|
||||
// Pseudo classes do not apply if set instantly before shown
|
||||
// If we start a new tab with a directory set, we have to set the pseudo class one pulse later
|
||||
Platform.runLater(() -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
INVISIBLE, !val && !struc.get().isFocused());
|
||||
});
|
||||
});
|
||||
|
||||
struc.get().setPromptText("Overview of " + model.getName());
|
||||
})
|
||||
.accessibleText("Current path");
|
||||
var pathBar = createPathBar();
|
||||
|
||||
var graphic = Bindings.createStringBinding(
|
||||
() -> {
|
||||
return model.getCurrentDirectory() != null
|
||||
? FileIconManager.getFileIcon(model.getCurrentDirectory())
|
||||
? BrowserIconManager.getFileIcon(model.getCurrentDirectory())
|
||||
: null;
|
||||
},
|
||||
model.getCurrentPath());
|
||||
|
@ -154,6 +110,51 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
|
|||
return new Structure(topBox, pathRegion, historyButton);
|
||||
}
|
||||
|
||||
private Comp<CompStructure<TextField>> createPathBar() {
|
||||
var path = new SimpleStringProperty(model.getCurrentPath().get());
|
||||
model.getCurrentPath().subscribe((newValue) -> {
|
||||
path.set(newValue);
|
||||
});
|
||||
path.addListener((observable, oldValue, newValue) -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||
var changed = model.cdSyncOrRetry(newValue, true);
|
||||
changed.ifPresent(s -> Platform.runLater(() -> path.set(s)));
|
||||
});
|
||||
});
|
||||
});
|
||||
var pathBar =
|
||||
new TextFieldComp(path, true).styleClass(Styles.CENTER_PILL).styleClass("path-text");
|
||||
pathBar.apply(struc -> {
|
||||
struc.get().focusedProperty().subscribe(val -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
INVISIBLE,
|
||||
!val && !model.getInOverview().get());
|
||||
|
||||
if (val) {
|
||||
Platform.runLater(() -> {
|
||||
struc.get().end();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
model.getInOverview().subscribe(val -> {
|
||||
// Pseudo classes do not apply if set instantly before shown
|
||||
// If we start a new tab with a directory set, we have to set the pseudo class one pulse later
|
||||
Platform.runLater(() -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
INVISIBLE, !val && !struc.get().isFocused());
|
||||
});
|
||||
});
|
||||
|
||||
struc.get().setPromptText("Overview of " + model.getName());
|
||||
})
|
||||
.accessibleText("Current path");
|
||||
return pathBar;
|
||||
}
|
||||
|
||||
public record Structure(HBox box, TextField textField, Button historyButton) implements CompStructure<HBox> {
|
||||
|
||||
@Override
|
||||
|
@ -164,9 +165,9 @@ public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
|
|||
|
||||
private static final PseudoClass INVISIBLE = PseudoClass.getPseudoClass("invisible");
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
|
||||
public BrowserNavBar(OpenFileSystemModel model) {
|
||||
public BrowserNavBarComp(BrowserFileSystemTabModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserFileOverviewComp;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.SimpleTitledPaneComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
@ -24,9 +23,9 @@ import java.util.List;
|
|||
|
||||
public class BrowserOverviewComp extends SimpleComp {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
|
||||
public BrowserOverviewComp(OpenFileSystemModel model) {
|
||||
public BrowserOverviewComp(BrowserFileSystemTabModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
|
@ -77,6 +76,8 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
||||
|
||||
var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview");
|
||||
return vbox.createRegion();
|
||||
var r = vbox.createRegion();
|
||||
AppFont.medium(r);
|
||||
return r;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -12,9 +11,9 @@ import java.util.function.Supplier;
|
|||
public class BrowserQuickAccessButtonComp extends SimpleComp {
|
||||
|
||||
private final Supplier<BrowserEntry> base;
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
|
||||
public BrowserQuickAccessButtonComp(Supplier<BrowserEntry> base, OpenFileSystemModel model) {
|
||||
public BrowserQuickAccessButtonComp(Supplier<BrowserEntry> base, BrowserFileSystemTabModel model) {
|
||||
this.base = base;
|
||||
this.model = model;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.browser.icon.BrowserIconManager;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.util.BooleanAnimationTimer;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -32,13 +31,13 @@ import java.util.stream.Collectors;
|
|||
public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||
|
||||
private final Supplier<BrowserEntry> base;
|
||||
private final OpenFileSystemModel model;
|
||||
private final BrowserFileSystemTabModel model;
|
||||
private ContextMenu shownBrowserActionsMenu;
|
||||
private boolean expandBrowserActionMenuKey;
|
||||
private boolean keyBasedNavigation;
|
||||
private boolean closeBrowserActionMenuKey;
|
||||
|
||||
public BrowserQuickAccessContextMenu(Supplier<BrowserEntry> base, OpenFileSystemModel model) {
|
||||
public BrowserQuickAccessContextMenu(Supplier<BrowserEntry> base, BrowserFileSystemTabModel model) {
|
||||
this.base = base;
|
||||
this.model = model;
|
||||
|
||||
|
@ -142,7 +141,8 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
|||
this.menu = new Menu(
|
||||
// Use original name, not the link target
|
||||
browserEntry.getRawFileEntry().getName(),
|
||||
PrettyImageHelper.ofFixedSize(FileIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24)
|
||||
PrettyImageHelper.ofFixedSize(
|
||||
BrowserIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24)
|
||||
.createRegion());
|
||||
createMenu();
|
||||
addInputListeners();
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserContextMenu;
|
||||
import io.xpipe.app.browser.file.BrowserFileListCompEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -29,7 +26,7 @@ import java.util.List;
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BrowserStatusBarComp extends SimpleComp {
|
||||
|
||||
OpenFileSystemModel model;
|
||||
BrowserFileSystemTabModel model;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
|
@ -0,0 +1,164 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.browser.BrowserSessionTab;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.terminal.TerminalDockComp;
|
||||
import io.xpipe.app.terminal.TerminalDockModel;
|
||||
import io.xpipe.app.terminal.TerminalView;
|
||||
|
||||
import io.xpipe.app.terminal.WindowsTerminalType;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||
|
||||
private final BrowserSessionTab origin;
|
||||
private final ObservableList<UUID> terminalRequests;
|
||||
private final TerminalDockModel dockModel = new TerminalDockModel();
|
||||
private TerminalView.Listener listener;
|
||||
private ObservableBooleanValue viewActive;
|
||||
|
||||
public BrowserTerminalDockTabModel(
|
||||
BrowserAbstractSessionModel<?> browserModel,
|
||||
BrowserSessionTab origin,
|
||||
ObservableList<UUID> terminalRequests) {
|
||||
super(browserModel, AppI18n.get("terminal"));
|
||||
this.origin = origin;
|
||||
this.terminalRequests = terminalRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comp<?> comp() {
|
||||
return new TerminalDockComp(dockModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImmediatelyClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
var hasOpened = new AtomicBoolean();
|
||||
listener = new TerminalView.Listener() {
|
||||
@Override
|
||||
public void onSessionOpened(TerminalView.ShellSession session) {
|
||||
if (!terminalRequests.contains(session.getRequest())) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasOpened.set(true);
|
||||
var sessions = TerminalView.get().getSessions();
|
||||
var tv = sessions.stream()
|
||||
.filter(s -> terminalRequests.contains(s.getRequest())
|
||||
&& s.getTerminal().isRunning())
|
||||
.map(s -> s.getTerminal().controllable())
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
for (int i = 0; i < tv.size() - 1; i++) {
|
||||
dockModel.closeTerminal(tv.get(i));
|
||||
}
|
||||
|
||||
// Closing and opening windows at the same time might be problematic for some bad implementations
|
||||
if (tv.size() > 1) {
|
||||
ThreadHelper.sleep(250);
|
||||
}
|
||||
|
||||
var toTrack = tv.getLast();
|
||||
dockModel.trackTerminal(toTrack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionClosed(TerminalView.ShellSession session) {
|
||||
if (!terminalRequests.contains(session.getRequest())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ugly fix for Windows Terminal instances not closing properly if multiple windows exist
|
||||
if (AppPrefs.get().terminalType().getValue() instanceof WindowsTerminalType) {
|
||||
var sessions = TerminalView.get().getSessions();
|
||||
var others = sessions.stream().filter(shellSession -> shellSession.getTerminal().equals(session.getTerminal())).count();
|
||||
if (others == 0) {
|
||||
session.getTerminal().controllable().ifPresent(controllableTerminalSession -> {
|
||||
controllableTerminalSession.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminalClosed(TerminalView.TerminalSession instance) {
|
||||
refreshShowingState();
|
||||
}
|
||||
};
|
||||
TerminalView.get().addListener(listener);
|
||||
|
||||
// If the terminal launch fails
|
||||
ThreadHelper.runAsync(() -> {
|
||||
ThreadHelper.sleep(5000);
|
||||
if (!hasOpened.get()) {
|
||||
refreshShowingState();
|
||||
}
|
||||
});
|
||||
|
||||
viewActive = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return this.browserModel.getSelectedEntry().getValue() == origin
|
||||
&& AppLayoutModel.get()
|
||||
.getEntries()
|
||||
.indexOf(AppLayoutModel.get()
|
||||
.getSelected()
|
||||
.getValue())
|
||||
== 1;
|
||||
},
|
||||
this.browserModel.getSelectedEntry(),
|
||||
AppLayoutModel.get().getSelected());
|
||||
viewActive.subscribe(aBoolean -> {
|
||||
Platform.runLater(() -> {
|
||||
dockModel.toggleView(aBoolean);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshShowingState() {
|
||||
var sessions = TerminalView.get().getSessions();
|
||||
var remaining = sessions.stream()
|
||||
.filter(s -> terminalRequests.contains(s.getRequest())
|
||||
&& s.getTerminal().isRunning())
|
||||
.toList();
|
||||
if (remaining.isEmpty()) {
|
||||
((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (listener != null) {
|
||||
TerminalView.get().removeListener(listener);
|
||||
}
|
||||
dockModel.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataColor getColor() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
||||
import io.xpipe.app.fxcomps.impl.*;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -17,6 +15,7 @@ import javafx.css.PseudoClass;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -43,13 +42,16 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
||||
.apply(struc -> struc.get().setWrapText(true))
|
||||
.visible(model.getEmpty());
|
||||
var backgroundStack =
|
||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
||||
var backgroundStack = new StackComp(List.of(background))
|
||||
.grow(true, true)
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray")
|
||||
.styleClass("download-background");
|
||||
|
||||
var binding = new DerivedObservableList<>(model.getItems(), true)
|
||||
.mapped(item -> item.getBrowserEntry())
|
||||
.getList();
|
||||
var list = new BrowserSelectionListComp(binding, entry -> {
|
||||
var list = new BrowserFileSelectionListComp(binding, entry -> {
|
||||
var sourceItem = model.getCurrentItems().stream()
|
||||
.filter(item -> item.getBrowserEntry() == entry)
|
||||
.findAny();
|
||||
|
@ -102,84 +104,84 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
.padding(new Insets(10, 10, 5, 10))
|
||||
.apply(struc -> struc.get().setMinHeight(200))
|
||||
.apply(struc -> struc.get().setMaxHeight(200));
|
||||
var stack = new StackComp(List.of(backgroundStack, listBox))
|
||||
.apply(DragOverPseudoClassAugment.create())
|
||||
.apply(struc -> {
|
||||
struc.get().setOnDragOver(event -> {
|
||||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
struc.get().setOnDragDropped(event -> {
|
||||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null) {
|
||||
var drag = BrowserClipboard.retrieveDrag(event.getDragboard());
|
||||
if (drag == null) {
|
||||
return;
|
||||
var stack = new StackComp(List.of(backgroundStack, listBox)).apply(struc -> {
|
||||
struc.get().addEventFilter(DragEvent.DRAG_ENTERED, event -> {
|
||||
struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("drag-over"), true);
|
||||
});
|
||||
struc.get().addEventFilter(DragEvent.DRAG_EXITED, event -> struc.get()
|
||||
.pseudoClassStateChanged(PseudoClass.getPseudoClass("drag-over"), false));
|
||||
struc.get().setOnDragOver(event -> {
|
||||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
struc.get().setOnDragDropped(event -> {
|
||||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null) {
|
||||
var drag = BrowserClipboard.retrieveDrag(event.getDragboard());
|
||||
if (drag == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(model.getBrowserSessionModel().getSelectedEntry().getValue()
|
||||
instanceof BrowserFileSystemTabModel fileSystemModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var files = drag.getEntries();
|
||||
model.drop(fileSystemModel, files);
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
struc.get().setOnDragDetected(event -> {
|
||||
var items = model.getCurrentItems();
|
||||
var selected =
|
||||
items.stream().map(item -> item.getBrowserEntry()).toList();
|
||||
var files = items.stream()
|
||||
.filter(item -> item.downloadFinished().get())
|
||||
.map(item -> {
|
||||
try {
|
||||
var file = item.getLocalFile();
|
||||
if (!Files.exists(file)) {
|
||||
return Optional.<File>empty();
|
||||
}
|
||||
|
||||
return Optional.of(file.toRealPath().toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(model.getBrowserSessionModel()
|
||||
.getSelectedEntry()
|
||||
.getValue()
|
||||
instanceof OpenFileSystemModel fileSystemModel)) {
|
||||
return;
|
||||
}
|
||||
var cc = new ClipboardContent();
|
||||
cc.putFiles(files);
|
||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
||||
db.setContent(cc);
|
||||
|
||||
var files = drag.getEntries();
|
||||
model.drop(fileSystemModel, files);
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
struc.get().setOnDragDetected(event -> {
|
||||
var items = model.getCurrentItems();
|
||||
var selected = items.stream()
|
||||
.map(item -> item.getBrowserEntry())
|
||||
.toList();
|
||||
var files = items.stream()
|
||||
.filter(item -> item.downloadFinished().get())
|
||||
.map(item -> {
|
||||
try {
|
||||
var file = item.getLocalFile();
|
||||
if (!Files.exists(file)) {
|
||||
return Optional.<File>empty();
|
||||
}
|
||||
Image image = BrowserFileSelectionListComp.snapshot(FXCollections.observableList(selected));
|
||||
db.setDragView(image, -20, 15);
|
||||
|
||||
return Optional.of(file.toRealPath().toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
event.setDragDetect(true);
|
||||
event.consume();
|
||||
});
|
||||
struc.get().setOnDragDone(event -> {
|
||||
if (!event.isAccepted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cc = new ClipboardContent();
|
||||
cc.putFiles(files);
|
||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
||||
db.setContent(cc);
|
||||
|
||||
Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected));
|
||||
db.setDragView(image, -20, 15);
|
||||
|
||||
event.setDragDetect(true);
|
||||
event.consume();
|
||||
});
|
||||
struc.get().setOnDragDone(event -> {
|
||||
if (!event.isAccepted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The files might not have been transferred yet
|
||||
// We can't listen to this, so just don't delete them
|
||||
model.clear(false);
|
||||
event.consume();
|
||||
});
|
||||
});
|
||||
// The files might not have been transferred yet
|
||||
// We can't listen to this, so just don't delete them
|
||||
model.clear(false);
|
||||
event.consume();
|
||||
});
|
||||
});
|
||||
|
||||
stack.apply(struc -> {
|
||||
model.getBrowserSessionModel().getDraggingFiles().addListener((observable, oldValue, newValue) -> {
|
|
@ -1,12 +1,8 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||
import io.xpipe.app.browser.file.BrowserFileTransferOperation;
|
||||
import io.xpipe.app.browser.file.LocalFileSystem;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -23,6 +19,7 @@ import org.apache.commons.io.FileUtils;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
|
@ -34,11 +31,11 @@ public class BrowserTransferModel {
|
|||
|
||||
private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("download");
|
||||
|
||||
BrowserSessionModel browserSessionModel;
|
||||
BrowserFullSessionModel browserSessionModel;
|
||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||
ObservableBooleanValue empty = Bindings.createBooleanBinding(() -> items.isEmpty(), items);
|
||||
|
||||
public BrowserTransferModel(BrowserSessionModel browserSessionModel) {
|
||||
public BrowserTransferModel(BrowserFullSessionModel browserSessionModel) {
|
||||
this.browserSessionModel = browserSessionModel;
|
||||
var thread = ThreadHelper.createPlatformThread("file downloader", true, () -> {
|
||||
while (true) {
|
||||
|
@ -96,7 +93,7 @@ public class BrowserTransferModel {
|
|||
}
|
||||
}
|
||||
|
||||
public void drop(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
public void drop(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
synchronized (items) {
|
||||
entries.forEach(entry -> {
|
||||
var name = entry.getFileName();
|
||||
|
@ -130,7 +127,7 @@ public class BrowserTransferModel {
|
|||
|
||||
try {
|
||||
var op = new BrowserFileTransferOperation(
|
||||
LocalFileSystem.getLocalFileEntry(TEMP),
|
||||
BrowserLocalFileSystem.getLocalFileEntry(TEMP),
|
||||
List.of(item.getBrowserEntry().getRawFileEntry()),
|
||||
BrowserFileTransferMode.COPY,
|
||||
false,
|
||||
|
@ -167,7 +164,7 @@ public class BrowserTransferModel {
|
|||
}
|
||||
|
||||
var files = toMove.stream().map(item -> item.getLocalFile()).toList();
|
||||
var downloads = DesktopHelper.getDownloadsDirectory();
|
||||
var downloads = getDownloadsTargetDirectory();
|
||||
Files.createDirectories(downloads);
|
||||
for (Path file : files) {
|
||||
if (!Files.exists(file)) {
|
||||
|
@ -188,15 +185,33 @@ public class BrowserTransferModel {
|
|||
DesktopHelper.browseFileInDirectory(downloads.resolve(files.getFirst().getFileName()));
|
||||
}
|
||||
|
||||
private Path getDownloadsTargetDirectory() throws Exception {
|
||||
var def = DesktopHelper.getDownloadsDirectory();
|
||||
var custom = AppPrefs.get().downloadsDirectory().getValue();
|
||||
if (custom == null || custom.isBlank()) {
|
||||
return def;
|
||||
}
|
||||
|
||||
try {
|
||||
var path = Path.of(custom);
|
||||
if (Files.isDirectory(path)) {
|
||||
return path;
|
||||
}
|
||||
} catch (InvalidPathException ignored) {
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class Item {
|
||||
OpenFileSystemModel openFileSystemModel;
|
||||
BrowserFileSystemTabModel openFileSystemModel;
|
||||
String name;
|
||||
BrowserEntry browserEntry;
|
||||
Path localFile;
|
||||
Property<BrowserTransferProgress> progress;
|
||||
|
||||
public Item(OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) {
|
||||
public Item(
|
||||
BrowserFileSystemTabModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) {
|
||||
this.openFileSystemModel = openFileSystemModel;
|
||||
this.name = name;
|
||||
this.browserEntry = browserEntry;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import lombok.Value;
|
||||
|
|
@ -63,7 +63,7 @@ public abstract class BrowserIconDirectoryType {
|
|||
var closedIcon = "browser/" + split[2].trim();
|
||||
var lightClosedIcon = split.length > 4 ? "browser/" + split[4].trim() : closedIcon;
|
||||
|
||||
ALL.add(new Simple(id, new IconVariant(lightClosedIcon, closedIcon), filter));
|
||||
ALL.add(new Simple(id, new BrowserIconVariant(lightClosedIcon, closedIcon), filter));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -84,10 +84,10 @@ public abstract class BrowserIconDirectoryType {
|
|||
@Getter
|
||||
private final String id;
|
||||
|
||||
private final IconVariant closed;
|
||||
private final BrowserIconVariant closed;
|
||||
private final Set<String> names;
|
||||
|
||||
public Simple(String id, IconVariant closed, Set<String> names) {
|
||||
public Simple(String id, BrowserIconVariant closed, Set<String> names) {
|
||||
this.id = id;
|
||||
this.closed = closed;
|
||||
this.names = names;
|
||||
|
|
|
@ -69,11 +69,11 @@ public abstract class BrowserIconFileType {
|
|||
public static class Simple extends BrowserIconFileType {
|
||||
|
||||
private final String id;
|
||||
private final IconVariant icon;
|
||||
private final BrowserIconVariant icon;
|
||||
private final Set<String> endings;
|
||||
|
||||
public Simple(String id, String lightIcon, String darkIcon, Set<String> endings) {
|
||||
this.icon = new IconVariant(lightIcon, darkIcon);
|
||||
this.icon = new BrowserIconVariant(lightIcon, darkIcon);
|
||||
this.id = id;
|
||||
this.endings = endings;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.app.browser.icon;
|
|||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
||||
public class FileIconManager {
|
||||
public class BrowserIconManager {
|
||||
|
||||
private static boolean loaded;
|
||||
|
|
@ -2,16 +2,16 @@ package io.xpipe.app.browser.icon;
|
|||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
public class IconVariant {
|
||||
public class BrowserIconVariant {
|
||||
|
||||
private final String lightIcon;
|
||||
private final String darkIcon;
|
||||
|
||||
public IconVariant(String icon) {
|
||||
public BrowserIconVariant(String icon) {
|
||||
this(icon, icon);
|
||||
}
|
||||
|
||||
public IconVariant(String lightIcon, String darkIcon) {
|
||||
public BrowserIconVariant(String lightIcon, String darkIcon) {
|
||||
this.lightIcon = lightIcon;
|
||||
this.darkIcon = darkIcon;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
public class BrowserIcons {
|
||||
|
@ -19,6 +19,6 @@ public class BrowserIcons {
|
|||
}
|
||||
|
||||
public static Comp<?> createIcon(FileEntry entry) {
|
||||
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry), 24);
|
||||
return PrettyImageHelper.ofFixedSizeSquare(BrowserIconManager.getFileIcon(entry), 24);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.browser.BrowserSavedState;
|
||||
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
||||
import io.xpipe.app.browser.BrowserTransferModel;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Getter
|
||||
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab<?>> {
|
||||
|
||||
public static final BrowserSessionModel DEFAULT = new BrowserSessionModel();
|
||||
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final Property<Boolean> draggingFiles = new SimpleBooleanProperty();
|
||||
|
||||
public void restoreState(BrowserSavedState state) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var l = new ArrayList<>(state.getEntries());
|
||||
l.forEach(e -> {
|
||||
restoreStateAsync(e, null);
|
||||
// Don't try to run everything in parallel as that can be taxing
|
||||
ThreadHelper.sleep(1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) {
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
storageEntry.ifPresent(entry -> {
|
||||
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
for (var o : new ArrayList<>(sessionEntries)) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (!o.canImmediatelyClose()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent blocking of shutdown
|
||||
closeAsync(o);
|
||||
}
|
||||
BrowserSavedStateImpl.get().save();
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
localTransfersStage.clear(true);
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
openFileSystemSync(store, path, externalBusy);
|
||||
});
|
||||
}
|
||||
|
||||
public OpenFileSystemModel openFileSystemSync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy)
|
||||
throws Exception {
|
||||
OpenFileSystemModel model;
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
try (var sessionBusy = new BooleanScope(busy).exclusive().start()) {
|
||||
model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL);
|
||||
model.init();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
sessionEntries.add(model);
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selectedEntry.setValue(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||
} else {
|
||||
model.initWithDefaultDirectory();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class BrowserSessionTab<T extends DataStore> {
|
||||
|
||||
protected final DataStoreEntryRef<? extends T> entry;
|
||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||
protected final String name;
|
||||
protected final String tooltip;
|
||||
|
||||
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
|
||||
this.browserModel = browserModel;
|
||||
this.entry = entry;
|
||||
this.name = DataStorage.get().getStoreEntryDisplayName(entry.get());
|
||||
this.tooltip = DataStorage.get().getStorePath(entry.getEntry()).toString();
|
||||
}
|
||||
|
||||
public abstract Comp<?> comp();
|
||||
|
||||
public abstract boolean canImmediatelyClose();
|
||||
|
||||
public abstract void init() throws Exception;
|
||||
|
||||
public abstract void close();
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.app.fxcomps;
|
||||
package io.xpipe.app.comp;
|
||||
|
||||
import io.xpipe.app.comp.augment.Augment;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.augment.Augment;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -97,8 +97,8 @@ public abstract class Comp<S extends CompStructure<?>> {
|
|||
return apply(struc -> struc.get().setMinWidth(width));
|
||||
}
|
||||
|
||||
public Comp<S> minHeight(double width) {
|
||||
return apply(struc -> struc.get().setMinHeight(width));
|
||||
public Comp<S> minHeight(double height) {
|
||||
return apply(struc -> struc.get().setMinHeight(height));
|
||||
}
|
||||
|
||||
public Comp<S> maxWidth(int width) {
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.fxcomps;
|
||||
package io.xpipe.app.comp;
|
||||
|
||||
import javafx.scene.layout.Region;
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package io.xpipe.app.comp;
|
||||
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DeveloperTabComp extends SimpleComp {
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var button = new ButtonComp(AppI18n.observable("Throw exception"), null, () -> {
|
||||
throw new IllegalStateException();
|
||||
});
|
||||
|
||||
var button2 = new ButtonComp(AppI18n.observable("Throw exception with file"), null, () -> {
|
||||
try {
|
||||
throw new IllegalStateException();
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex)
|
||||
.attachment(Path.of("extensions.txt"))
|
||||
.build()
|
||||
.handle();
|
||||
}
|
||||
});
|
||||
|
||||
var button3 = new ButtonComp(AppI18n.observable("Exit"), null, () -> {
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
var button6 = new ButtonComp(AppI18n.observable("Restart"), null, () -> {
|
||||
OperationMode.restart();
|
||||
});
|
||||
|
||||
var button4 = new ButtonComp(AppI18n.observable("Throw terminal exception"), null, () -> {
|
||||
try {
|
||||
throw new IllegalStateException();
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
||||
}
|
||||
});
|
||||
|
||||
var button5 = new ButtonComp(AppI18n.observable("Operation mode null"), null, OperationMode::close);
|
||||
|
||||
return new HBox(
|
||||
button.createRegion(),
|
||||
button2.createRegion(),
|
||||
button3.createRegion(),
|
||||
button4.createRegion(),
|
||||
button5.createRegion(),
|
||||
button6.createRegion());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.fxcomps;
|
||||
package io.xpipe.app.comp;
|
||||
|
||||
import javafx.scene.layout.Region;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.fxcomps;
|
||||
package io.xpipe.app.comp;
|
||||
|
||||
import javafx.scene.layout.Region;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.fxcomps.augment;
|
||||
package io.xpipe.app.comp.augment;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Region;
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.augment;
|
||||
package io.xpipe.app.comp.augment;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Side;
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.augment;
|
||||
package io.xpipe.app.comp.augment;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.Node;
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package io.xpipe.app.comp;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.base.SideMenuBarComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
|
||||
|
@ -54,7 +52,7 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
|
|||
DataStorage.get().saveAsync();
|
||||
}
|
||||
|
||||
if (o != null && o.equals(model.getEntries().get(1))) {
|
||||
if (o != null && o.equals(model.getEntries().get(0))) {
|
||||
StoreViewState.get().updateDisplay();
|
||||
}
|
||||
});
|
|
@ -1,62 +0,0 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
public class BackgroundImageComp extends Comp<CompStructure<Pane>> {
|
||||
|
||||
private final Image image;
|
||||
|
||||
public BackgroundImageComp(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<Pane> createBase() {
|
||||
ImageView v = new ImageView(image);
|
||||
Pane pane = new Pane(v);
|
||||
v.fitWidthProperty().bind(pane.widthProperty());
|
||||
v.fitHeightProperty().bind(pane.heightProperty());
|
||||
if (image == null) {
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
|
||||
double imageAspect = image.getWidth() / image.getHeight();
|
||||
ChangeListener<? super Number> cl = (c, o, n) -> {
|
||||
double paneAspect = pane.getWidth() / pane.getHeight();
|
||||
|
||||
double relViewportWidth;
|
||||
double relViewportHeight;
|
||||
|
||||
// Pane width too big for image
|
||||
if (paneAspect > imageAspect) {
|
||||
relViewportWidth = 1;
|
||||
double newImageHeight = pane.getWidth() / imageAspect;
|
||||
relViewportHeight = Math.min(1, pane.getHeight() / newImageHeight);
|
||||
}
|
||||
|
||||
// Height too big
|
||||
else {
|
||||
relViewportHeight = 1;
|
||||
double newImageWidth = pane.getHeight() * imageAspect;
|
||||
relViewportWidth = Math.min(1, pane.getWidth() / newImageWidth);
|
||||
}
|
||||
|
||||
v.setViewport(new Rectangle2D(
|
||||
((1 - relViewportWidth) / 2.0) * image.getWidth(),
|
||||
((1 - relViewportHeight) / 2.0) * image.getHeight(),
|
||||
image.getWidth() * relViewportWidth,
|
||||
image.getHeight() * relViewportHeight));
|
||||
};
|
||||
pane.widthProperty().addListener(cl);
|
||||
pane.heightProperty().addListener(cl);
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
public class BigIconButton extends ButtonComp {
|
||||
|
||||
public BigIconButton(ObservableValue<String> name, Node graphic, Runnable listener) {
|
||||
super(name, graphic, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
var vbox = new VBox();
|
||||
vbox.getStyleClass().add("vbox");
|
||||
vbox.setAlignment(Pos.CENTER);
|
||||
|
||||
var icon = new StackPane(getGraphic());
|
||||
icon.setAlignment(Pos.CENTER);
|
||||
icon.getStyleClass().add("icon");
|
||||
vbox.getChildren().add(icon);
|
||||
|
||||
var label = new Label();
|
||||
label.textProperty().bind(getName());
|
||||
label.getStyleClass().add("name");
|
||||
vbox.getChildren().add(label);
|
||||
|
||||
var b = new Button(null);
|
||||
b.accessibleTextProperty().bind(getName());
|
||||
b.setGraphic(vbox);
|
||||
b.setOnAction(e -> getListener().run());
|
||||
b.getStyleClass().add("big-icon-button-comp");
|
||||
return Structure.builder()
|
||||
.stack(vbox)
|
||||
.graphic(getGraphic())
|
||||
.graphicPane(icon)
|
||||
.text(label)
|
||||
.button(b)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class Structure implements CompStructure<Button> {
|
||||
Button button;
|
||||
VBox stack;
|
||||
Node graphic;
|
||||
StackPane graphicPane;
|
||||
Label text;
|
||||
|
||||
@Override
|
||||
public Button get() {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.Translatable;
|
||||
|
||||
import javafx.beans.property.Property;
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.FXCollections;
|
|
@ -1,12 +1,11 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.browser.session.BrowserChooserComp;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.browser.BrowserFileChooserSessionComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.ContextualFileReference;
|
||||
|
@ -67,7 +66,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
|||
public CompStructure<HBox> createBase() {
|
||||
var path = previousFileReferences.isEmpty() ? createTextField() : createComboBox();
|
||||
var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> {
|
||||
BrowserChooserComp.openSingleFile(
|
||||
BrowserFileChooserSessionComp.openSingleFile(
|
||||
() -> fileSystem.getValue(),
|
||||
fileStore -> {
|
||||
if (fileStore == null) {
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppActionLinkDetector;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -58,6 +58,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
|||
|
||||
filter.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
|
||||
filter.clear();
|
||||
filter.getScene().getRoot().requestFocus();
|
||||
event.consume();
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.HBox;
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
|
@ -1,9 +1,7 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.fxcomps.impl.TextAreaComp;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
@ -68,7 +66,14 @@ public class IntegratedTextAreaComp extends Comp<IntegratedTextAreaComp.Structur
|
|||
return new TextAreaStructure(c, textArea.getTextArea());
|
||||
}
|
||||
},
|
||||
paths -> value.setValue(Files.readString(paths.getFirst())));
|
||||
paths -> {
|
||||
var first = paths.getFirst();
|
||||
if (Files.size(first) > 1_000_000) {
|
||||
return;
|
||||
}
|
||||
|
||||
value.setValue(Files.readString(first));
|
||||
});
|
||||
var struc = fileDrop.createStructure();
|
||||
return new Structure(struc.get(), struc.getCompStructure().getTextArea());
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -11,14 +11,14 @@ import lombok.Value;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||
public class LeftSplitPaneComp extends Comp<LeftSplitPaneComp.Structure> {
|
||||
|
||||
private final Comp<?> left;
|
||||
private final Comp<?> center;
|
||||
private Double initialWidth;
|
||||
private Consumer<Double> onDividerChange;
|
||||
|
||||
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||
public LeftSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||
this.left = left;
|
||||
this.center = center;
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
|||
return new Structure(sidebar, c, r, r.getDividers().getFirst());
|
||||
}
|
||||
|
||||
public SideSplitPaneComp withInitialWidth(double val) {
|
||||
public LeftSplitPaneComp withInitialWidth(double val) {
|
||||
this.initialWidth = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
||||
public LeftSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
||||
this.onDividerChange = onDividerChange;
|
||||
return this;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue