mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Rework pinning
This commit is contained in:
parent
6dabe53011
commit
54fce7248f
212 changed files with 2800 additions and 1980 deletions
|
@ -23,8 +23,8 @@ dependencies {
|
||||||
api project(':beacon')
|
api project(':beacon')
|
||||||
|
|
||||||
compileOnly 'org.hamcrest:hamcrest:3.0'
|
compileOnly 'org.hamcrest:hamcrest:3.0'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.0'
|
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.3'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.0'
|
compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.3'
|
||||||
|
|
||||||
api 'com.vladsch.flexmark:flexmark:0.64.8'
|
api 'com.vladsch.flexmark:flexmark:0.64.8'
|
||||||
api 'com.vladsch.flexmark:flexmark-util:0.64.8'
|
api 'com.vladsch.flexmark:flexmark-util:0.64.8'
|
||||||
|
@ -58,8 +58,8 @@ dependencies {
|
||||||
api 'org.apache.commons:commons-lang3:3.17.0'
|
api 'org.apache.commons:commons-lang3:3.17.0'
|
||||||
api 'io.sentry:sentry:7.14.0'
|
api 'io.sentry:sentry:7.14.0'
|
||||||
api 'commons-io:commons-io:2.16.1'
|
api 'commons-io:commons-io:2.16.1'
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.2"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.18.1"
|
||||||
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.17.2"
|
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-material2-pack', version: "12.2.0"
|
||||||
api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
|
||||||
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange {
|
||||||
throw new BeaconClientException("Not a file system connection");
|
throw new BeaconClientException("Not a file system connection");
|
||||||
}
|
}
|
||||||
BrowserSessionModel.DEFAULT.openFileSystemSync(
|
BrowserSessionModel.DEFAULT.openFileSystemSync(
|
||||||
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null);
|
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null, true);
|
||||||
AppLayoutModel.get().selectBrowser();
|
AppLayoutModel.get().selectBrowser();
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange {
|
||||||
.getStoreEntryIfPresent(msg.getConnection())
|
.getStoreEntryIfPresent(msg.getConnection())
|
||||||
.orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection()));
|
.orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection()));
|
||||||
if (e.getStore() instanceof FixedHierarchyStore) {
|
if (e.getStore() instanceof FixedHierarchyStore) {
|
||||||
DataStorage.get().refreshChildren(e, null, true);
|
DataStorage.get().refreshChildren(e, true);
|
||||||
} else {
|
} else {
|
||||||
e.validateOrThrowAndClose(null);
|
e.validateOrThrow();
|
||||||
}
|
}
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.app.beacon.impl;
|
package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.TerminalLauncher;
|
import io.xpipe.app.util.TerminalLauncher;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.api.ConnectionTerminalExchange;
|
import io.xpipe.beacon.api.ConnectionTerminalExchange;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
|
@ -18,9 +18,8 @@ public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange {
|
||||||
if (!(e.getStore() instanceof ShellStore shellStore)) {
|
if (!(e.getStore() instanceof ShellStore shellStore)) {
|
||||||
throw new BeaconClientException("Not a shell connection");
|
throw new BeaconClientException("Not a shell connection");
|
||||||
}
|
}
|
||||||
try (var sc = shellStore.control().start()) {
|
var sc = shellStore.getOrStartSession();
|
||||||
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc);
|
||||||
}
|
|
||||||
return Response.builder().build();
|
return Response.builder().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package io.xpipe.app.beacon.impl;
|
||||||
|
|
||||||
import io.xpipe.app.beacon.AppBeaconServer;
|
import io.xpipe.app.beacon.AppBeaconServer;
|
||||||
import io.xpipe.app.beacon.BeaconShellSession;
|
import io.xpipe.app.beacon.BeaconShellSession;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.api.ShellStartExchange;
|
import io.xpipe.beacon.api.ShellStartExchange;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
@ -25,7 +25,9 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
|
||||||
var existing = AppBeaconServer.get().getCache().getShellSessions().stream()
|
var existing = AppBeaconServer.get().getCache().getShellSessions().stream()
|
||||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
var control = (existing.isPresent() ? existing.get().getControl() : s.control());
|
var control = (existing.isPresent()
|
||||||
|
? existing.get().getControl()
|
||||||
|
: s.standaloneControl().start());
|
||||||
control.setNonInteractive();
|
control.setNonInteractive();
|
||||||
control.start();
|
control.start();
|
||||||
|
|
||||||
|
|
49
app/src/main/java/io/xpipe/app/browser/BrowserHomeModel.java
Normal file
49
app/src/main/java/io/xpipe/app/browser/BrowserHomeModel.java
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||||
|
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||||
|
import io.xpipe.app.browser.session.BrowserSessionTab;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
import io.xpipe.app.storage.DataColor;
|
||||||
|
import io.xpipe.core.store.*;
|
||||||
|
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
|
||||||
|
public final class BrowserHomeModel extends BrowserSessionTab {
|
||||||
|
|
||||||
|
public BrowserHomeModel(BrowserAbstractSessionModel<?> browserModel) {
|
||||||
|
super(browserModel, AppI18n.get("overview"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> comp() {
|
||||||
|
return new BrowserWelcomeComp((BrowserSessionModel) 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BrowserSavedStateImpl load() {
|
private static BrowserSavedStateImpl load() {
|
||||||
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
|
return AppCache.getNonNull("browser-state", BrowserSavedStateImpl.class, () -> {
|
||||||
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,11 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
|
||||||
.apply(struc -> struc.get().setWrapText(true))
|
.apply(struc -> struc.get().setWrapText(true))
|
||||||
.visible(model.getEmpty());
|
.visible(model.getEmpty());
|
||||||
var backgroundStack =
|
var backgroundStack = new StackComp(List.of(background))
|
||||||
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
|
.grow(true, true)
|
||||||
|
.styleClass("color-box")
|
||||||
|
.styleClass("gray")
|
||||||
|
.styleClass("download-background");
|
||||||
|
|
||||||
var binding = new DerivedObservableList<>(model.getItems(), true)
|
var binding = new DerivedObservableList<>(model.getItems(), true)
|
||||||
.mapped(item -> item.getBrowserEntry())
|
.mapped(item -> item.getBrowserEntry())
|
||||||
|
|
|
@ -75,9 +75,8 @@ public interface LeafAction extends BrowserAction {
|
||||||
var name = getName(model, selected);
|
var name = getName(model, selected);
|
||||||
var mi = new MenuItem();
|
var mi = new MenuItem();
|
||||||
mi.textProperty().bind(BindingsHelper.map(name, s -> {
|
mi.textProperty().bind(BindingsHelper.map(name, s -> {
|
||||||
if (getProFeatureId() != null
|
if (getProFeatureId() != null) {
|
||||||
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
return LicenseProvider.get().getFeature(getProFeatureId()).suffix(s);
|
||||||
return s + " (Pro)";
|
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -130,7 +130,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
table.setAccessibleText("Directory contents");
|
table.setAccessibleText("Directory contents");
|
||||||
table.setPlaceholder(new Region());
|
table.setPlaceholder(new Region());
|
||||||
table.getStyleClass().add(Styles.STRIPED);
|
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.getSortOrder().add(filenameCol);
|
||||||
table.setFocusTraversable(true);
|
table.setFocusTraversable(true);
|
||||||
table.setSortPolicy(param -> {
|
table.setSortPolicy(param -> {
|
||||||
|
@ -313,8 +313,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
.filter(browserAction -> browserAction.getShortcut().match(event))
|
.filter(browserAction -> browserAction.getShortcut().match(event))
|
||||||
.findAny();
|
.findAny();
|
||||||
action.ifPresent(browserAction -> {
|
action.ifPresent(browserAction -> {
|
||||||
|
// Prevent concurrent modification by creating copy on platform thread
|
||||||
|
var selectionCopy = new ArrayList<>(selected);
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
browserAction.execute(fileList.getFileSystemModel(), selected);
|
browserAction.execute(fileList.getFileSystemModel(), selectionCopy);
|
||||||
});
|
});
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,10 @@ import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||||
import io.xpipe.app.browser.file.BrowserFileTransferOperation;
|
import io.xpipe.app.browser.file.BrowserFileTransferOperation;
|
||||||
import io.xpipe.app.browser.file.FileSystemHelper;
|
import io.xpipe.app.browser.file.FileSystemHelper;
|
||||||
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionTab;
|
import io.xpipe.app.browser.session.BrowserStoreSessionTab;
|
||||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -41,7 +42,7 @@ import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore> {
|
public final class OpenFileSystemModel extends BrowserStoreSessionTab<FileSystemStore> {
|
||||||
|
|
||||||
private final Property<String> filter = new SimpleStringProperty();
|
private final Property<String> filter = new SimpleStringProperty();
|
||||||
private final BrowserFileListModel fileList;
|
private final BrowserFileListModel fileList;
|
||||||
|
|
|
@ -57,7 +57,8 @@ public class OpenFileSystemSavedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
||||||
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
var state = AppCache.getNonNull(
|
||||||
|
"fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
||||||
return new OpenFileSystemSavedState();
|
return new OpenFileSystemSavedState();
|
||||||
});
|
});
|
||||||
state.setModel(model);
|
state.setModel(model);
|
||||||
|
|
|
@ -13,13 +13,13 @@ import javafx.collections.ObservableList;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class BrowserAbstractSessionModel<T extends BrowserSessionTab<?>> {
|
public class BrowserAbstractSessionModel<T extends BrowserSessionTab> {
|
||||||
|
|
||||||
protected final ObservableList<T> sessionEntries = FXCollections.observableArrayList();
|
protected final ObservableList<T> sessionEntries = FXCollections.observableArrayList();
|
||||||
protected final Property<T> selectedEntry = new SimpleObjectProperty<>();
|
protected final Property<T> selectedEntry = new SimpleObjectProperty<>();
|
||||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public void closeAsync(BrowserSessionTab<?> e) {
|
public void closeAsync(BrowserSessionTab e) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
closeSync(e);
|
closeSync(e);
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,7 @@ public class BrowserAbstractSessionModel<T extends BrowserSessionTab<?>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeSync(BrowserSessionTab<?> e) {
|
public void closeSync(BrowserSessionTab e) {
|
||||||
e.close();
|
e.close();
|
||||||
synchronized (BrowserAbstractSessionModel.this) {
|
synchronized (BrowserAbstractSessionModel.this) {
|
||||||
this.sessionEntries.remove(e);
|
this.sessionEntries.remove(e);
|
||||||
|
|
|
@ -6,10 +6,11 @@ import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.comp.base.DialogComp;
|
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.store.StoreEntryWrapper;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
|
@ -19,7 +20,6 @@ import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.FileReference;
|
import io.xpipe.app.util.FileReference;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
|
@ -148,7 +148,7 @@ public class BrowserChooserComp extends DialogComp {
|
||||||
});
|
});
|
||||||
|
|
||||||
var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer)).styleClass("left");
|
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())
|
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||||
.styleClass("background")
|
.styleClass("background")
|
||||||
|
|
|
@ -4,23 +4,25 @@ import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||||
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
|
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
|
||||||
import io.xpipe.app.browser.BrowserTransferComp;
|
import io.xpipe.app.browser.BrowserTransferComp;
|
||||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.AnchorComp;
|
import io.xpipe.app.fxcomps.impl.AnchorComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
|
@ -67,7 +69,11 @@ public class BrowserSessionComp extends SimpleComp {
|
||||||
|
|
||||||
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
|
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
|
||||||
var bookmarksList = new BrowserBookmarkComp(
|
var bookmarksList = new BrowserBookmarkComp(
|
||||||
BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()),
|
BindingsHelper.map(
|
||||||
|
model.getSelectedEntry(),
|
||||||
|
v -> v instanceof BrowserStoreSessionTab<?> st
|
||||||
|
? st.getEntry().get()
|
||||||
|
: null),
|
||||||
applicable,
|
applicable,
|
||||||
action,
|
action,
|
||||||
bookmarkTopBar.getCategory(),
|
bookmarkTopBar.getCategory(),
|
||||||
|
@ -99,8 +105,10 @@ public class BrowserSessionComp extends SimpleComp {
|
||||||
var vertical =
|
var vertical =
|
||||||
new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
|
new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
|
||||||
|
|
||||||
var split = new SimpleDoubleProperty();
|
var leftSplit = new SimpleDoubleProperty();
|
||||||
var tabs = new BrowserSessionTabsComp(model, split).apply(struc -> {
|
var rightSplit = new SimpleDoubleProperty();
|
||||||
|
var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit);
|
||||||
|
tabs.apply(struc -> {
|
||||||
struc.get().setViewOrder(1);
|
struc.get().setViewOrder(1);
|
||||||
struc.get().setPickOnBounds(false);
|
struc.get().setPickOnBounds(false);
|
||||||
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
||||||
|
@ -108,20 +116,54 @@ public class BrowserSessionComp extends SimpleComp {
|
||||||
AnchorPane.setLeftAnchor(struc.get(), 0.0);
|
AnchorPane.setLeftAnchor(struc.get(), 0.0);
|
||||||
AnchorPane.setRightAnchor(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())
|
var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy())
|
||||||
.apply(struc -> {
|
.apply(struc -> {
|
||||||
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
AnchorPane.setTopAnchor(struc.get(), 0.0);
|
||||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||||
})
|
})
|
||||||
.styleClass("tab-loading-indicator");
|
.styleClass("tab-loading-indicator");
|
||||||
var loadingStack = new AnchorComp(List.of(tabs, loadingIndicator));
|
|
||||||
var splitPane = new SideSplitPaneComp(vertical, loadingStack)
|
var pinnedStack = new StackComp(List.of(new LabelComp("a")));
|
||||||
|
pinnedStack.apply(struc -> {
|
||||||
|
model.getEffectiveRightTab().subscribe( (newValue) -> {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
var r = newValue.comp().createRegion();
|
||||||
|
struc.get().getChildren().add(r);
|
||||||
|
} else {
|
||||||
|
struc.get().getChildren().clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator));
|
||||||
|
var splitPane = new LeftSplitPaneComp(vertical, loadingStack)
|
||||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||||
.withOnDividerChange(d -> {
|
.withOnDividerChange(d -> {
|
||||||
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
|
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
|
||||||
split.set(d);
|
leftSplit.set(d);
|
||||||
})
|
});
|
||||||
.apply(struc -> {
|
splitPane.apply(struc -> {
|
||||||
struc.getLeft().setMinWidth(200);
|
struc.getLeft().setMinWidth(200);
|
||||||
struc.getLeft().setMaxWidth(500);
|
struc.getLeft().setMaxWidth(500);
|
||||||
struc.get().setPickOnBounds(false);
|
struc.get().setPickOnBounds(false);
|
||||||
|
@ -140,9 +182,7 @@ public class BrowserSessionComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
splitPane.styleClass("browser");
|
||||||
var r = splitPane.createRegion();
|
return splitPane.createRegion();
|
||||||
r.getStyleClass().add("browser");
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package io.xpipe.app.browser.session;
|
package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.BrowserHomeModel;
|
||||||
import io.xpipe.app.browser.BrowserSavedState;
|
import io.xpipe.app.browser.BrowserSavedState;
|
||||||
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
||||||
import io.xpipe.app.browser.BrowserTransferModel;
|
import io.xpipe.app.browser.BrowserTransferModel;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
|
@ -11,22 +13,94 @@ import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
import io.xpipe.core.util.FailableFunction;
|
import io.xpipe.core.util.FailableFunction;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableMap;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab<?>> {
|
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab> {
|
||||||
|
|
||||||
public static final BrowserSessionModel DEFAULT = new BrowserSessionModel();
|
public static final BrowserSessionModel DEFAULT = new BrowserSessionModel();
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEFAULT.getSessionEntries().add(new BrowserHomeModel(DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||||
private final Property<Boolean> draggingFiles = new SimpleBooleanProperty();
|
private final Property<Boolean> draggingFiles = new SimpleBooleanProperty();
|
||||||
|
private final Property<BrowserSessionTab> globalPinnedTab = new SimpleObjectProperty<>();
|
||||||
|
private final ObservableValue<BrowserSessionTab> effectiveRightTab = createEffectiveRightTab();
|
||||||
|
private final ObservableMap<BrowserSessionTab, BrowserSessionTab> splits = FXCollections.observableHashMap();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BrowserSessionModel() {
|
||||||
|
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 void splitTab(BrowserSessionTab tab, BrowserSessionTab split) {
|
||||||
|
splits.put(tab, split);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(BrowserSavedState state) {
|
public void restoreState(BrowserSavedState state) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
|
@ -74,14 +148,15 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
openFileSystemSync(store, path, externalBusy);
|
openFileSystemSync(store, path, externalBusy, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenFileSystemModel openFileSystemSync(
|
public OpenFileSystemModel openFileSystemSync(
|
||||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||||
BooleanProperty externalBusy)
|
BooleanProperty externalBusy,
|
||||||
|
boolean select)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
OpenFileSystemModel model;
|
OpenFileSystemModel model;
|
||||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||||
|
@ -91,11 +166,13 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||||
// Prevent multiple calls from interfering with each other
|
// Prevent multiple calls from interfering with each other
|
||||||
synchronized (BrowserSessionModel.this) {
|
synchronized (BrowserSessionModel.this) {
|
||||||
sessionEntries.add(model);
|
sessionEntries.add(model);
|
||||||
|
if (select) {
|
||||||
// The tab pane doesn't automatically select new tabs
|
// The tab pane doesn't automatically select new tabs
|
||||||
selectedEntry.setValue(model);
|
selectedEntry.setValue(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
package io.xpipe.app.browser.session;
|
package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataColor;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class BrowserSessionTab<T extends DataStore> {
|
public abstract class BrowserSessionTab {
|
||||||
|
|
||||||
protected final DataStoreEntryRef<? extends T> entry;
|
|
||||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||||
protected final String name;
|
protected final String name;
|
||||||
protected final String tooltip;
|
protected final String tooltip;
|
||||||
|
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
|
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name, String tooltip) {
|
||||||
this.browserModel = browserModel;
|
this.browserModel = browserModel;
|
||||||
this.entry = entry;
|
this.name = name;
|
||||||
this.name = DataStorage.get().getStoreEntryDisplayName(entry.get());
|
this.tooltip = tooltip;
|
||||||
this.tooltip = DataStorage.get().getStorePath(entry.getEntry()).toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Comp<?> comp();
|
public abstract Comp<?> comp();
|
||||||
|
@ -33,4 +34,12 @@ public abstract class BrowserSessionTab<T extends DataStore> {
|
||||||
public abstract void init() throws Exception;
|
public abstract void init() throws Exception;
|
||||||
|
|
||||||
public abstract void close();
|
public abstract void close();
|
||||||
|
|
||||||
|
public abstract String getIcon();
|
||||||
|
|
||||||
|
public abstract DataColor getColor();
|
||||||
|
|
||||||
|
public boolean isCloseable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package io.xpipe.app.browser.session;
|
package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserWelcomeComp;
|
|
||||||
import io.xpipe.app.comp.base.MultiContentComp;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
@ -10,28 +8,33 @@ import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ContextMenuHelper;
|
import io.xpipe.app.util.ContextMenuHelper;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableDoubleValue;
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.skin.TabPaneSkin;
|
||||||
import javafx.scene.input.*;
|
import javafx.scene.input.*;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
import atlantafx.base.controls.RingProgressIndicator;
|
import atlantafx.base.controls.RingProgressIndicator;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static atlantafx.base.theme.Styles.DENSE;
|
import static atlantafx.base.theme.Styles.DENSE;
|
||||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||||
|
@ -41,26 +44,30 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
|
|
||||||
private final BrowserSessionModel model;
|
private final BrowserSessionModel model;
|
||||||
private final ObservableDoubleValue leftPadding;
|
private final ObservableDoubleValue leftPadding;
|
||||||
|
private final DoubleProperty rightPadding;
|
||||||
|
|
||||||
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding) {
|
@Getter
|
||||||
|
private final DoubleProperty headerHeight;
|
||||||
|
|
||||||
|
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding, DoubleProperty rightPadding) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.leftPadding = leftPadding;
|
this.leftPadding = leftPadding;
|
||||||
|
this.rightPadding = rightPadding;
|
||||||
|
this.headerHeight = new SimpleDoubleProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Region createSimple() {
|
public Region createSimple() {
|
||||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
var tabs = createTabPane();
|
||||||
map.put(Comp.hspacer().styleClass("top-spacer"), new SimpleBooleanProperty(true));
|
var topBackground = Comp.hspacer().styleClass("top-spacer").createRegion();
|
||||||
map.put(Comp.of(() -> createTabPane()), Bindings.isNotEmpty(model.getSessionEntries()));
|
leftPadding.subscribe(number -> {
|
||||||
map.put(
|
StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue()));
|
||||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
});
|
||||||
Bindings.createBooleanBinding(
|
var stack = new StackPane(topBackground, tabs);
|
||||||
() -> {
|
stack.setAlignment(Pos.TOP_CENTER);
|
||||||
return model.getSessionEntries().size() == 0;
|
topBackground.prefHeightProperty().bind(headerHeight);
|
||||||
},
|
topBackground.maxHeightProperty().bind(topBackground.prefHeightProperty());
|
||||||
model.getSessionEntries()));
|
topBackground.prefWidthProperty().bind(tabs.widthProperty());
|
||||||
var multi = new MultiContentComp(map);
|
return stack;
|
||||||
multi.apply(struc -> ((StackPane) struc.get()).setAlignment(Pos.TOP_CENTER));
|
|
||||||
return multi.createRegion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TabPane createTabPane() {
|
private TabPane createTabPane() {
|
||||||
|
@ -69,6 +76,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
tabs.setTabMinWidth(Region.USE_PREF_SIZE);
|
tabs.setTabMinWidth(Region.USE_PREF_SIZE);
|
||||||
tabs.setTabMaxWidth(400);
|
tabs.setTabMaxWidth(400);
|
||||||
tabs.setTabClosingPolicy(ALL_TABS);
|
tabs.setTabClosingPolicy(ALL_TABS);
|
||||||
|
tabs.setSkin(new TabPaneSkin(tabs));
|
||||||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||||
toggleStyleClass(tabs, DENSE);
|
toggleStyleClass(tabs, DENSE);
|
||||||
|
|
||||||
|
@ -80,22 +88,31 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
tabs.lookupAll(".tab-header-area").forEach(node -> {
|
tabs.lookupAll(".tab-header-area").forEach(node -> {
|
||||||
node.setClip(null);
|
node.setClip(null);
|
||||||
node.setPickOnBounds(false);
|
node.setPickOnBounds(false);
|
||||||
|
|
||||||
|
var r = (Region) node;
|
||||||
|
r.prefHeightProperty().bind(r.maxHeightProperty());
|
||||||
|
r.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
});
|
});
|
||||||
tabs.lookupAll(".headers-region").forEach(node -> {
|
tabs.lookupAll(".headers-region").forEach(node -> {
|
||||||
node.setClip(null);
|
node.setClip(null);
|
||||||
node.setPickOnBounds(false);
|
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");
|
Region headerArea = (Region) tabs.lookup(".tab-header-area");
|
||||||
headerArea
|
headerArea
|
||||||
.paddingProperty()
|
.paddingProperty()
|
||||||
.bind(Bindings.createObjectBinding(
|
.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
|
// Restore state
|
||||||
model.getSessionEntries().forEach(v -> {
|
model.getSessionEntries().forEach(v -> {
|
||||||
|
@ -156,7 +173,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab<?>>) c -> {
|
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab>) c -> {
|
||||||
while (c.next()) {
|
while (c.next()) {
|
||||||
for (var r : c.getRemoved()) {
|
for (var r : c.getRemoved()) {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
@ -245,9 +262,28 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab) {
|
private ContextMenu createContextMenu(TabPane tabs, Tab tab, BrowserSessionTab tabModel) {
|
||||||
var cm = ContextMenuHelper.create();
|
var cm = ContextMenuHelper.create();
|
||||||
|
|
||||||
|
if (tabModel.isCloseable()) {
|
||||||
|
var unsplit = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("unpinTab"));
|
||||||
|
unsplit.visibleProperty().bind(PlatformThread.sync(Bindings.createBooleanBinding(() -> {
|
||||||
|
return model.getGlobalPinnedTab().getValue() != null && model.getGlobalPinnedTab().getValue().equals(tabModel);
|
||||||
|
}, model.getGlobalPinnedTab())));
|
||||||
|
unsplit.setOnAction(event -> {
|
||||||
|
model.unpinTab(tabModel);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
cm.getItems().add(unsplit);
|
||||||
|
|
||||||
|
var split = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("pinTab"));
|
||||||
|
split.setOnAction(event -> {
|
||||||
|
model.pinTab(tabModel);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
cm.getItems().add(split);
|
||||||
|
}
|
||||||
|
|
||||||
var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab"));
|
var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab"));
|
||||||
select.acceleratorProperty()
|
select.acceleratorProperty()
|
||||||
.bind(Bindings.createObjectBinding(
|
.bind(Bindings.createObjectBinding(
|
||||||
|
@ -272,7 +308,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
var close = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeTab"));
|
var close = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeTab"));
|
||||||
close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
|
close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
|
||||||
close.setOnAction(event -> {
|
close.setOnAction(event -> {
|
||||||
|
if (tab.isClosable()) {
|
||||||
tabs.getTabs().remove(tab);
|
tabs.getTabs().remove(tab);
|
||||||
|
}
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
cm.getItems().add(close);
|
cm.getItems().add(close);
|
||||||
|
@ -280,7 +318,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeOtherTabs"));
|
var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeOtherTabs"));
|
||||||
closeOthers.setOnAction(event -> {
|
closeOthers.setOnAction(event -> {
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
.removeAll(tabs.getTabs().stream().filter(t -> t != tab).toList());
|
.removeAll(tabs.getTabs().stream()
|
||||||
|
.filter(t -> t != tab && t.isClosable())
|
||||||
|
.toList());
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
cm.getItems().add(closeOthers);
|
cm.getItems().add(closeOthers);
|
||||||
|
@ -290,7 +330,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
var index = tabs.getTabs().indexOf(tab);
|
var index = tabs.getTabs().indexOf(tab);
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
.removeAll(tabs.getTabs().stream()
|
.removeAll(tabs.getTabs().stream()
|
||||||
.filter(t -> tabs.getTabs().indexOf(t) < index)
|
.filter(t -> tabs.getTabs().indexOf(t) < index && t.isClosable())
|
||||||
.toList());
|
.toList());
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
|
@ -301,7 +341,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
var index = tabs.getTabs().indexOf(tab);
|
var index = tabs.getTabs().indexOf(tab);
|
||||||
tabs.getTabs()
|
tabs.getTabs()
|
||||||
.removeAll(tabs.getTabs().stream()
|
.removeAll(tabs.getTabs().stream()
|
||||||
.filter(t -> tabs.getTabs().indexOf(t) > index)
|
.filter(t -> tabs.getTabs().indexOf(t) > index && t.isClosable())
|
||||||
.toList());
|
.toList());
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
|
@ -311,7 +351,9 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
closeAll.setAccelerator(
|
closeAll.setAccelerator(
|
||||||
new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
|
new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
|
||||||
closeAll.setOnAction(event -> {
|
closeAll.setOnAction(event -> {
|
||||||
tabs.getTabs().clear();
|
tabs.getTabs()
|
||||||
|
.removeAll(
|
||||||
|
tabs.getTabs().stream().filter(t -> t.isClosable()).toList());
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
cm.getItems().add(closeAll);
|
cm.getItems().add(closeAll);
|
||||||
|
@ -319,36 +361,92 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tab createTab(TabPane tabs, BrowserSessionTab<?> model) {
|
private Tab createTab(TabPane tabs, BrowserSessionTab tabModel) {
|
||||||
var tab = new Tab();
|
var tab = new Tab();
|
||||||
tab.setContextMenu(createContextMenu(tabs, tab));
|
tab.setContextMenu(createContextMenu(tabs, tab, tabModel));
|
||||||
|
|
||||||
|
tab.setClosable(tabModel.isCloseable());
|
||||||
|
|
||||||
|
if (tabModel.getIcon() != null) {
|
||||||
var ring = new RingProgressIndicator(0, false);
|
var ring = new RingProgressIndicator(0, false);
|
||||||
ring.setMinSize(16, 16);
|
ring.setMinSize(16, 16);
|
||||||
ring.setPrefSize(16, 16);
|
ring.setPrefSize(16, 16);
|
||||||
ring.setMaxSize(16, 16);
|
ring.setMaxSize(16, 16);
|
||||||
ring.progressProperty()
|
ring.progressProperty()
|
||||||
.bind(Bindings.createDoubleBinding(
|
.bind(Bindings.createDoubleBinding(
|
||||||
() -> model.getBusy().get()
|
() -> tabModel.getBusy().get()
|
||||||
&& !AppPrefs.get().performanceMode().get()
|
&& !AppPrefs.get().performanceMode().get()
|
||||||
? -1d
|
? -1d
|
||||||
: 0,
|
: 0,
|
||||||
PlatformThread.sync(model.getBusy()),
|
PlatformThread.sync(tabModel.getBusy()),
|
||||||
AppPrefs.get().performanceMode()));
|
AppPrefs.get().performanceMode()));
|
||||||
|
|
||||||
var image = model.getEntry().get().getEffectiveIconFile();
|
var image = tabModel.getIcon();
|
||||||
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion();
|
||||||
|
|
||||||
tab.graphicProperty()
|
tab.graphicProperty()
|
||||||
.bind(Bindings.createObjectBinding(
|
.bind(Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return model.getBusy().get() ? ring : logo;
|
return tabModel.getBusy().get() ? ring : logo;
|
||||||
},
|
},
|
||||||
PlatformThread.sync(model.getBusy())));
|
PlatformThread.sync(tabModel.getBusy())));
|
||||||
tab.setText(model.getName());
|
}
|
||||||
|
|
||||||
Comp<?> comp = model.comp();
|
if (tabModel.getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||||
tab.setContent(comp.createRegion());
|
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.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 lastSplitRegion = new AtomicReference<Region>();
|
||||||
|
// model.getGlobalPinnedTab().subscribe( (newValue) -> {
|
||||||
|
// PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
// if (newValue != null) {
|
||||||
|
// var r = newValue.comp().createRegion();
|
||||||
|
// split.getItems().add(r);
|
||||||
|
// lastSplitRegion.set(r);
|
||||||
|
// } else if (split.getItems().size() > 1) {
|
||||||
|
// split.getItems().removeLast();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// model.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
|
||||||
|
// PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
// if (newValue != null && newValue.equals(model.getGlobalPinnedTab().getValue()) && split.getItems().size() > 1) {
|
||||||
|
// split.getItems().remove(lastSplitRegion.get());
|
||||||
|
// } else if (split.getItems().size() > 1 && !split.getItems().contains(lastSplitRegion.get())) {
|
||||||
|
// split.getItems().add(lastSplitRegion.get());
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
var id = UUID.randomUUID().toString();
|
var id = UUID.randomUUID().toString();
|
||||||
tab.setId(id);
|
tab.setId(id);
|
||||||
|
@ -360,18 +458,20 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||||
var w = l.maxWidthProperty();
|
var w = l.maxWidthProperty();
|
||||||
l.minWidthProperty().bind(w);
|
l.minWidthProperty().bind(w);
|
||||||
l.prefWidthProperty().bind(w);
|
l.prefWidthProperty().bind(w);
|
||||||
|
if (!tabModel.isCloseable()) {
|
||||||
|
l.pseudoClassStateChanged(PseudoClass.getPseudoClass("static"), true);
|
||||||
|
}
|
||||||
|
|
||||||
var close = (StackPane) tabs.lookup("#" + id + " .tab-close-button");
|
var close = (StackPane) tabs.lookup("#" + id + " .tab-close-button");
|
||||||
close.setPrefWidth(30);
|
close.setPrefWidth(30);
|
||||||
|
|
||||||
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
|
||||||
c.getStyleClass().add("color-box");
|
c.getStyleClass().add("color-box");
|
||||||
var color =
|
var color = tabModel.getColor();
|
||||||
DataStorage.get().getEffectiveColor(model.getEntry().get());
|
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
c.getStyleClass().add(color.getId());
|
c.getStyleClass().add(color.getId());
|
||||||
}
|
}
|
||||||
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip()), null).augment(c);
|
new TooltipAugment<>(new SimpleStringProperty(tabModel.getTooltip()), null).augment(c);
|
||||||
c.addEventHandler(
|
c.addEventHandler(
|
||||||
DragEvent.DRAG_ENTERED,
|
DragEvent.DRAG_ENTERED,
|
||||||
mouseEvent -> Platform.runLater(
|
mouseEvent -> Platform.runLater(
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
|
import io.xpipe.app.fxcomps.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()),
|
||||||
|
DataStorage.get().getStorePath(entry.getEntry()).toString());
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,7 +68,14 @@ public class IntegratedTextAreaComp extends Comp<IntegratedTextAreaComp.Structur
|
||||||
return new TextAreaStructure(c, textArea.getTextArea());
|
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();
|
var struc = fileDrop.createStructure();
|
||||||
return new Structure(struc.get(), struc.getCompStructure().getTextArea());
|
return new Structure(struc.get(), struc.getCompStructure().getTextArea());
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,14 @@ import lombok.Value;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Consumer;
|
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<?> left;
|
||||||
private final Comp<?> center;
|
private final Comp<?> center;
|
||||||
private Double initialWidth;
|
private Double initialWidth;
|
||||||
private Consumer<Double> onDividerChange;
|
private Consumer<Double> onDividerChange;
|
||||||
|
|
||||||
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
public LeftSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||||
this.left = left;
|
this.left = left;
|
||||||
this.center = center;
|
this.center = center;
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,12 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||||
return new Structure(sidebar, c, r, r.getDividers().getFirst());
|
return new Structure(sidebar, c, r, r.getDividers().getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SideSplitPaneComp withInitialWidth(double val) {
|
public LeftSplitPaneComp withInitialWidth(double val) {
|
||||||
this.initialWidth = val;
|
this.initialWidth = val;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
public LeftSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
||||||
this.onDividerChange = onDividerChange;
|
this.onDividerChange = onDividerChange;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
@ -9,17 +8,13 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.fxcomps.util.LabelGraphic;
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
@ -27,9 +22,6 @@ import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
|
@ -50,14 +42,14 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
var selectedBorder = Bindings.createObjectBinding(
|
var selectedBorder = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var c = Platform.getPreferences().getAccentColor().desaturate();
|
var c = Platform.getPreferences().getAccentColor().desaturate();
|
||||||
return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(10, 1, 10, 2)));
|
return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(12, 1, 12, 2)));
|
||||||
},
|
},
|
||||||
Platform.getPreferences().accentColorProperty());
|
Platform.getPreferences().accentColorProperty());
|
||||||
|
|
||||||
var hoverBorder = Bindings.createObjectBinding(
|
var hoverBorder = Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var c = Platform.getPreferences().getAccentColor().darker().desaturate();
|
var c = Platform.getPreferences().getAccentColor().darker().desaturate();
|
||||||
return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(10, 1, 10, 2)));
|
return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(12, 1, 12, 2)));
|
||||||
},
|
},
|
||||||
Platform.getPreferences().accentColorProperty());
|
Platform.getPreferences().accentColorProperty());
|
||||||
|
|
||||||
|
@ -141,29 +133,6 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
vbox.getChildren().add(b.createRegion());
|
vbox.getChildren().add(b.createRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
var zone = ZoneId.of(ZoneId.SHORT_IDS.get("PST"));
|
|
||||||
var now = Instant.now();
|
|
||||||
var phStart = ZonedDateTime.of(2024, 10, 22, 0, 1, 0, 0, zone).toInstant();
|
|
||||||
var phEnd = ZonedDateTime.of(2024, 10, 23, 0, 1, 0, 0, zone).toInstant();
|
|
||||||
var clicked = AppCache.get("phClicked", Boolean.class, () -> false);
|
|
||||||
var phShow = now.isAfter(phStart) && now.isBefore(phEnd) && !clicked;
|
|
||||||
if (phShow) {
|
|
||||||
var hide = new SimpleBooleanProperty(false);
|
|
||||||
var b = new IconButtonComp(new LabelGraphic.ImageGraphic("app:producthunt-color.png", 24), () -> {
|
|
||||||
AppCache.update("phClicked", true);
|
|
||||||
Hyperlinks.open(Hyperlinks.PRODUCT_HUNT);
|
|
||||||
hide.set(true);
|
|
||||||
})
|
|
||||||
.tooltip(new SimpleStringProperty("Product Hunt"));
|
|
||||||
b.apply(struc -> {
|
|
||||||
AppFont.setSize(struc.get(), 1);
|
|
||||||
});
|
|
||||||
b.hide(hide);
|
|
||||||
vbox.getChildren().add(b.createRegion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var filler = new Button();
|
var filler = new Button();
|
||||||
filler.setDisable(true);
|
filler.setDisable(true);
|
||||||
filler.setMaxHeight(3000);
|
filler.setMaxHeight(3000);
|
||||||
|
|
|
@ -95,7 +95,16 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||||
nameCC.setMinWidth(100);
|
nameCC.setMinWidth(100);
|
||||||
nameCC.setHgrow(Priority.ALWAYS);
|
nameCC.setHgrow(Priority.ALWAYS);
|
||||||
grid.getColumnConstraints().addAll(nameCC);
|
grid.getColumnConstraints().addAll(nameCC);
|
||||||
|
|
||||||
|
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||||
var nameBox = new HBox(name, notes);
|
var nameBox = new HBox(name, notes);
|
||||||
|
getWrapper().getSessionActive().subscribe(aBoolean -> {
|
||||||
|
if (!aBoolean) {
|
||||||
|
nameBox.getChildren().remove(active);
|
||||||
|
} else {
|
||||||
|
nameBox.getChildren().add(1, active);
|
||||||
|
}
|
||||||
|
});
|
||||||
nameBox.setSpacing(6);
|
nameBox.setSpacing(6);
|
||||||
nameBox.setAlignment(Pos.CENTER_LEFT);
|
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
grid.addRow(0, nameBox);
|
grid.addRow(0, nameBox);
|
||||||
|
|
|
@ -41,11 +41,19 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||||
grid.add(storeIcon, 0, 0, 1, 2);
|
grid.add(storeIcon, 0, 0, 1, 2);
|
||||||
grid.getColumnConstraints().add(new ColumnConstraints(56));
|
grid.getColumnConstraints().add(new ColumnConstraints(56));
|
||||||
|
|
||||||
var nameAndNotes = new HBox(name, notes);
|
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||||
nameAndNotes.setSpacing(6);
|
var nameBox = new HBox(name, notes);
|
||||||
nameAndNotes.setAlignment(Pos.CENTER_LEFT);
|
nameBox.setSpacing(6);
|
||||||
grid.add(nameAndNotes, 1, 0);
|
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
GridPane.setVgrow(nameAndNotes, Priority.ALWAYS);
|
grid.add(nameBox, 1, 0);
|
||||||
|
GridPane.setVgrow(nameBox, Priority.ALWAYS);
|
||||||
|
getWrapper().getSessionActive().subscribe(aBoolean -> {
|
||||||
|
if (!aBoolean) {
|
||||||
|
nameBox.getChildren().remove(active);
|
||||||
|
} else {
|
||||||
|
nameBox.getChildren().add(1, active);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var summaryBox = new HBox(createSummary());
|
var summaryBox = new HBox(createSummary());
|
||||||
summaryBox.setAlignment(Pos.TOP_LEFT);
|
summaryBox.setAlignment(Pos.TOP_LEFT);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
|
||||||
|
public class StoreActiveComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final StoreEntryWrapper wrapper;
|
||||||
|
|
||||||
|
public StoreActiveComp(StoreEntryWrapper wrapper) {
|
||||||
|
this.wrapper = wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var c = new Circle(6);
|
||||||
|
c.getStyleClass().add("dot");
|
||||||
|
c.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY) {
|
||||||
|
wrapper.stopSession();
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var pane = new StackPane(c);
|
||||||
|
pane.setAlignment(Pos.CENTER);
|
||||||
|
pane.visibleProperty().bind(wrapper.getSessionActive());
|
||||||
|
pane.getStyleClass().add("store-active-comp");
|
||||||
|
new TooltipAugment<>("sessionActive", null).augment(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.*;
|
import io.xpipe.app.util.*;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.ValidationContext;
|
import io.xpipe.core.store.ValidatableStore;
|
||||||
import io.xpipe.core.util.ValidationException;
|
import io.xpipe.core.util.ValidationException;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -157,6 +157,17 @@ public class StoreCreationComp extends DialogComp {
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
store);
|
store);
|
||||||
|
|
||||||
|
skippable.bind(Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
if (name.get() != null && store.get().isComplete() && store.get() instanceof ValidatableStore) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
store,
|
||||||
|
name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showEdit(DataStoreEntry e) {
|
public static void showEdit(DataStoreEntry e) {
|
||||||
|
@ -165,11 +176,8 @@ public class StoreCreationComp extends DialogComp {
|
||||||
e.getProvider(),
|
e.getProvider(),
|
||||||
e.getStore(),
|
e.getStore(),
|
||||||
v -> true,
|
v -> true,
|
||||||
(newE, context, validated) -> {
|
(newE, validated) -> {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
if (context != null) {
|
|
||||||
context.close();
|
|
||||||
}
|
|
||||||
if (!DataStorage.get().getStoreEntries().contains(e)) {
|
if (!DataStorage.get().getStoreEntries().contains(e)) {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(newE);
|
DataStorage.get().addStoreEntryIfNotPresent(newE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,21 +199,22 @@ public class StoreCreationComp extends DialogComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showCreation(DataStore base, DataStoreCreationCategory category) {
|
public static void showCreation(DataStore base, DataStoreCreationCategory category) {
|
||||||
|
var prov = base != null ? DataStoreProviders.byStore(base) : null;
|
||||||
show(
|
show(
|
||||||
null,
|
null,
|
||||||
base != null ? DataStoreProviders.byStore(base) : null,
|
prov,
|
||||||
base,
|
base,
|
||||||
dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()),
|
dataStoreProvider -> (category != null && category.equals(dataStoreProvider.getCreationCategory()))
|
||||||
(e, context, validated) -> {
|
|| dataStoreProvider.equals(prov),
|
||||||
|
(e, validated) -> {
|
||||||
try {
|
try {
|
||||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||||
if (context != null
|
if (validated
|
||||||
&& validated
|
|
||||||
&& e.getProvider().shouldShowScan()
|
&& e.getProvider().shouldShowScan()
|
||||||
&& AppPrefs.get()
|
&& AppPrefs.get()
|
||||||
.openConnectionSearchWindowOnConnectionCreation()
|
.openConnectionSearchWindowOnConnectionCreation()
|
||||||
.get()) {
|
.get()) {
|
||||||
ScanAlert.showAsync(e, context);
|
ScanAlert.showAsync(e);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
@ -217,7 +226,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
public interface CreationConsumer {
|
public interface CreationConsumer {
|
||||||
|
|
||||||
void consume(DataStoreEntry entry, ValidationContext<?> validationContext, boolean validated);
|
void consume(DataStoreEntry entry, boolean validated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void show(
|
private static void show(
|
||||||
|
@ -254,9 +263,9 @@ public class StoreCreationComp extends DialogComp {
|
||||||
@Override
|
@Override
|
||||||
protected List<Comp<?>> customButtons() {
|
protected List<Comp<?>> customButtons() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new ButtonComp(AppI18n.observable("skip"), null, () -> {
|
new ButtonComp(AppI18n.observable("skipValidation"), null, () -> {
|
||||||
if (showInvalidConfirmAlert()) {
|
if (showInvalidConfirmAlert()) {
|
||||||
commit(null, false);
|
commit(false);
|
||||||
} else {
|
} else {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -299,7 +308,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
// We didn't change anything
|
// We didn't change anything
|
||||||
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) {
|
||||||
commit(null, false);
|
commit(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,18 +338,14 @@ public class StoreCreationComp extends DialogComp {
|
||||||
|
|
||||||
try (var ignored = new BooleanScope(busy).start()) {
|
try (var ignored = new BooleanScope(busy).start()) {
|
||||||
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
DataStorage.get().addStoreEntryInProgress(entry.getValue());
|
||||||
var context = entry.getValue().validateAndKeepOpenOrThrowAndClose(null);
|
entry.getValue().validateOrThrow();
|
||||||
commit(context, true);
|
commit(true);
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
if (ex instanceof ValidationException) {
|
if (ex instanceof ValidationException) {
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
skippable.set(false);
|
|
||||||
} else if (ex instanceof StackOverflowError) {
|
} else if (ex instanceof StackOverflowError) {
|
||||||
// Cycles in connection graphs can fail hard but are expected
|
// Cycles in connection graphs can fail hard but are expected
|
||||||
ErrorEvent.expected(ex);
|
ErrorEvent.expected(ex);
|
||||||
skippable.set(false);
|
|
||||||
} else {
|
|
||||||
skippable.set(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMessage = ExceptionConverter.convertMessage(ex);
|
var newMessage = ExceptionConverter.convertMessage(ex);
|
||||||
|
@ -415,14 +420,14 @@ public class StoreCreationComp extends DialogComp {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commit(ValidationContext<?> validationContext, boolean validated) {
|
private void commit(boolean validated) {
|
||||||
if (finished.get()) {
|
if (finished.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
finished.setValue(true);
|
finished.setValue(true);
|
||||||
|
|
||||||
if (entry.getValue() != null) {
|
if (entry.getValue() != null) {
|
||||||
consumer.consume(entry.getValue(), validationContext, validated);
|
consumer.consume(entry.getValue(), validated);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
@ -433,7 +438,7 @@ public class StoreCreationComp extends DialogComp {
|
||||||
private Region createLayout() {
|
private Region createLayout() {
|
||||||
var layout = new BorderPane();
|
var layout = new BorderPane();
|
||||||
layout.getStyleClass().add("store-creator");
|
layout.getStyleClass().add("store-creator");
|
||||||
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
|
var providerChoice = new StoreProviderChoiceComp(filter, provider);
|
||||||
var showProviders = (!staticDisplay
|
var showProviders = (!staticDisplay
|
||||||
&& (providerChoice.getProviders().size() > 1
|
&& (providerChoice.getProviders().size() > 1
|
||||||
|| providerChoice.getProviders().getFirst().showProviderChoice()))
|
|| providerChoice.getProviders().getFirst().showProviderChoice()))
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class StoreCreationMenu {
|
||||||
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline"));
|
||||||
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
automatically.textProperty().bind(AppI18n.observable("addAutomatically"));
|
||||||
automatically.setOnAction(event -> {
|
automatically.setOnAction(event -> {
|
||||||
ScanAlert.showAsync(null, null);
|
ScanAlert.showAsync(null);
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
menu.getItems().add(automatically);
|
menu.getItems().add(automatically);
|
||||||
|
@ -32,13 +32,11 @@ public class StoreCreationMenu {
|
||||||
|
|
||||||
menu.getItems().add(category("addDesktop", "mdi2c-camera-plus", DataStoreCreationCategory.DESKTOP, null));
|
menu.getItems().add(category("addDesktop", "mdi2c-camera-plus", DataStoreCreationCategory.DESKTOP, null));
|
||||||
|
|
||||||
menu.getItems()
|
|
||||||
.add(category(
|
|
||||||
"addShell", "mdi2t-text-box-multiple", DataStoreCreationCategory.SHELL, "shellEnvironment"));
|
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category("addScript", "mdi2s-script-text-outline", DataStoreCreationCategory.SCRIPT, "script"));
|
.add(category("addScript", "mdi2s-script-text-outline", DataStoreCreationCategory.SCRIPT, "script"));
|
||||||
|
|
||||||
|
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, null));
|
||||||
|
|
||||||
menu.getItems()
|
menu.getItems()
|
||||||
.add(category(
|
.add(category(
|
||||||
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "customService"));
|
"addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, "customService"));
|
||||||
|
|
|
@ -439,7 +439,8 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
&& !LicenseProvider.get().getFeature(p.getProFeatureId()).isSupported();
|
&& !LicenseProvider.get().getFeature(p.getProFeatureId()).isSupported();
|
||||||
if (proRequired) {
|
if (proRequired) {
|
||||||
item.setDisable(true);
|
item.setDisable(true);
|
||||||
item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name));
|
item.textProperty()
|
||||||
|
.bind(LicenseProvider.get().getFeature(p.getProFeatureId()).suffixObservable(name.getValue()));
|
||||||
} else {
|
} else {
|
||||||
item.textProperty().bind(name);
|
item.textProperty().bind(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.MultiContentComp;
|
import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
@ -34,18 +36,23 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
StoreViewState.get().getActiveCategory().addListener((observable, oldValue, newValue) -> {
|
StoreViewState.get().getActiveCategory().addListener((observable, oldValue, newValue) -> {
|
||||||
struc.get().setVvalue(0);
|
struc.get().setVvalue(0);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
content.apply(struc -> {
|
|
||||||
// Reset scroll
|
// Reset scroll
|
||||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||||
struc.get().setVvalue(0);
|
struc.get().setVvalue(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reset scroll
|
||||||
|
StoreViewState.get().getFilterString().addListener((observable, oldValue, newValue) -> {
|
||||||
|
struc.get().setVvalue(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return content.styleClass("store-list-comp");
|
return content.styleClass("store-list-comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
|
var scriptsIntroShowing = new SimpleBooleanProperty(!AppCache.getBoolean("scriptsIntroCompleted", false));
|
||||||
var initialCount = 1;
|
var initialCount = 1;
|
||||||
var showIntro = Bindings.createBooleanBinding(
|
var showIntro = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -63,6 +70,46 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
},
|
},
|
||||||
StoreViewState.get().getAllEntries().getList(),
|
StoreViewState.get().getAllEntries().getList(),
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
|
var showScriptsIntro = Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
if (StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(StoreViewState.get().getAllScriptsCategory())) {
|
||||||
|
return scriptsIntroShowing.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
scriptsIntroShowing,
|
||||||
|
StoreViewState.get().getActiveCategory());
|
||||||
|
var showList = Bindings.createBooleanBinding(
|
||||||
|
() -> {
|
||||||
|
if (StoreViewState.get()
|
||||||
|
.getActiveCategory()
|
||||||
|
.getValue()
|
||||||
|
.getRoot()
|
||||||
|
.equals(StoreViewState.get().getAllScriptsCategory())) {
|
||||||
|
return !scriptsIntroShowing.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StoreViewState.get()
|
||||||
|
.getCurrentTopLevelSection()
|
||||||
|
.getShownChildren()
|
||||||
|
.getList()
|
||||||
|
.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
StoreViewState.get().getActiveCategory(),
|
||||||
|
scriptsIntroShowing,
|
||||||
|
StoreViewState.get()
|
||||||
|
.getCurrentTopLevelSection()
|
||||||
|
.getShownChildren()
|
||||||
|
.getList());
|
||||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||||
map.put(
|
map.put(
|
||||||
new StoreNotFoundComp(),
|
new StoreNotFoundComp(),
|
||||||
|
@ -73,13 +120,9 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
.getCurrentTopLevelSection()
|
.getCurrentTopLevelSection()
|
||||||
.getShownChildren()
|
.getShownChildren()
|
||||||
.getList())));
|
.getList())));
|
||||||
map.put(
|
map.put(createList(), showList);
|
||||||
createList(),
|
|
||||||
Bindings.not(Bindings.isEmpty(StoreViewState.get()
|
|
||||||
.getCurrentTopLevelSection()
|
|
||||||
.getShownChildren()
|
|
||||||
.getList())));
|
|
||||||
map.put(new StoreIntroComp(), showIntro);
|
map.put(new StoreIntroComp(), showIntro);
|
||||||
|
map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro);
|
||||||
|
|
||||||
return new MultiContentComp(map).createRegion();
|
return new MultiContentComp(map).createRegion();
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,13 +83,7 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||||
return inRootCategory && showProvider;
|
return inRootCategory && showProvider;
|
||||||
},
|
},
|
||||||
StoreViewState.get().getActiveCategory());
|
StoreViewState.get().getActiveCategory());
|
||||||
var shownList = all.filtered(
|
var count = new CountComp<>(all.getList(), all.getList());
|
||||||
storeEntryWrapper -> {
|
|
||||||
return storeEntryWrapper.matchesFilter(
|
|
||||||
StoreViewState.get().getFilterString().getValue());
|
|
||||||
},
|
|
||||||
StoreViewState.get().getFilterString());
|
|
||||||
var count = new CountComp<>(shownList.getList(), all.getList());
|
|
||||||
|
|
||||||
var c = count.createRegion();
|
var c = count.createRegion();
|
||||||
var topBar = new HBox(
|
var topBar = new HBox(
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreCategory;
|
import io.xpipe.app.storage.DataStoreCategory;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
import io.xpipe.core.store.SingletonSessionStore;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
@ -44,6 +45,7 @@ public class StoreEntryWrapper {
|
||||||
private final Property<StoreNotes> notes;
|
private final Property<StoreNotes> notes;
|
||||||
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
||||||
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
||||||
|
private final BooleanProperty sessionActive = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public StoreEntryWrapper(DataStoreEntry entry) {
|
public StoreEntryWrapper(DataStoreEntry entry) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
|
@ -118,7 +120,15 @@ public class StoreEntryWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void stopSession() {
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
if (entry.getStore() instanceof SingletonSessionStore<?> singletonSessionStore) {
|
||||||
|
singletonSessionStore.stopSessionIfNeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
// We are probably in shutdown then
|
// We are probably in shutdown then
|
||||||
if (StoreViewState.get() == null) {
|
if (StoreViewState.get() == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -147,6 +157,7 @@ public class StoreEntryWrapper {
|
||||||
busy.setValue(entry.getBusyCounter().get() != 0);
|
busy.setValue(entry.getBusyCounter().get() != 0);
|
||||||
deletable.setValue(entry.getConfiguration().isDeletable()
|
deletable.setValue(entry.getConfiguration().isDeletable()
|
||||||
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
|
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
|
||||||
|
sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore<?> ss && ss.isSessionRunning());
|
||||||
|
|
||||||
category.setValue(StoreViewState.get()
|
category.setValue(StoreViewState.get()
|
||||||
.getCategoryWrapper(DataStorage.get()
|
.getCategoryWrapper(DataStorage.get()
|
||||||
|
@ -220,7 +231,7 @@ public class StoreEntryWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshChildren() {
|
public void refreshChildren() {
|
||||||
var hasChildren = DataStorage.get().refreshChildren(entry, null);
|
var hasChildren = DataStorage.get().refreshChildren(entry);
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
expanded.set(hasChildren);
|
expanded.set(hasChildren);
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class StoreIntroComp extends SimpleComp {
|
||||||
|
|
||||||
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
var scanButton = new Button(null, new FontIcon("mdi2m-magnify"));
|
||||||
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
scanButton.textProperty().bind(AppI18n.observable("detectConnections"));
|
||||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local(), null));
|
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
||||||
scanButton.setDefaultButton(true);
|
scanButton.setDefaultButton(true);
|
||||||
var scanPane = new StackPane(scanButton);
|
var scanPane = new StackPane(scanButton);
|
||||||
scanPane.setAlignment(Pos.CENTER);
|
scanPane.setAlignment(Pos.CENTER);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.comp.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||||
import io.xpipe.app.core.AppActionLinkDetector;
|
import io.xpipe.app.core.AppActionLinkDetector;
|
||||||
import io.xpipe.app.core.AppLayoutModel;
|
import io.xpipe.app.core.AppLayoutModel;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
@ -15,7 +15,7 @@ public class StoreLayoutComp extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
var struc = new LeftSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
||||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
|
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
|
||||||
.withOnDividerChange(aDouble -> {
|
.withOnDividerChange(aDouble -> {
|
||||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||||
|
|
|
@ -27,7 +27,6 @@ public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataSto
|
||||||
|
|
||||||
Predicate<DataStoreProvider> filter;
|
Predicate<DataStoreProvider> filter;
|
||||||
Property<DataStoreProvider> provider;
|
Property<DataStoreProvider> provider;
|
||||||
boolean staticDisplay;
|
|
||||||
|
|
||||||
public List<DataStoreProvider> getProviders() {
|
public List<DataStoreProvider> getProviders() {
|
||||||
return DataStoreProviders.getAll().stream()
|
return DataStoreProviders.getAll().stream()
|
||||||
|
@ -65,9 +64,7 @@ public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataSto
|
||||||
return cellFactory.get();
|
return cellFactory.get();
|
||||||
});
|
});
|
||||||
cb.setButtonCell(cellFactory.get());
|
cb.setButtonCell(cellFactory.get());
|
||||||
var l = getProviders().stream()
|
var l = getProviders();
|
||||||
.filter(p -> p.getCreationCategory() != null || staticDisplay)
|
|
||||||
.toList();
|
|
||||||
l.forEach(dataStoreProvider -> cb.getItems().add(dataStoreProvider));
|
l.forEach(dataStoreProvider -> cb.getItems().add(dataStoreProvider));
|
||||||
if (provider.getValue() == null) {
|
if (provider.getValue() == null) {
|
||||||
provider.setValue(l.getFirst());
|
provider.setValue(l.getFirst());
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
|
import io.xpipe.app.core.AppFont;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
public class StoreScriptsIntroComp extends SimpleComp {
|
||||||
|
|
||||||
|
private final BooleanProperty show;
|
||||||
|
|
||||||
|
public StoreScriptsIntroComp(BooleanProperty show) {
|
||||||
|
this.show = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createIntro() {
|
||||||
|
var title = new Label();
|
||||||
|
title.textProperty().bind(AppI18n.observable("scriptsIntroTitle"));
|
||||||
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
}
|
||||||
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
|
var introDesc = new Label();
|
||||||
|
introDesc.textProperty().bind(AppI18n.observable("scriptsIntroText"));
|
||||||
|
introDesc.setWrapText(true);
|
||||||
|
introDesc.setMaxWidth(470);
|
||||||
|
|
||||||
|
var img = new FontIcon("mdi2s-script-text");
|
||||||
|
img.setIconSize(80);
|
||||||
|
var text = new VBox(title, introDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
var hbox = new HBox(img, text);
|
||||||
|
hbox.setSpacing(55);
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var v = new VBox(hbox);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
v.setSpacing(10);
|
||||||
|
v.getStyleClass().add("intro");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createBottom() {
|
||||||
|
var title = new Label();
|
||||||
|
title.textProperty().bind(AppI18n.observable("scriptsIntroBottomTitle"));
|
||||||
|
if (OsType.getLocal() != OsType.MACOS) {
|
||||||
|
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||||
|
}
|
||||||
|
AppFont.setSize(title, 7);
|
||||||
|
|
||||||
|
var importDesc = new Label();
|
||||||
|
importDesc.textProperty().bind(AppI18n.observable("scriptsIntroBottomText"));
|
||||||
|
importDesc.setWrapText(true);
|
||||||
|
importDesc.setMaxWidth(470);
|
||||||
|
|
||||||
|
var importButton = new Button(null, new FontIcon("mdi2p-play-circle"));
|
||||||
|
importButton.setDefaultButton(true);
|
||||||
|
importButton.textProperty().bind(AppI18n.observable("scriptsIntroStart"));
|
||||||
|
importButton.setOnAction(event -> {
|
||||||
|
AppCache.update("scriptsIntroCompleted", true);
|
||||||
|
show.set(false);
|
||||||
|
});
|
||||||
|
var importPane = new StackPane(importButton);
|
||||||
|
importPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var fi = new FontIcon("mdi2t-tooltip-edit");
|
||||||
|
fi.setIconSize(80);
|
||||||
|
var img = new StackPane(fi);
|
||||||
|
img.setPrefWidth(100);
|
||||||
|
img.setPrefHeight(150);
|
||||||
|
var text = new VBox(title, importDesc);
|
||||||
|
text.setSpacing(5);
|
||||||
|
text.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
var hbox = new HBox(img, text);
|
||||||
|
hbox.setSpacing(35);
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
var v = new VBox(hbox, importPane);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
v.setSpacing(20);
|
||||||
|
v.getStyleClass().add("intro");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region createSimple() {
|
||||||
|
var intro = createIntro();
|
||||||
|
var introImport = createBottom();
|
||||||
|
var v = new VBox(intro, introImport);
|
||||||
|
v.setSpacing(80);
|
||||||
|
v.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
|
v.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
|
v.setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
var sp = new StackPane(v);
|
||||||
|
sp.setPadding(new Insets(40, 0, 0, 0));
|
||||||
|
sp.setAlignment(Pos.CENTER);
|
||||||
|
sp.setPickOnBounds(false);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ public class StoreSidebarComp extends SimpleComp {
|
||||||
.styleClass("gray")
|
.styleClass("gray")
|
||||||
.styleClass("bar")
|
.styleClass("bar")
|
||||||
.styleClass("filler-bar")
|
.styleClass("filler-bar")
|
||||||
|
.minHeight(10)
|
||||||
.vgrow()));
|
.vgrow()));
|
||||||
sideBar.apply(struc -> struc.get().setFillWidth(true));
|
sideBar.apply(struc -> struc.get().setFillWidth(true));
|
||||||
sideBar.styleClass("sidebar");
|
sideBar.styleClass("sidebar");
|
||||||
|
|
|
@ -126,7 +126,7 @@ public class StoreViewState {
|
||||||
activeCategory.addListener((observable, oldValue, newValue) -> {
|
activeCategory.addListener((observable, oldValue, newValue) -> {
|
||||||
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
||||||
});
|
});
|
||||||
var selected = AppCache.get("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
var selected = AppCache.getNonNull("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
||||||
activeCategory.setValue(categories.getList().stream()
|
activeCategory.setValue(categories.getList().stream()
|
||||||
.filter(storeCategoryWrapper ->
|
.filter(storeCategoryWrapper ->
|
||||||
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
||||||
|
|
|
@ -4,23 +4,20 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.JsonConfigHelper;
|
import io.xpipe.app.util.JsonConfigHelper;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class AppCache {
|
public class AppCache {
|
||||||
|
|
||||||
public static <T> Optional<T> getIfPresent(String key, Class<T> type) {
|
@Getter
|
||||||
return Optional.ofNullable(get(key, type, () -> null));
|
@Setter
|
||||||
}
|
private static Path basePath;
|
||||||
|
|
||||||
private static Path getBasePath() {
|
|
||||||
return AppProperties.get().getDataDir().resolve("cache");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path getPath(String key) {
|
private static Path getPath(String key) {
|
||||||
var name = key + ".cache";
|
var name = key + ".cache";
|
||||||
|
@ -47,7 +44,33 @@ public class AppCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T get(String key, Class<?> type, Supplier<T> notPresent) {
|
public static <T> T getNonNull(String key, Class<?> type, Supplier<T> notPresent) {
|
||||||
|
var path = getPath(key);
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
try {
|
||||||
|
var tree = JsonConfigHelper.readRaw(path);
|
||||||
|
if (tree.isMissingNode() || tree.isNull()) {
|
||||||
|
FileUtils.deleteQuietly(path.toFile());
|
||||||
|
return notPresent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = (T) JacksonMapper.getDefault().treeToValue(tree, type);
|
||||||
|
if (r == null || !type.isAssignableFrom(r.getClass())) {
|
||||||
|
FileUtils.deleteQuietly(path.toFile());
|
||||||
|
return notPresent.get();
|
||||||
|
} else {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
|
FileUtils.deleteQuietly(path.toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notPresent != null ? notPresent.get() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T getNullable(String key, Class<?> type, Supplier<T> notPresent) {
|
||||||
var path = getPath(key);
|
var path = getPath(key);
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +88,25 @@ public class AppCache {
|
||||||
return notPresent != null ? notPresent.get() : null;
|
return notPresent != null ? notPresent.get() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getBoolean(String key, boolean notPresent) {
|
||||||
|
var path = getPath(key);
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
try {
|
||||||
|
var tree = JsonConfigHelper.readRaw(path);
|
||||||
|
if (!tree.isBoolean()) {
|
||||||
|
FileUtils.deleteQuietly(path.toFile());
|
||||||
|
return notPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree.asBoolean();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
|
FileUtils.deleteQuietly(path.toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notPresent;
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> void update(String key, T val) {
|
public static <T> void update(String key, T val) {
|
||||||
var path = getPath(key);
|
var path = getPath(key);
|
||||||
|
|
||||||
|
@ -79,12 +121,4 @@ public class AppCache {
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T getValue(String key, Class<?> type, Supplier<T> notPresent) {
|
|
||||||
return get(key, type, notPresent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void updateValue(String key, T val) {
|
|
||||||
update(key, val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ public class AppExtensionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAllExtensions() {
|
private void loadAllExtensions() {
|
||||||
for (var ext : List.of("jdbc", "proc", "uacc")) {
|
for (var ext : List.of("proc", "uacc")) {
|
||||||
var extension = findAndParseExtension(ext, baseLayer)
|
var extension = findAndParseExtension(ext, baseLayer)
|
||||||
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
|
.orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext));
|
||||||
loadedExtensions.add(extension);
|
loadedExtensions.add(extension);
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class AppGreetings {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showIfNeeded() {
|
public static void showIfNeeded() {
|
||||||
boolean set = AppCache.get("legalAccepted", Boolean.class, () -> false);
|
boolean set = AppCache.getBoolean("legalAccepted", false);
|
||||||
if (set || AppProperties.get().isDevelopmentEnvironment()) {
|
if (set || AppProperties.get().isDevelopmentEnvironment()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class AppLayoutModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
var state = AppCache.get("layoutState", SavedState.class, () -> new SavedState(260, 300));
|
var state = AppCache.getNonNull("layoutState", SavedState.class, () -> new SavedState(260, 300));
|
||||||
INSTANCE = new AppLayoutModel(state);
|
INSTANCE = new AppLayoutModel(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,8 @@ public class AppProperties {
|
||||||
boolean locatorVersionCheck;
|
boolean locatorVersionCheck;
|
||||||
boolean isTest;
|
boolean isTest;
|
||||||
boolean autoAcceptEula;
|
boolean autoAcceptEula;
|
||||||
|
UUID uuid;
|
||||||
|
boolean initialLaunch;
|
||||||
|
|
||||||
public AppProperties() {
|
public AppProperties() {
|
||||||
var appDir = Path.of(System.getProperty("user.dir")).resolve("app");
|
var appDir = Path.of(System.getProperty("user.dir")).resolve("app");
|
||||||
|
@ -113,6 +115,15 @@ public class AppProperties {
|
||||||
autoAcceptEula = Optional.ofNullable(System.getProperty("io.xpipe.app.acceptEula"))
|
autoAcceptEula = Optional.ofNullable(System.getProperty("io.xpipe.app.acceptEula"))
|
||||||
.map(Boolean::parseBoolean)
|
.map(Boolean::parseBoolean)
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
|
AppCache.setBasePath(dataDir.resolve("cache"));
|
||||||
|
UUID id = AppCache.getNonNull("uuid", UUID.class, null);
|
||||||
|
if (id == null) {
|
||||||
|
uuid = UUID.randomUUID();
|
||||||
|
AppCache.update("uuid", uuid);
|
||||||
|
} else {
|
||||||
|
uuid = id;
|
||||||
|
}
|
||||||
|
initialLaunch = AppCache.getNonNull("lastBuild", String.class, () -> null) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isJUnitTest() {
|
private static boolean isJUnitTest() {
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package io.xpipe.app.core;
|
|
||||||
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.Value;
|
|
||||||
import lombok.experimental.NonFinal;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
public class AppState {
|
|
||||||
|
|
||||||
private static AppState INSTANCE;
|
|
||||||
|
|
||||||
UUID userId;
|
|
||||||
boolean initialLaunch;
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
@Setter
|
|
||||||
String userName;
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
@Setter
|
|
||||||
String userEmail;
|
|
||||||
|
|
||||||
public AppState() {
|
|
||||||
UUID id = AppCache.get("userId", UUID.class, null);
|
|
||||||
if (id == null) {
|
|
||||||
initialLaunch = AppCache.getIfPresent("lastBuild", String.class).isEmpty();
|
|
||||||
userId = UUID.randomUUID();
|
|
||||||
AppCache.update("userId", userId);
|
|
||||||
} else {
|
|
||||||
userId = id;
|
|
||||||
initialLaunch = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
if (INSTANCE != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTANCE = new AppState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AppState get() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ import java.util.*;
|
||||||
public class AppStyle {
|
public class AppStyle {
|
||||||
|
|
||||||
private static final Map<Path, String> STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
private static final Map<Path, String> STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
||||||
|
private static final Map<AppTheme.Theme, String> THEME_SPECIFIC_STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
||||||
private static final List<Scene> scenes = new ArrayList<>();
|
private static final List<Scene> scenes = new ArrayList<>();
|
||||||
private static String FONT_CONTENTS = "";
|
private static String FONT_CONTENTS = "";
|
||||||
|
|
||||||
|
@ -33,6 +34,9 @@ public class AppStyle {
|
||||||
AppPrefs.get().useSystemFont().addListener((c, o, n) -> {
|
AppPrefs.get().useSystemFont().addListener((c, o, n) -> {
|
||||||
changeFontUsage(n);
|
changeFontUsage(n);
|
||||||
});
|
});
|
||||||
|
AppPrefs.get().theme.addListener((c, o, n) -> {
|
||||||
|
changeTheme(n);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +77,19 @@ public class AppStyle {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppResources.with(AppResources.XPIPE_MODULE, "theme", path -> {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AppTheme.Theme theme : AppTheme.Theme.ALL) {
|
||||||
|
var file = path.resolve(theme.getId() + ".css");
|
||||||
|
var bytes = Files.readAllBytes(file);
|
||||||
|
var s = "data:text/css;base64," + Base64.getEncoder().encodeToString(bytes);
|
||||||
|
THEME_SPECIFIC_STYLESHEET_CONTENTS.put(theme, s);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void changeFontUsage(boolean use) {
|
private static void changeFontUsage(boolean use) {
|
||||||
|
@ -87,8 +104,16 @@ public class AppStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void changeTheme(AppTheme.Theme theme) {
|
||||||
|
scenes.forEach(scene -> {
|
||||||
|
scene.getStylesheets().removeAll(THEME_SPECIFIC_STYLESHEET_CONTENTS.values());
|
||||||
|
scene.getStylesheets().add(THEME_SPECIFIC_STYLESHEET_CONTENTS.get(theme));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static void reloadStylesheets(Scene scene) {
|
public static void reloadStylesheets(Scene scene) {
|
||||||
STYLESHEET_CONTENTS.clear();
|
STYLESHEET_CONTENTS.clear();
|
||||||
|
THEME_SPECIFIC_STYLESHEET_CONTENTS.clear();
|
||||||
FONT_CONTENTS = "";
|
FONT_CONTENTS = "";
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
@ -107,7 +132,7 @@ public class AppStyle {
|
||||||
if (AppPrefs.get() != null) {
|
if (AppPrefs.get() != null) {
|
||||||
var t = AppPrefs.get().theme.get();
|
var t = AppPrefs.get().theme.get();
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
scene.getStylesheets().addAll(t.getAdditionalStylesheets());
|
scene.getStylesheets().add(THEME_SPECIFIC_STYLESHEET_CONTENTS.get(t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TrackEvent.debug("Added stylesheets for scene");
|
TrackEvent.debug("Added stylesheets for scene");
|
||||||
|
|
|
@ -97,7 +97,10 @@ public class AppTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (AppPrefs.get().theme.getValue() == null) {
|
var lastSystemDark = AppCache.getBoolean("lastTheme", false);
|
||||||
|
var nowDark = Platform.getPreferences().getColorScheme() == ColorScheme.DARK;
|
||||||
|
AppCache.update("lastTheme", nowDark);
|
||||||
|
if (AppPrefs.get().theme.getValue() == null || lastSystemDark != nowDark) {
|
||||||
setDefault();
|
setDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +240,7 @@ public class AppTheme {
|
||||||
public static final Theme CUPERTINO_LIGHT = new Theme("cupertinoLight", "cupertino", new CupertinoLight());
|
public static final Theme CUPERTINO_LIGHT = new Theme("cupertinoLight", "cupertino", new CupertinoLight());
|
||||||
public static final Theme CUPERTINO_DARK = new Theme("cupertinoDark", "cupertino", new CupertinoDark());
|
public static final Theme CUPERTINO_DARK = new Theme("cupertinoDark", "cupertino", new CupertinoDark());
|
||||||
public static final Theme DRACULA = new Theme("dracula", "dracula", new Dracula());
|
public static final Theme DRACULA = new Theme("dracula", "dracula", new Dracula());
|
||||||
public static final Theme MOCHA = new DerivedTheme("mocha", "primer", "Mocha", new PrimerDark());
|
public static final Theme MOCHA = new DerivedTheme("mocha", "mocha", "Mocha", new PrimerDark());
|
||||||
|
|
||||||
// Adjust this to create your own theme
|
// Adjust this to create your own theme
|
||||||
public static final Theme CUSTOM = new DerivedTheme("custom", "primer", "Custom", new PrimerDark());
|
public static final Theme CUSTOM = new DerivedTheme("custom", "primer", "Custom", new PrimerDark());
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.core.check;
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.AppState;
|
|
||||||
import io.xpipe.app.core.AppStyle;
|
import io.xpipe.app.core.AppStyle;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
@ -35,7 +34,7 @@ public class AppAvCheck {
|
||||||
|
|
||||||
public static void check() throws Throwable {
|
public static void check() throws Throwable {
|
||||||
// Only show this on first launch on windows
|
// Only show this on first launch on windows
|
||||||
if (OsType.getLocal() != OsType.WINDOWS || !AppState.get().isInitialLaunch()) {
|
if (OsType.getLocal() != OsType.WINDOWS || !AppProperties.get().isInitialLaunch()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
public class AppJavaOptionsCheck {
|
public class AppJavaOptionsCheck {
|
||||||
|
|
||||||
public static void check() {
|
public static void check() {
|
||||||
if (AppCache.get("javaOptionsWarningShown", Boolean.class, () -> false)) {
|
if (AppCache.getBoolean("javaOptionsWarningShown", false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,12 +105,17 @@ public abstract class OperationMode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle any startup uncaught errors
|
||||||
|
if (OperationMode.isInStartup() && thread.threadId() == 1) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
OperationMode.halt(1);
|
||||||
|
}
|
||||||
|
|
||||||
ErrorEvent.fromThrowable(ex).unhandled(true).build().handle();
|
ErrorEvent.fromThrowable(ex).unhandled(true).build().handle();
|
||||||
});
|
});
|
||||||
|
|
||||||
TrackEvent.info("Initial setup");
|
TrackEvent.info("Initial setup");
|
||||||
AppProperties.init();
|
AppProperties.init();
|
||||||
AppState.init();
|
|
||||||
XPipeSession.init(AppProperties.get().getBuildUuid());
|
XPipeSession.init(AppProperties.get().getBuildUuid());
|
||||||
AppUserDirectoryCheck.check();
|
AppUserDirectoryCheck.check();
|
||||||
AppTempCheck.check();
|
AppTempCheck.check();
|
||||||
|
|
|
@ -56,7 +56,7 @@ public abstract class PlatformMode extends OperationMode {
|
||||||
|
|
||||||
// If we downloaded an update, and decided to no longer automatically update, don't remind us!
|
// If we downloaded an update, and decided to no longer automatically update, don't remind us!
|
||||||
// You can still update manually in the about tab
|
// You can still update manually in the about tab
|
||||||
if (AppPrefs.get().automaticallyUpdate().get()) {
|
if (AppPrefs.get().automaticallyUpdate().get() || AppPrefs.get().checkForSecurityUpdates().get()) {
|
||||||
UpdateAvailableAlert.showIfNeeded();
|
UpdateAvailableAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,7 @@ public class AppMainWindow {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowState state = AppCache.get("windowState", WindowState.class, () -> null);
|
WindowState state = AppCache.getNonNull("windowState", WindowState.class, () -> null);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.process.ShellStoreState;
|
import io.xpipe.core.process.ShellStoreState;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.NetworkTunnelStore;
|
import io.xpipe.core.store.NetworkTunnelStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.store.StatefulDataStore;
|
import io.xpipe.core.store.StatefulDataStore;
|
||||||
import io.xpipe.core.util.JacksonizedValue;
|
import io.xpipe.core.util.JacksonizedValue;
|
||||||
|
|
||||||
|
@ -19,18 +18,22 @@ public class LocalStore extends JacksonizedValue
|
||||||
return ShellStoreState.class;
|
return ShellStoreState.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public ShellControl control(ShellControl parent) {
|
||||||
public ShellControl parentControl() {
|
return parent;
|
||||||
var pc = ProcessControlProvider.get().createLocalProcessControl(true);
|
|
||||||
pc.withSourceStore(this);
|
|
||||||
pc.withShellStateInit(this);
|
|
||||||
pc.withShellStateFail(this);
|
|
||||||
return pc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShellControl control(ShellControl parent) {
|
public ShellControlFunction shellFunction() {
|
||||||
return parent;
|
return new ShellControlFunction() {
|
||||||
|
@Override
|
||||||
|
public ShellControl control() throws Exception {
|
||||||
|
var pc = ProcessControlProvider.get().createLocalProcessControl(true);
|
||||||
|
pc.withSourceStore(LocalStore.this);
|
||||||
|
pc.withShellStateInit(LocalStore.this);
|
||||||
|
pc.withShellStateFail(LocalStore.this);
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,5 +6,5 @@ import javafx.beans.property.Property;
|
||||||
|
|
||||||
public interface PrefsHandler {
|
public interface PrefsHandler {
|
||||||
|
|
||||||
<T> void addSetting(String id, Class<T> c, Property<T> property, Comp<?> comp);
|
<T> void addSetting(String id, Class<T> c, Property<T> property, Comp<?> comp, boolean requiresRestart);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.ext;
|
||||||
|
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
|
||||||
import io.xpipe.core.util.ModuleLayerLoader;
|
import io.xpipe.core.util.ModuleLayerLoader;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -21,31 +20,34 @@ public abstract class ScanProvider {
|
||||||
return ALL;
|
return ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception {
|
public ScanOpportunity create(DataStoreEntry entry, ShellControl sc) throws Exception {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void scan(DataStoreEntry entry, ShellControl sc) throws Throwable;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class ScanOperation {
|
public class ScanOpportunity {
|
||||||
String nameKey;
|
String nameKey;
|
||||||
boolean disabled;
|
boolean disabled;
|
||||||
boolean defaultSelected;
|
boolean defaultSelected;
|
||||||
FailableRunnable<Throwable> scanner;
|
|
||||||
String licenseFeatureId;
|
String licenseFeatureId;
|
||||||
|
|
||||||
public ScanOperation(
|
public ScanOpportunity(String nameKey, boolean disabled, boolean defaultSelected) {
|
||||||
String nameKey, boolean disabled, boolean defaultSelected, FailableRunnable<Throwable> scanner) {
|
|
||||||
this.nameKey = nameKey;
|
this.nameKey = nameKey;
|
||||||
this.disabled = disabled;
|
this.disabled = disabled;
|
||||||
this.defaultSelected = defaultSelected;
|
this.defaultSelected = defaultSelected;
|
||||||
this.scanner = scanner;
|
|
||||||
this.licenseFeatureId = null;
|
this.licenseFeatureId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLicensedFeatureId() {
|
public String getLicensedFeatureId() {
|
||||||
return licenseFeatureId;
|
return licenseFeatureId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ScanProvider getProvider() {
|
||||||
|
return ScanProvider.this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Loader implements ModuleLayerLoader {
|
public static class Loader implements ModuleLayerLoader {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
|
||||||
|
public interface ShellControlFunction {
|
||||||
|
|
||||||
|
ShellControl control() throws Exception;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
|
||||||
|
public interface ShellControlParentStoreFunction extends ShellControlFunction {
|
||||||
|
|
||||||
|
default ShellControl control() throws Exception {
|
||||||
|
return control(getParentStore().standaloneControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellControl control(ShellControl parent) throws Exception;
|
||||||
|
|
||||||
|
ShellStore getParentStore();
|
||||||
|
}
|
65
app/src/main/java/io/xpipe/app/ext/ShellSession.java
Normal file
65
app/src/main/java/io/xpipe/app/ext/ShellSession.java
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
import io.xpipe.core.store.Session;
|
||||||
|
import io.xpipe.core.store.SessionListener;
|
||||||
|
import io.xpipe.core.util.FailableSupplier;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ShellSession extends Session {
|
||||||
|
|
||||||
|
private final FailableSupplier<ShellControl> supplier;
|
||||||
|
private final ShellControl shellControl;
|
||||||
|
|
||||||
|
public ShellSession(SessionListener listener, FailableSupplier<ShellControl> supplier) throws Exception {
|
||||||
|
super(listener);
|
||||||
|
this.supplier = supplier;
|
||||||
|
this.shellControl = createControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws Exception {
|
||||||
|
if (shellControl.isRunning()) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
shellControl.start();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
stop();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShellControl createControl() throws Exception {
|
||||||
|
var pc = supplier.get();
|
||||||
|
pc.onStartupFail(shellControl -> {
|
||||||
|
listener.onStateChange(false);
|
||||||
|
});
|
||||||
|
pc.onInit(shellControl -> {
|
||||||
|
listener.onStateChange(true);
|
||||||
|
});
|
||||||
|
pc.onKill(() -> {
|
||||||
|
listener.onStateChange(false);
|
||||||
|
});
|
||||||
|
// Listen for parent exit as onExit is called before exit is completed
|
||||||
|
// In case it is stuck, we would not get the right status otherwise
|
||||||
|
pc.getParentControl().ifPresent(p -> {
|
||||||
|
p.onExit(shellControl -> {
|
||||||
|
listener.onStateChange(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return shellControl.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws Exception {
|
||||||
|
shellControl.close();
|
||||||
|
}
|
||||||
|
}
|
74
app/src/main/java/io/xpipe/app/ext/ShellStore.java
Normal file
74
app/src/main/java/io/xpipe/app/ext/ShellStore.java
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
import io.xpipe.core.store.*;
|
||||||
|
|
||||||
|
public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore, SingletonSessionStore<ShellSession> {
|
||||||
|
|
||||||
|
default ShellControl getOrStartSession() throws Exception {
|
||||||
|
var session = getSession();
|
||||||
|
if (session != null) {
|
||||||
|
session.getShellControl().refreshRunningState();
|
||||||
|
if (!session.isRunning()) {
|
||||||
|
stopSessionIfNeeded();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
session.getShellControl().command(" echo xpipetest").execute();
|
||||||
|
return session.getShellControl();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).expected().omit().handle();
|
||||||
|
stopSessionIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startSessionIfNeeded();
|
||||||
|
return new StubShellControl(getSession().getShellControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ShellSession newSession() throws Exception {
|
||||||
|
var func = shellFunction();
|
||||||
|
var c = func.control();
|
||||||
|
if (!isInStorage()) {
|
||||||
|
c.withoutLicenseCheck();
|
||||||
|
}
|
||||||
|
return new ShellSession(this, () -> c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Class<?> getSessionClass() {
|
||||||
|
return ShellSession.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default FileSystem createFileSystem() throws Exception {
|
||||||
|
var func = shellFunction();
|
||||||
|
return new ConnectionFileSystem(func.control());
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellControlFunction shellFunction();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void validate() throws Exception {
|
||||||
|
try (var sc = tempControl().start()) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
default ShellControl standaloneControl() throws Exception {
|
||||||
|
return shellFunction().control();
|
||||||
|
}
|
||||||
|
|
||||||
|
default ShellControl tempControl() throws Exception {
|
||||||
|
if (isSessionRunning()) {
|
||||||
|
return getOrStartSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
var func = shellFunction();
|
||||||
|
if (!(func instanceof ShellControlParentStoreFunction p)) {
|
||||||
|
return func.control();
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.control(p.getParentStore().getOrStartSession());
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/java/io/xpipe/app/ext/StubShellControl.java
Normal file
13
app/src/main/java/io/xpipe/app/ext/StubShellControl.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
|
||||||
|
public class StubShellControl extends WrapperShellControl {
|
||||||
|
|
||||||
|
public StubShellControl(ShellControl parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {}
|
||||||
|
}
|
338
app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java
Normal file
338
app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
package io.xpipe.app.ext;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.*;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.store.FilePath;
|
||||||
|
import io.xpipe.core.util.FailableConsumer;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class WrapperShellControl implements ShellControl {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
protected final ShellControl parent;
|
||||||
|
|
||||||
|
public WrapperShellControl(ShellControl parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ShellControl> getParentControl() {
|
||||||
|
return parent.getParentControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellTtyState getTtyState() {
|
||||||
|
return parent.getTtyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonInteractive() {
|
||||||
|
parent.setNonInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInteractive() {
|
||||||
|
return parent.isInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElevationHandler getElevationHandler() {
|
||||||
|
return parent.getElevationHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setElevationHandler(ElevationHandler ref) {
|
||||||
|
parent.setElevationHandler(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UUID> getExitUuids() {
|
||||||
|
return parent.getExitUuids();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWorkingDirectory(WorkingDirectoryFunction workingDirectory) {
|
||||||
|
parent.setWorkingDirectory(workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DataStore> getSourceStore() {
|
||||||
|
return parent.getSourceStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withSourceStore(DataStore store) {
|
||||||
|
return parent.withSourceStore(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ShellInitCommand> getInitCommands() {
|
||||||
|
return parent.getInitCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParentSystemAccess getParentSystemAccess() {
|
||||||
|
return parent.getParentSystemAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParentSystemAccess(ParentSystemAccess access) {
|
||||||
|
parent.setParentSystemAccess(access);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParentSystemAccess getLocalSystemAccess() {
|
||||||
|
return parent.getLocalSystemAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return parent.isLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl getMachineRootSession() {
|
||||||
|
return parent.getMachineRootSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withoutLicenseCheck() {
|
||||||
|
return parent.withoutLicenseCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOsName() {
|
||||||
|
return parent.getOsName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLicenseCheck() {
|
||||||
|
return parent.isLicenseCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReentrantLock getLock() {
|
||||||
|
return parent.getLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellDialect getOriginalShellDialect() {
|
||||||
|
return parent.getOriginalShellDialect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOriginalShellDialect(ShellDialect dialect) {
|
||||||
|
parent.setOriginalShellDialect(dialect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl onInit(FailableConsumer<ShellControl, Exception> pc) {
|
||||||
|
return parent.onInit(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl onExit(Consumer<ShellControl> pc) {
|
||||||
|
return parent.onExit(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl onKill(Runnable pc) {
|
||||||
|
return parent.onKill(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl onStartupFail(Consumer<Throwable> t) {
|
||||||
|
return parent.onStartupFail(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getUuid() {
|
||||||
|
return parent.getUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withExceptionConverter(ExceptionConverter converter) {
|
||||||
|
return parent.withExceptionConverter(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetData() {
|
||||||
|
parent.resetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String prepareTerminalOpen(TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory)
|
||||||
|
throws Exception {
|
||||||
|
return parent.prepareTerminalOpen(config, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshRunningState() {
|
||||||
|
parent.refreshRunningState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeStdin() throws IOException {
|
||||||
|
parent.closeStdin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStdinClosed() {
|
||||||
|
return parent.isStdinClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return parent.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellDialect getShellDialect() {
|
||||||
|
return parent.getShellDialect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeLine(String line) throws IOException {
|
||||||
|
parent.writeLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeLine(String line, boolean log) throws IOException {
|
||||||
|
parent.writeLine(line, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
parent.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
parent.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() throws Exception {
|
||||||
|
parent.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void kill() {
|
||||||
|
parent.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl start() throws Exception {
|
||||||
|
return parent.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getStdout() {
|
||||||
|
return parent.getStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getStdin() {
|
||||||
|
return parent.getStdin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getStderr() {
|
||||||
|
return parent.getStderr();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Charset getCharset() {
|
||||||
|
return parent.getCharset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withErrorFormatter(Function<String, String> formatter) {
|
||||||
|
return parent.withErrorFormatter(formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String prepareIntermediateTerminalOpen(
|
||||||
|
TerminalInitFunction content, TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory)
|
||||||
|
throws Exception {
|
||||||
|
return parent.prepareIntermediateTerminalOpen(content, config, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilePath getSystemTemporaryDirectory() {
|
||||||
|
return parent.getSystemTemporaryDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withSecurityPolicy(ShellSecurityPolicy policy) {
|
||||||
|
return parent.withSecurityPolicy(policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellSecurityPolicy getEffectiveSecurityPolicy() {
|
||||||
|
return parent.getEffectiveSecurityPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildElevatedCommand(CommandConfiguration input, String prefix, UUID requestId, CountDown countDown)
|
||||||
|
throws Exception {
|
||||||
|
return parent.buildElevatedCommand(input, prefix, requestId, countDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restart() throws Exception {
|
||||||
|
parent.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OsType.Any getOsType() {
|
||||||
|
return parent.getOsType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl elevated(ElevationFunction elevationFunction) {
|
||||||
|
return parent.elevated(elevationFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl withInitSnippet(ShellInitCommand snippet) {
|
||||||
|
return parent.withInitSnippet(snippet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl subShell(ShellOpenFunction command, ShellOpenFunction terminalCommand) {
|
||||||
|
return parent.subShell(command, terminalCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellControl singularSubShell(ShellOpenFunction command) {
|
||||||
|
return parent.singularSubShell(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cd(String directory) throws Exception {
|
||||||
|
parent.cd(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandControl command(CommandBuilder builder) {
|
||||||
|
return parent.command(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exitAndWait() throws IOException {
|
||||||
|
parent.exitAndWait();
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,8 +97,8 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||||
return apply(struc -> struc.get().setMinWidth(width));
|
return apply(struc -> struc.get().setMinWidth(width));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<S> minHeight(double width) {
|
public Comp<S> minHeight(double height) {
|
||||||
return apply(struc -> struc.get().setMinHeight(width));
|
return apply(struc -> struc.get().setMinHeight(height));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comp<S> maxWidth(int width) {
|
public Comp<S> maxWidth(int width) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.store.*;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.LocalStore;
|
import io.xpipe.app.ext.LocalStore;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
@ -12,7 +13,6 @@ import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
|
|
@ -11,7 +11,6 @@ public class GuiErrorHandlerBase {
|
||||||
try {
|
try {
|
||||||
PlatformState.initPlatformOrThrow();
|
PlatformState.initPlatformOrThrow();
|
||||||
AppProperties.init();
|
AppProperties.init();
|
||||||
AppState.init();
|
|
||||||
AppExtensionManager.init(false);
|
AppExtensionManager.init(false);
|
||||||
AppI18n.init();
|
AppI18n.init();
|
||||||
AppStyle.init();
|
AppStyle.init();
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.issue;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppLogs;
|
import io.xpipe.app.core.AppLogs;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.AppState;
|
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
|
@ -145,6 +144,11 @@ public class SentryErrorHandler implements ErrorHandler {
|
||||||
AppPrefs.get() != null
|
AppPrefs.get() != null
|
||||||
? AppPrefs.get().automaticallyUpdate().getValue().toString()
|
? AppPrefs.get().automaticallyUpdate().getValue().toString()
|
||||||
: "unknown");
|
: "unknown");
|
||||||
|
s.setTag(
|
||||||
|
"securityUpdatesEnabled",
|
||||||
|
AppPrefs.get() != null
|
||||||
|
? AppPrefs.get().checkForSecurityUpdates().getValue().toString()
|
||||||
|
: "unknown");
|
||||||
s.setTag("initError", String.valueOf(OperationMode.isInStartup()));
|
s.setTag("initError", String.valueOf(OperationMode.isInStartup()));
|
||||||
s.setTag(
|
s.setTag(
|
||||||
"developerMode",
|
"developerMode",
|
||||||
|
@ -177,11 +181,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = new User();
|
var user = new User();
|
||||||
user.setId(AppState.get().getUserId().toString());
|
user.setId(AppProperties.get().getUuid().toString());
|
||||||
if (ee.isShouldSendDiagnostics()) {
|
|
||||||
user.setEmail(AppState.get().getUserEmail());
|
|
||||||
user.setUsername(AppState.get().getUserName());
|
|
||||||
}
|
|
||||||
s.setUser(user);
|
s.setUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +189,6 @@ public class SentryErrorHandler implements ErrorHandler {
|
||||||
// Assume that this object is wrapped by a synchronous error handler
|
// Assume that this object is wrapped by a synchronous error handler
|
||||||
if (!init) {
|
if (!init) {
|
||||||
AppProperties.init();
|
AppProperties.init();
|
||||||
AppState.init();
|
|
||||||
if (AppProperties.get().getSentryUrl() != null) {
|
if (AppProperties.get().getSentryUrl() != null) {
|
||||||
Sentry.init(options -> {
|
Sentry.init(options -> {
|
||||||
options.setDsn(AppProperties.get().getSentryUrl());
|
options.setDsn(AppProperties.get().getSentryUrl());
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.issue;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
@ -40,7 +41,6 @@ public class TerminalErrorHandler extends GuiErrorHandlerBase implements ErrorHa
|
||||||
private void handleGui(ErrorEvent event) {
|
private void handleGui(ErrorEvent event) {
|
||||||
try {
|
try {
|
||||||
AppProperties.init();
|
AppProperties.init();
|
||||||
AppState.init();
|
|
||||||
AppExtensionManager.init(false);
|
AppExtensionManager.init(false);
|
||||||
AppI18n.init();
|
AppI18n.init();
|
||||||
AppStyle.init();
|
AppStyle.init();
|
||||||
|
@ -74,7 +74,7 @@ public class TerminalErrorHandler extends GuiErrorHandlerBase implements ErrorHa
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var rel = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
|
var rel = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck(false, !AppPrefs.get().automaticallyUpdate().get());
|
||||||
if (rel != null && rel.isUpdate()) {
|
if (rel != null && rel.isUpdate()) {
|
||||||
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
alert.setAlertType(Alert.AlertType.INFORMATION);
|
alert.setAlertType(Alert.AlertType.INFORMATION);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.prefs;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.ext.PrefsHandler;
|
import io.xpipe.app.ext.PrefsHandler;
|
||||||
import io.xpipe.app.ext.PrefsProvider;
|
import io.xpipe.app.ext.PrefsProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
|
@ -22,8 +23,11 @@ import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.beans.value.ObservableStringValue;
|
import javafx.beans.value.ObservableStringValue;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
import lombok.experimental.NonFinal;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -36,100 +40,104 @@ public class AppPrefs {
|
||||||
AppProperties.get() != null ? AppProperties.get().getDataDir().resolve("storage") : null;
|
AppProperties.get() != null ? AppProperties.get().getDataDir().resolve("storage") : null;
|
||||||
private static final String DEVELOPER_MODE_PROP = "io.xpipe.app.developerMode";
|
private static final String DEVELOPER_MODE_PROP = "io.xpipe.app.developerMode";
|
||||||
private static AppPrefs INSTANCE;
|
private static AppPrefs INSTANCE;
|
||||||
private final List<Mapping<?>> mapping = new ArrayList<>();
|
private final List<Mapping> mapping = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final BooleanProperty requiresRestart = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
final BooleanProperty dontAllowTerminalRestart =
|
final BooleanProperty dontAllowTerminalRestart =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAllowTerminalRestart", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "dontAllowTerminalRestart", Boolean.class, false);
|
||||||
final BooleanProperty enableHttpApi =
|
final BooleanProperty enableHttpApi =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class, false);
|
||||||
final BooleanProperty dontAutomaticallyStartVmSshServer =
|
final BooleanProperty dontAutomaticallyStartVmSshServer =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class, false);
|
||||||
final BooleanProperty dontAcceptNewHostKeys =
|
final BooleanProperty dontAcceptNewHostKeys =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class, false);
|
||||||
public final BooleanProperty performanceMode = map(new SimpleBooleanProperty(), "performanceMode", Boolean.class);
|
public final BooleanProperty performanceMode = mapLocal(new SimpleBooleanProperty(), "performanceMode", Boolean.class, false);
|
||||||
public final BooleanProperty useBundledTools =
|
public final BooleanProperty useBundledTools =
|
||||||
map(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class, true);
|
||||||
public final ObjectProperty<AppTheme.Theme> theme =
|
public final ObjectProperty<AppTheme.Theme> theme =
|
||||||
map(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class);
|
mapLocal(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class, false);
|
||||||
final BooleanProperty useSystemFont = map(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class);
|
final BooleanProperty useSystemFont = mapLocal(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class, false);
|
||||||
final Property<Integer> uiScale = map(new SimpleObjectProperty<>(null), "uiScale", Integer.class);
|
final Property<Integer> uiScale = mapLocal(new SimpleObjectProperty<>(null), "uiScale", Integer.class, true);
|
||||||
final BooleanProperty saveWindowLocation =
|
final BooleanProperty saveWindowLocation =
|
||||||
map(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class, false);
|
||||||
final ObjectProperty<ExternalTerminalType> terminalType =
|
final ObjectProperty<ExternalTerminalType> terminalType =
|
||||||
map(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class);
|
mapLocal(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class, false);
|
||||||
final ObjectProperty<ExternalRdpClientType> rdpClientType =
|
final ObjectProperty<ExternalRdpClientType> rdpClientType =
|
||||||
map(new SimpleObjectProperty<>(), "rdpClientType", ExternalRdpClientType.class);
|
mapLocal(new SimpleObjectProperty<>(), "rdpClientType", ExternalRdpClientType.class, false);
|
||||||
final DoubleProperty windowOpacity = map(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class);
|
final DoubleProperty windowOpacity = mapLocal(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class, false);
|
||||||
final StringProperty customRdpClientCommand =
|
final StringProperty customRdpClientCommand =
|
||||||
map(new SimpleStringProperty(null), "customRdpClientCommand", String.class);
|
mapLocal(new SimpleStringProperty(null), "customRdpClientCommand", String.class, false);
|
||||||
final StringProperty customTerminalCommand =
|
final StringProperty customTerminalCommand =
|
||||||
map(new SimpleStringProperty(null), "customTerminalCommand", String.class);
|
mapLocal(new SimpleStringProperty(null), "customTerminalCommand", String.class, false);
|
||||||
final BooleanProperty clearTerminalOnInit =
|
final BooleanProperty clearTerminalOnInit =
|
||||||
map(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class, false);
|
||||||
public final BooleanProperty disableCertutilUse =
|
public final BooleanProperty disableCertutilUse =
|
||||||
map(new SimpleBooleanProperty(false), "disableCertutilUse", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "disableCertutilUse", Boolean.class, false);
|
||||||
public final BooleanProperty useLocalFallbackShell =
|
public final BooleanProperty useLocalFallbackShell =
|
||||||
map(new SimpleBooleanProperty(false), "useLocalFallbackShell", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "useLocalFallbackShell", Boolean.class, true);
|
||||||
public final BooleanProperty disableTerminalRemotePasswordPreparation = mapVaultSpecific(
|
public final BooleanProperty disableTerminalRemotePasswordPreparation = mapVaultShared(
|
||||||
new SimpleBooleanProperty(false), "disableTerminalRemotePasswordPreparation", Boolean.class);
|
new SimpleBooleanProperty(false), "disableTerminalRemotePasswordPreparation", Boolean.class, false);
|
||||||
public final Property<Boolean> alwaysConfirmElevation =
|
public final Property<Boolean> alwaysConfirmElevation =
|
||||||
mapVaultSpecific(new SimpleObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class);
|
mapVaultShared(new SimpleObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class, false);
|
||||||
public final BooleanProperty dontCachePasswords =
|
public final BooleanProperty dontCachePasswords =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class, false);
|
||||||
public final BooleanProperty denyTempScriptCreation =
|
public final BooleanProperty denyTempScriptCreation =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class, false);
|
||||||
final Property<ExternalPasswordManager> passwordManager =
|
final Property<ExternalPasswordManager> passwordManager =
|
||||||
mapVaultSpecific(new SimpleObjectProperty<>(), "passwordManager", ExternalPasswordManager.class);
|
mapVaultShared(new SimpleObjectProperty<>(), "passwordManager", ExternalPasswordManager.class, false);
|
||||||
final StringProperty passwordManagerCommand =
|
final StringProperty passwordManagerCommand =
|
||||||
map(new SimpleStringProperty(""), "passwordManagerCommand", String.class);
|
mapLocal(new SimpleStringProperty(""), "passwordManagerCommand", String.class, false);
|
||||||
final ObjectProperty<StartupBehaviour> startupBehaviour =
|
final ObjectProperty<StartupBehaviour> startupBehaviour =
|
||||||
map(new SimpleObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class);
|
mapLocal(new SimpleObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class, true);
|
||||||
public final BooleanProperty enableGitStorage =
|
public final BooleanProperty enableGitStorage =
|
||||||
map(new SimpleBooleanProperty(false), "enableGitStorage", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "enableGitStorage", Boolean.class, true);
|
||||||
final StringProperty storageGitRemote = map(new SimpleStringProperty(""), "storageGitRemote", String.class);
|
final StringProperty storageGitRemote = mapLocal(new SimpleStringProperty(""), "storageGitRemote", String.class, true);
|
||||||
final ObjectProperty<CloseBehaviour> closeBehaviour =
|
final ObjectProperty<CloseBehaviour> closeBehaviour =
|
||||||
map(new SimpleObjectProperty<>(CloseBehaviour.QUIT), "closeBehaviour", CloseBehaviour.class);
|
mapLocal(new SimpleObjectProperty<>(CloseBehaviour.QUIT), "closeBehaviour", CloseBehaviour.class, false);
|
||||||
final ObjectProperty<ExternalEditorType> externalEditor =
|
final ObjectProperty<ExternalEditorType> externalEditor =
|
||||||
map(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class);
|
mapLocal(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class, false);
|
||||||
final StringProperty customEditorCommand = map(new SimpleStringProperty(""), "customEditorCommand", String.class);
|
final StringProperty customEditorCommand = mapLocal(new SimpleStringProperty(""), "customEditorCommand", String.class, false);
|
||||||
final BooleanProperty preferEditorTabs = map(new SimpleBooleanProperty(true), "preferEditorTabs", Boolean.class);
|
|
||||||
final BooleanProperty automaticallyCheckForUpdates =
|
final BooleanProperty automaticallyCheckForUpdates =
|
||||||
map(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class, false);
|
||||||
final BooleanProperty encryptAllVaultData =
|
final BooleanProperty encryptAllVaultData =
|
||||||
mapVaultSpecific(new SimpleBooleanProperty(false), "encryptAllVaultData", Boolean.class);
|
mapVaultShared(new SimpleBooleanProperty(false), "encryptAllVaultData", Boolean.class, true);
|
||||||
final BooleanProperty enableTerminalLogging =
|
final BooleanProperty enableTerminalLogging = map(Mapping.builder()
|
||||||
map(new SimpleBooleanProperty(false), "enableTerminalLogging", Boolean.class);
|
.property(new SimpleBooleanProperty(false)).key("enableTerminalLogging").valueClass(Boolean.class).licenseFeatureId("logging").build());
|
||||||
final BooleanProperty enforceWindowModality =
|
final BooleanProperty enforceWindowModality =
|
||||||
map(new SimpleBooleanProperty(false), "enforceWindowModality", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "enforceWindowModality", Boolean.class, false);
|
||||||
|
final BooleanProperty checkForSecurityUpdates =
|
||||||
|
mapLocal(new SimpleBooleanProperty(true), "checkForSecurityUpdates", Boolean.class, false);
|
||||||
final BooleanProperty condenseConnectionDisplay =
|
final BooleanProperty condenseConnectionDisplay =
|
||||||
map(new SimpleBooleanProperty(false), "condenseConnectionDisplay", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "condenseConnectionDisplay", Boolean.class, false);
|
||||||
final BooleanProperty showChildCategoriesInParentCategory =
|
final BooleanProperty showChildCategoriesInParentCategory =
|
||||||
map(new SimpleBooleanProperty(true), "showChildrenConnectionsInParentCategory", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "showChildrenConnectionsInParentCategory", Boolean.class, false);
|
||||||
final BooleanProperty lockVaultOnHibernation =
|
final BooleanProperty lockVaultOnHibernation =
|
||||||
map(new SimpleBooleanProperty(false), "lockVaultOnHibernation", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "lockVaultOnHibernation", Boolean.class, false);
|
||||||
final BooleanProperty openConnectionSearchWindowOnConnectionCreation =
|
final BooleanProperty openConnectionSearchWindowOnConnectionCreation =
|
||||||
map(new SimpleBooleanProperty(true), "openConnectionSearchWindowOnConnectionCreation", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "openConnectionSearchWindowOnConnectionCreation", Boolean.class, false);
|
||||||
final ObjectProperty<Path> storageDirectory =
|
final ObjectProperty<Path> storageDirectory =
|
||||||
map(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class);
|
mapLocal(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class, true);
|
||||||
final BooleanProperty confirmAllDeletions =
|
final BooleanProperty confirmAllDeletions =
|
||||||
map(new SimpleBooleanProperty(false), "confirmAllDeletions", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "confirmAllDeletions", Boolean.class, false);
|
||||||
final BooleanProperty developerMode = map(new SimpleBooleanProperty(false), "developerMode", Boolean.class);
|
final BooleanProperty developerMode = mapLocal(new SimpleBooleanProperty(false), "developerMode", Boolean.class, true);
|
||||||
final BooleanProperty developerDisableUpdateVersionCheck =
|
final BooleanProperty developerDisableUpdateVersionCheck =
|
||||||
map(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class, false);
|
||||||
private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective =
|
private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective =
|
||||||
bindDeveloperTrue(developerDisableUpdateVersionCheck);
|
bindDeveloperTrue(developerDisableUpdateVersionCheck);
|
||||||
final BooleanProperty developerDisableGuiRestrictions =
|
final BooleanProperty developerDisableGuiRestrictions =
|
||||||
map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class, false);
|
||||||
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
||||||
bindDeveloperTrue(developerDisableGuiRestrictions);
|
bindDeveloperTrue(developerDisableGuiRestrictions);
|
||||||
final BooleanProperty developerForceSshTty =
|
final BooleanProperty developerForceSshTty =
|
||||||
map(new SimpleBooleanProperty(false), "developerForceSshTty", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "developerForceSshTty", Boolean.class, false);
|
||||||
|
|
||||||
final ObjectProperty<SupportedLocale> language =
|
final ObjectProperty<SupportedLocale> language =
|
||||||
map(new SimpleObjectProperty<>(SupportedLocale.getEnglish()), "language", SupportedLocale.class);
|
mapLocal(new SimpleObjectProperty<>(SupportedLocale.getEnglish()), "language", SupportedLocale.class, false);
|
||||||
|
|
||||||
final BooleanProperty requireDoubleClickForConnections =
|
final BooleanProperty requireDoubleClickForConnections =
|
||||||
map(new SimpleBooleanProperty(false), "requireDoubleClickForConnections", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "requireDoubleClickForConnections", Boolean.class, false);
|
||||||
|
|
||||||
public ObservableBooleanValue requireDoubleClickForConnections() {
|
public ObservableBooleanValue requireDoubleClickForConnections() {
|
||||||
return requireDoubleClickForConnections;
|
return requireDoubleClickForConnections;
|
||||||
|
@ -140,12 +148,16 @@ public class AppPrefs {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final StringProperty lockCrypt =
|
private final StringProperty lockCrypt =
|
||||||
mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class);
|
mapVaultShared(new SimpleStringProperty(), "workspaceLock", String.class, true);
|
||||||
|
|
||||||
final StringProperty apiKey =
|
final StringProperty apiKey =
|
||||||
mapVaultSpecific(new SimpleStringProperty(UUID.randomUUID().toString()), "apiKey", String.class);
|
mapVaultShared(new SimpleStringProperty(UUID.randomUUID().toString()), "apiKey", String.class ,true);
|
||||||
final BooleanProperty disableApiAuthentication =
|
final BooleanProperty disableApiAuthentication =
|
||||||
map(new SimpleBooleanProperty(false), "disableApiAuthentication", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(false), "disableApiAuthentication", Boolean.class, false);
|
||||||
|
|
||||||
|
public ObservableBooleanValue checkForSecurityUpdates() {
|
||||||
|
return checkForSecurityUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableBooleanValue enableTerminalLogging() {
|
public ObservableBooleanValue enableTerminalLogging() {
|
||||||
return enableTerminalLogging;
|
return enableTerminalLogging;
|
||||||
|
@ -168,16 +180,16 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final IntegerProperty editorReloadTimeout =
|
private final IntegerProperty editorReloadTimeout =
|
||||||
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
|
mapLocal(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class, false);
|
||||||
private final BooleanProperty confirmDeletions =
|
private final BooleanProperty confirmDeletions =
|
||||||
map(new SimpleBooleanProperty(true), "confirmDeletions", Boolean.class);
|
mapLocal(new SimpleBooleanProperty(true), "confirmDeletions", Boolean.class, false);
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final List<AppPrefsCategory> categories;
|
private final List<AppPrefsCategory> categories;
|
||||||
|
|
||||||
private final AppPrefsStorageHandler globalStorageHandler = new AppPrefsStorageHandler(
|
private final AppPrefsStorageHandler globalStorageHandler = new AppPrefsStorageHandler(
|
||||||
AppProperties.get().getDataDir().resolve("settings").resolve("preferences.json"));
|
AppProperties.get().getDataDir().resolve("settings").resolve("preferences.json"));
|
||||||
private final Map<Mapping<?>, Comp<?>> customEntries = new LinkedHashMap<>();
|
private final Map<Mapping, Comp<?>> customEntries = new LinkedHashMap<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Property<AppPrefsCategory> selectedCategory;
|
private final Property<AppPrefsCategory> selectedCategory;
|
||||||
|
@ -207,7 +219,7 @@ public class AppPrefs {
|
||||||
new DeveloperCategory())
|
new DeveloperCategory())
|
||||||
.filter(appPrefsCategory -> appPrefsCategory.show())
|
.filter(appPrefsCategory -> appPrefsCategory.show())
|
||||||
.toList();
|
.toList();
|
||||||
var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0);
|
var selected = AppCache.getNonNull("selectedPrefsCategory", Integer.class, () -> 0);
|
||||||
if (selected == null) {
|
if (selected == null) {
|
||||||
selected = 0;
|
selected = 0;
|
||||||
}
|
}
|
||||||
|
@ -473,14 +485,20 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> T map(T o, String name, Class<?> clazz) {
|
private <T> T map(Mapping m) {
|
||||||
mapping.add(new Mapping<>(name, (Property<T>) o, (Class<T>) clazz));
|
mapping.add(m);
|
||||||
return o;
|
return (T) m.getProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> T mapVaultSpecific(T o, String name, Class<?> clazz) {
|
private <T> T mapLocal(Property<?> o, String name, Class<?> clazz, boolean requiresRestart) {
|
||||||
mapping.add(new Mapping<>(name, (Property<T>) o, (Class<T>) clazz, true));
|
mapping.add(new Mapping(name, o, clazz, false, requiresRestart));
|
||||||
|
return (T) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T mapVaultShared(T o, String name, Class<?> clazz, boolean requiresRestart) {
|
||||||
|
mapping.add(new Mapping(name, (Property<T>) o, (Class<T>) clazz, true, requiresRestart));
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,7 +516,7 @@ public class AppPrefs {
|
||||||
if (rdpClientType.get() == null) {
|
if (rdpClientType.get() == null) {
|
||||||
rdpClientType.setValue(ExternalRdpClientType.determineDefault());
|
rdpClientType.setValue(ExternalRdpClientType.determineDefault());
|
||||||
}
|
}
|
||||||
if (AppState.get().isInitialLaunch()) {
|
if (AppProperties.get().isInitialLaunch()) {
|
||||||
performanceMode.setValue(XPipeDistributionType.get() == XPipeDistributionType.WEBTOP);
|
performanceMode.setValue(XPipeDistributionType.get() == XPipeDistributionType.WEBTOP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,7 +530,7 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadLocal() {
|
private void loadLocal() {
|
||||||
for (Mapping<?> value : mapping) {
|
for (Mapping value : mapping) {
|
||||||
if (value.isVaultSpecific()) {
|
if (value.isVaultSpecific()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -546,7 +564,7 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSharedRemote() {
|
private void loadSharedRemote() {
|
||||||
for (Mapping<?> value : mapping) {
|
for (Mapping value : mapping) {
|
||||||
if (!value.isVaultSpecific()) {
|
if (!value.isVaultSpecific()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -562,15 +580,19 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T loadValue(AppPrefsStorageHandler handler, Mapping<T> value) {
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T loadValue(AppPrefsStorageHandler handler, Mapping value) {
|
||||||
|
T def = (T) value.getProperty().getValue();
|
||||||
|
Property<T> property = (Property<T>) value.getProperty();
|
||||||
|
Class<T> clazz = (Class<T>) value.getValueClass();
|
||||||
var val = handler.loadObject(
|
var val = handler.loadObject(
|
||||||
value.getKey(), value.getValueClass(), value.getProperty().getValue());
|
value.getKey(), clazz, def);
|
||||||
value.getProperty().setValue(val);
|
property.setValue(val);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
for (Mapping<?> m : mapping) {
|
for (Mapping m : mapping) {
|
||||||
AppPrefsStorageHandler handler = m.isVaultSpecific() ? vaultStorageHandler : globalStorageHandler;
|
AppPrefsStorageHandler handler = m.isVaultSpecific() ? vaultStorageHandler : globalStorageHandler;
|
||||||
// It might be possible that we save while the vault handler is not initialized yet / has no file or
|
// It might be possible that we save while the vault handler is not initialized yet / has no file or
|
||||||
// directory
|
// directory
|
||||||
|
@ -608,26 +630,36 @@ public class AppPrefs {
|
||||||
return ExternalApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
|
return ExternalApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
public Mapping getMapping(Object property) {
|
||||||
public static class Mapping<T> {
|
return mapping.stream().filter(m -> m.property == property).findFirst().orElseThrow();
|
||||||
|
|
||||||
String key;
|
|
||||||
Property<T> property;
|
|
||||||
Class<T> valueClass;
|
|
||||||
boolean vaultSpecific;
|
|
||||||
|
|
||||||
public Mapping(String key, Property<T> property, Class<T> valueClass) {
|
|
||||||
this.key = key;
|
|
||||||
this.property = property;
|
|
||||||
this.valueClass = valueClass;
|
|
||||||
this.vaultSpecific = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mapping(String key, Property<T> property, Class<T> valueClass, boolean vaultSpecific) {
|
@Value
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Mapping {
|
||||||
|
|
||||||
|
String key;
|
||||||
|
Property<?> property;
|
||||||
|
Class<?> valueClass;
|
||||||
|
boolean vaultSpecific;
|
||||||
|
boolean requiresRestart;
|
||||||
|
String licenseFeatureId;
|
||||||
|
|
||||||
|
public Mapping(String key, Property<?> property, Class<?> valueClass, boolean vaultSpecific, boolean requiresRestart) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.valueClass = valueClass;
|
this.valueClass = valueClass;
|
||||||
this.vaultSpecific = vaultSpecific;
|
this.vaultSpecific = vaultSpecific;
|
||||||
|
this.requiresRestart = requiresRestart;
|
||||||
|
this.licenseFeatureId = null;
|
||||||
|
|
||||||
|
this.property.addListener((observable, oldValue, newValue) -> {
|
||||||
|
var running = OperationMode.get() == OperationMode.GUI;
|
||||||
|
if (running && requiresRestart) {
|
||||||
|
AppPrefs.get().requiresRestart.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,8 +667,8 @@ public class AppPrefs {
|
||||||
private class PrefsHandlerImpl implements PrefsHandler {
|
private class PrefsHandlerImpl implements PrefsHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void addSetting(String id, Class<T> c, Property<T> property, Comp<?> comp) {
|
public <T> void addSetting(String id, Class<T> c, Property<T> property, Comp<?> comp, boolean requiresRestart) {
|
||||||
var m = new Mapping<>(id, property, c);
|
var m = new Mapping(id, property, c, false, requiresRestart);
|
||||||
customEntries.put(m, comp);
|
customEntries.put(m, comp);
|
||||||
mapping.add(m);
|
mapping.add(m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ public class AppPrefsComp extends SimpleComp {
|
||||||
sidebar.setMaxWidth(280);
|
sidebar.setMaxWidth(280);
|
||||||
|
|
||||||
var split = new HBox(sidebar, pfxLimit);
|
var split = new HBox(sidebar, pfxLimit);
|
||||||
|
HBox.setMargin(sidebar, new Insets(6));
|
||||||
HBox.setHgrow(pfxLimit, Priority.ALWAYS);
|
HBox.setHgrow(pfxLimit, Priority.ALWAYS);
|
||||||
split.setFillHeight(true);
|
split.setFillHeight(true);
|
||||||
split.getStyleClass().add("prefs");
|
split.getStyleClass().add("prefs");
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
package io.xpipe.app.prefs;
|
package io.xpipe.app.prefs;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class AppPrefsSidebarComp extends SimpleComp {
|
public class AppPrefsSidebarComp extends SimpleComp {
|
||||||
|
|
||||||
|
@ -33,7 +40,17 @@ public class AppPrefsSidebarComp extends SimpleComp {
|
||||||
})
|
})
|
||||||
.grow(true, false);
|
.grow(true, false);
|
||||||
})
|
})
|
||||||
.toList();
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
var restartButton = new ButtonComp(AppI18n.observable("restart"), new FontIcon("mdi2r-restart"), () -> {
|
||||||
|
OperationMode.restart();
|
||||||
|
});
|
||||||
|
restartButton.grow(true, false);
|
||||||
|
restartButton.visible(AppPrefs.get().getRequiresRestart());
|
||||||
|
restartButton.padding(new Insets(6, 10, 6, 6));
|
||||||
|
buttons.add(Comp.vspacer());
|
||||||
|
buttons.add(restartButton);
|
||||||
|
|
||||||
var vbox = new VerticalComp(buttons).styleClass("sidebar");
|
var vbox = new VerticalComp(buttons).styleClass("sidebar");
|
||||||
vbox.apply(struc -> {
|
vbox.apply(struc -> {
|
||||||
AppPrefs.get().getSelectedCategory().subscribe(val -> {
|
AppPrefs.get().getSelectedCategory().subscribe(val -> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class CloseBehaviourAlert {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean set = AppCache.get("closeBehaviourSet", Boolean.class, () -> false);
|
boolean set = AppCache.getBoolean("closeBehaviourSet", false);
|
||||||
if (set) {
|
if (set) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,7 @@ public class EditorCategory extends AppPrefsCategory {
|
||||||
.addComp(new TextFieldComp(prefs.customEditorCommand, true)
|
.addComp(new TextFieldComp(prefs.customEditorCommand, true)
|
||||||
.apply(struc -> struc.get().setPromptText("myeditor $FILE"))
|
.apply(struc -> struc.get().setPromptText("myeditor $FILE"))
|
||||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM)))
|
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM)))
|
||||||
.addComp(terminalTest)
|
.addComp(terminalTest))
|
||||||
.nameAndDescription("preferEditorTabs")
|
|
||||||
.addToggle(prefs.preferEditorTabs))
|
|
||||||
.buildComp();
|
.buildComp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,10 @@ public class LoggingCategory extends AppPrefsCategory {
|
||||||
@Override
|
@Override
|
||||||
protected Comp<?> create() {
|
protected Comp<?> create() {
|
||||||
var prefs = AppPrefs.get();
|
var prefs = AppPrefs.get();
|
||||||
var feature = LicenseProvider.get().getFeature("logging");
|
|
||||||
var supported = feature.isSupported() || feature.isPreviewSupported();
|
|
||||||
var title = AppI18n.observable("sessionLogging").map(s -> s + (supported ? "" : " (Pro)"));
|
|
||||||
return new OptionsBuilder()
|
return new OptionsBuilder()
|
||||||
.addTitle(title)
|
.addTitle("sessionLogging")
|
||||||
.sub(new OptionsBuilder()
|
.sub(new OptionsBuilder()
|
||||||
.nameAndDescription("enableTerminalLogging")
|
.pref(prefs.enableTerminalLogging)
|
||||||
.addToggle(prefs.enableTerminalLogging)
|
.addToggle(prefs.enableTerminalLogging)
|
||||||
.nameAndDescription("terminalLoggingDirectory")
|
.nameAndDescription("terminalLoggingDirectory")
|
||||||
.addComp(new ButtonComp(AppI18n.observable("openSessionLogs"), () -> {
|
.addComp(new ButtonComp(AppI18n.observable("openSessionLogs"), () -> {
|
||||||
|
|
|
@ -15,6 +15,8 @@ public class SecurityCategory extends AppPrefsCategory {
|
||||||
var builder = new OptionsBuilder();
|
var builder = new OptionsBuilder();
|
||||||
builder.addTitle("securityPolicy")
|
builder.addTitle("securityPolicy")
|
||||||
.sub(new OptionsBuilder()
|
.sub(new OptionsBuilder()
|
||||||
|
.pref(prefs.checkForSecurityUpdates)
|
||||||
|
.addToggle(prefs.checkForSecurityUpdates)
|
||||||
.nameAndDescription("alwaysConfirmElevation")
|
.nameAndDescription("alwaysConfirmElevation")
|
||||||
.addToggle(prefs.alwaysConfirmElevation)
|
.addToggle(prefs.alwaysConfirmElevation)
|
||||||
.nameAndDescription("dontCachePasswords")
|
.nameAndDescription("dontCachePasswords")
|
||||||
|
|
|
@ -71,13 +71,7 @@ public class SyncCategory extends AppPrefsCategory {
|
||||||
testButton.apply(struc -> button.set(struc.get()));
|
testButton.apply(struc -> button.set(struc.get()));
|
||||||
testButton.padding(new Insets(6, 10, 6, 6));
|
testButton.padding(new Insets(6, 10, 6, 6));
|
||||||
|
|
||||||
var restartButton = new ButtonComp(AppI18n.observable("restart"), new FontIcon("mdi2r-restart"), () -> {
|
var testRow = new HorizontalComp(List.of(testButton))
|
||||||
OperationMode.restart();
|
|
||||||
});
|
|
||||||
restartButton.visible(canRestart);
|
|
||||||
restartButton.padding(new Insets(6, 10, 6, 6));
|
|
||||||
|
|
||||||
var testRow = new HorizontalComp(List.of(testButton, restartButton))
|
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(new Insets(10, 0, 0, 0))
|
.padding(new Insets(10, 0, 0, 0))
|
||||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||||
|
@ -92,10 +86,9 @@ public class SyncCategory extends AppPrefsCategory {
|
||||||
var builder = new OptionsBuilder();
|
var builder = new OptionsBuilder();
|
||||||
builder.addTitle("sync")
|
builder.addTitle("sync")
|
||||||
.sub(new OptionsBuilder()
|
.sub(new OptionsBuilder()
|
||||||
.name("enableGitStorage")
|
.pref(prefs.enableGitStorage)
|
||||||
.description("enableGitStorageDescription")
|
|
||||||
.addToggle(prefs.enableGitStorage)
|
.addToggle(prefs.enableGitStorage)
|
||||||
.nameAndDescription("storageGitRemote")
|
.pref(prefs.storageGitRemote)
|
||||||
.addComp(remoteRow, prefs.storageGitRemote)
|
.addComp(remoteRow, prefs.storageGitRemote)
|
||||||
.disable(prefs.enableGitStorage.not())
|
.disable(prefs.enableGitStorage.not())
|
||||||
.addComp(testRow)
|
.addComp(testRow)
|
||||||
|
|
|
@ -2,8 +2,8 @@ package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.LocalStore;
|
|
||||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
import io.xpipe.app.fxcomps.impl.ChoiceComp;
|
||||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||||
|
@ -41,7 +41,10 @@ public class TerminalCategory extends AppPrefsCategory {
|
||||||
var term = AppPrefs.get().terminalType().getValue();
|
var term = AppPrefs.get().terminalType().getValue();
|
||||||
if (term != null) {
|
if (term != null) {
|
||||||
TerminalLauncher.open(
|
TerminalLauncher.open(
|
||||||
"Test", new LocalStore().control().command("echo Test"));
|
"Test",
|
||||||
|
ProcessControlProvider.get()
|
||||||
|
.createLocalProcessControl(true)
|
||||||
|
.command("echo Test"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})))
|
})))
|
||||||
|
|
|
@ -29,13 +29,13 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performUpdateAndRestart() {
|
private void performUpdateAndRestart() {
|
||||||
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent(false, false);
|
||||||
UpdateAvailableAlert.showIfNeeded();
|
UpdateAvailableAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
|
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck(false, false);
|
||||||
XPipeDistributionType.get().getUpdateHandler().prepareUpdate();
|
XPipeDistributionType.get().getUpdateHandler().prepareUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,6 @@ public class VaultCategory extends AppPrefsCategory {
|
||||||
public Comp<?> create() {
|
public Comp<?> create() {
|
||||||
var prefs = AppPrefs.get();
|
var prefs = AppPrefs.get();
|
||||||
var builder = new OptionsBuilder();
|
var builder = new OptionsBuilder();
|
||||||
if (!STORAGE_DIR_FIXED) {
|
|
||||||
var sub =
|
|
||||||
new OptionsBuilder().nameAndDescription("storageDirectory").addPath(prefs.storageDirectory);
|
|
||||||
sub.withValidator(val -> {
|
|
||||||
sub.check(Validator.absolutePath(val, prefs.storageDirectory));
|
|
||||||
sub.check(Validator.directory(val, prefs.storageDirectory));
|
|
||||||
});
|
|
||||||
builder.addTitle("storage").sub(sub);
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptVault = new SimpleBooleanProperty(prefs.encryptAllVaultData().get());
|
var encryptVault = new SimpleBooleanProperty(prefs.encryptAllVaultData().get());
|
||||||
encryptVault.addListener((observable, oldValue, newValue) -> {
|
encryptVault.addListener((observable, oldValue, newValue) -> {
|
||||||
|
|
|
@ -16,13 +16,7 @@ public class WorkspacesCategory extends AppPrefsCategory {
|
||||||
@Override
|
@Override
|
||||||
protected Comp<?> create() {
|
protected Comp<?> create() {
|
||||||
return new OptionsBuilder()
|
return new OptionsBuilder()
|
||||||
.addTitle(AppI18n.observable("manageWorkspaces")
|
.addTitle(LicenseProvider.get().getFeature("workspaces").suffixObservable("manageWorkspaces"))
|
||||||
.map(s -> s
|
|
||||||
+ (LicenseProvider.get()
|
|
||||||
.getFeature("workspaces")
|
|
||||||
.isSupported()
|
|
||||||
? ""
|
|
||||||
: " (Pro)")))
|
|
||||||
.sub(new OptionsBuilder()
|
.sub(new OptionsBuilder()
|
||||||
.nameAndDescription("workspaceAdd")
|
.nameAndDescription("workspaceAdd")
|
||||||
.addComp(new ButtonComp(AppI18n.observable("addWorkspace"), WorkspaceCreationAlert::showAsync)))
|
.addComp(new ButtonComp(AppI18n.observable("addWorkspace"), WorkspaceCreationAlert::showAsync)))
|
||||||
|
|
|
@ -52,7 +52,8 @@ public class DataStateProviderImpl extends DataStateProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
var entry = DataStorage.get().getStoreEntryIfPresent(store, true).or(() -> DataStorage.get()
|
||||||
|
.getStoreEntryInProgressIfPresent(store));
|
||||||
if (entry.isEmpty()) {
|
if (entry.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -66,7 +67,8 @@ public class DataStateProviderImpl extends DataStateProvider {
|
||||||
return def.get();
|
return def.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
var entry = DataStorage.get().getStoreEntryIfPresent(store, true).or(() -> DataStorage.get()
|
||||||
|
.getStoreEntryInProgressIfPresent(store));
|
||||||
if (entry.isEmpty()) {
|
if (entry.isEmpty()) {
|
||||||
return def.get();
|
return def.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.FixedChildStore;
|
import io.xpipe.core.store.FixedChildStore;
|
||||||
import io.xpipe.core.store.StorePath;
|
import io.xpipe.core.store.StorePath;
|
||||||
import io.xpipe.core.store.ValidationContext;
|
|
||||||
import io.xpipe.core.util.UuidHelper;
|
import io.xpipe.core.util.UuidHelper;
|
||||||
|
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
|
@ -365,30 +364,24 @@ public abstract class DataStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public boolean refreshChildren(DataStoreEntry e, ValidationContext<?> context) {
|
public boolean refreshChildren(DataStoreEntry e) {
|
||||||
return refreshChildren(e, context, false);
|
return refreshChildren(e, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public boolean refreshChildrenOrThrow(DataStoreEntry e) throws Exception {
|
||||||
public <T extends ValidationContext<?>> boolean refreshChildren(DataStoreEntry e, T context, boolean throwOnFail)
|
return refreshChildren(e, true);
|
||||||
throws Exception {
|
}
|
||||||
if (!(e.getStore() instanceof FixedHierarchyStore<?> h)) {
|
|
||||||
|
public boolean refreshChildren(DataStoreEntry e, boolean throwOnFail) throws Exception {
|
||||||
|
if (!(e.getStore() instanceof FixedHierarchyStore h)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.incrementBusyCounter();
|
e.incrementBusyCounter();
|
||||||
List<? extends DataStoreEntryRef<? extends FixedChildStore>> newChildren;
|
List<? extends DataStoreEntryRef<? extends FixedChildStore>> newChildren;
|
||||||
var hadContext = context != null;
|
|
||||||
try {
|
try {
|
||||||
if (context == null) {
|
newChildren = ((FixedHierarchyStore) h)
|
||||||
context = (T) h.createContext();
|
.listChildren().stream()
|
||||||
if (context == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newChildren = ((FixedHierarchyStore<T>) h)
|
|
||||||
.listChildren(context).stream()
|
|
||||||
.filter(dataStoreEntryRef -> dataStoreEntryRef != null && dataStoreEntryRef.get() != null)
|
.filter(dataStoreEntryRef -> dataStoreEntryRef != null && dataStoreEntryRef.get() != null)
|
||||||
.toList();
|
.toList();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -399,9 +392,6 @@ public abstract class DataStorage {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (context != null && !hadContext) {
|
|
||||||
context.close();
|
|
||||||
}
|
|
||||||
e.decrementBusyCounter();
|
e.decrementBusyCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -516,47 +516,18 @@ public class DataStoreEntry extends StorageElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateOrThrow() throws Throwable {
|
public void validateOrThrow() throws Throwable {
|
||||||
validateOrThrowAndClose(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validateOrThrowAndClose(ValidationContext<?> existingContext) throws Throwable {
|
|
||||||
var subContext = validateAndKeepOpenOrThrowAndClose(existingContext);
|
|
||||||
if (subContext != null) {
|
|
||||||
subContext.close();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> ValidationContext<?> validateAndKeepOpenOrThrowAndClose(ValidationContext<?> existingContext)
|
|
||||||
throws Throwable {
|
|
||||||
if (store == null) {
|
if (store == null) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(store instanceof ValidatableStore<?> l)) {
|
if (!(store instanceof ValidatableStore l)) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
store.checkComplete();
|
store.checkComplete();
|
||||||
incrementBusyCounter();
|
incrementBusyCounter();
|
||||||
ValidationContext<T> context = existingContext != null
|
l.validate();
|
||||||
? (ValidationContext<T>) existingContext
|
|
||||||
: (ValidationContext<T>) l.createContext();
|
|
||||||
if (context == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var r = ((ValidatableStore<ValidationContext<T>>) l).validate(context);
|
|
||||||
return r;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
context.close();
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
decrementBusyCounter();
|
decrementBusyCounter();
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean showInfo() {
|
private boolean showInfo() {
|
||||||
boolean set = AppCache.get("xshellSetup", Boolean.class, () -> false);
|
boolean set = AppCache.getBoolean("xshellSetup", false);
|
||||||
if (set) {
|
if (set) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean showInfo() throws IOException {
|
private boolean showInfo() throws IOException {
|
||||||
boolean set = AppCache.get("termiusSetup", Boolean.class, () -> false);
|
boolean set = AppCache.getBoolean("termiusSetup", false);
|
||||||
if (set) {
|
if (set) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ public interface KittyTerminalType extends ExternalTerminalType {
|
||||||
var socket = getSocket();
|
var socket = getSocket();
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
if (sc.executeSimpleBooleanCommand(
|
if (sc.executeSimpleBooleanCommand(
|
||||||
"test -w " + sc.getShellDialect().fileArgument(socket))) {
|
"/usr/bin/test -w " + sc.getShellDialect().fileArgument(socket))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ public interface KittyTerminalType extends ExternalTerminalType {
|
||||||
var socket = getSocket();
|
var socket = getSocket();
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
if (sc.executeSimpleBooleanCommand(
|
if (sc.executeSimpleBooleanCommand(
|
||||||
"test -w " + sc.getShellDialect().fileArgument(socket))) {
|
"/usr/bin/test -w " + sc.getShellDialect().fileArgument(socket))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ public interface WindowsTerminalType extends ExternalTerminalType {
|
||||||
// wt can't elevate a command consisting out of multiple parts if wt is configured to elevate by default
|
// wt can't elevate a command consisting out of multiple parts if wt is configured to elevate by default
|
||||||
// So work around it by just passing a script file if possible
|
// So work around it by just passing a script file if possible
|
||||||
if (ShellDialects.isPowershell(configuration.getScriptDialect())) {
|
if (ShellDialects.isPowershell(configuration.getScriptDialect())) {
|
||||||
var usesPowershell = ShellDialects.isPowershell(ProcessControlProvider.get().getEffectiveLocalDialect());
|
var usesPowershell =
|
||||||
|
ShellDialects.isPowershell(ProcessControlProvider.get().getEffectiveLocalDialect());
|
||||||
if (usesPowershell) {
|
if (usesPowershell) {
|
||||||
// We can't work around it in this case, so let's just hope that there's no elevation configured
|
// We can't work around it in this case, so let's just hope that there's no elevation configured
|
||||||
cmd.add(configuration.getDialectLaunchCommand());
|
cmd.add(configuration.getDialectLaunchCommand());
|
||||||
|
@ -41,7 +42,9 @@ public interface WindowsTerminalType extends ExternalTerminalType {
|
||||||
// There might be a mismatch if we are for example using logging
|
// There might be a mismatch if we are for example using logging
|
||||||
// In this case we can actually work around the problem
|
// In this case we can actually work around the problem
|
||||||
cmd.addFile(shellControl -> {
|
cmd.addFile(shellControl -> {
|
||||||
var script = ScriptHelper.createExecScript(shellControl, configuration.getDialectLaunchCommand().buildFull(shellControl));
|
var script = ScriptHelper.createExecScript(
|
||||||
|
shellControl,
|
||||||
|
configuration.getDialectLaunchCommand().buildFull(shellControl));
|
||||||
return script.toString();
|
return script.toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package io.xpipe.app.test;
|
package io.xpipe.app.test;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.FailableSupplier;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.junit.jupiter.api.Named;
|
import org.junit.jupiter.api.Named;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class TestModule<V> {
|
public abstract class TestModule<V> {
|
||||||
|
@ -12,6 +13,7 @@ public abstract class TestModule<V> {
|
||||||
private static final Map<Class<?>, Map<String, ?>> values = new LinkedHashMap<>();
|
private static final Map<Class<?>, Map<String, ?>> values = new LinkedHashMap<>();
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
@SneakyThrows
|
||||||
public static <T> Map<String, T> get(Class<T> c, Module module, String... classes) {
|
public static <T> Map<String, T> get(Class<T> c, Module module, String... classes) {
|
||||||
if (!values.containsKey(c)) {
|
if (!values.containsKey(c)) {
|
||||||
List<Class<?>> loadedClasses = Arrays.stream(classes)
|
List<Class<?>> loadedClasses = Arrays.stream(classes)
|
||||||
|
@ -31,8 +33,13 @@ public abstract class TestModule<V> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Map<String, T>) values.get(c).entrySet().stream()
|
Map<String, Object> map = new HashMap<>();
|
||||||
.collect(Collectors.toMap(o -> o.getKey(), o -> ((Supplier<?>) o.getValue()).get()));
|
for (Map.Entry<String, ?> o : values.get(c).entrySet()) {
|
||||||
|
if (map.put(o.getKey(), ((FailableSupplier<?>) o.getValue()).get()) != null) {
|
||||||
|
throw new IllegalStateException("Duplicate key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Map<String, T>) map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> Stream<Named<T>> getArguments(Class<T> c, Module module, String... classes) {
|
public static <T> Stream<Named<T>> getArguments(Class<T> c, Module module, String... classes) {
|
||||||
|
@ -43,7 +50,7 @@ public abstract class TestModule<V> {
|
||||||
return argumentBuilder.build();
|
return argumentBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void init(Map<String, Supplier<V>> list) throws Exception;
|
protected abstract void init(Map<String, FailableSupplier<V>> list) throws Exception;
|
||||||
|
|
||||||
protected abstract Class<V> getValueClass();
|
protected abstract Class<V> getValueClass();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package io.xpipe.app.update;
|
package io.xpipe.app.update;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.util.HttpHelper;
|
import io.xpipe.app.util.HttpHelper;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
@ -15,6 +17,9 @@ import org.kohsuke.github.authorization.AuthorizationProvider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -112,30 +117,39 @@ public class AppDownloads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<GHRelease> getTopReleaseIncludingPreRelease() throws IOException {
|
private static String queryLatestVersion(boolean first, boolean securityOnly) throws Exception {
|
||||||
var repo = getRepository();
|
var req = JsonNodeFactory.instance.objectNode();
|
||||||
return Optional.ofNullable(repo.listReleases().iterator().next());
|
req.put("securityOnly", securityOnly);
|
||||||
|
req.put("ptb", AppProperties.get().isStaging());
|
||||||
|
req.put("os", OsType.getLocal().getId());
|
||||||
|
req.put("arch", AppProperties.get().getArch());
|
||||||
|
req.put("uuid", AppProperties.get().getUuid().toString());
|
||||||
|
req.put("version", AppProperties.get().getVersion());
|
||||||
|
req.put("first", first);
|
||||||
|
var url = URI.create("https://api.xpipe.io/version");
|
||||||
|
|
||||||
|
var builder = HttpRequest.newBuilder();
|
||||||
|
var httpRequest = builder.uri(url)
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(req.toPrettyString()))
|
||||||
|
.build();
|
||||||
|
var client = HttpClient.newHttpClient();
|
||||||
|
var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException(response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<GHRelease> getMarkedLatestRelease() throws IOException {
|
var json = JacksonMapper.getDefault().readTree(response.body());
|
||||||
var repo = getRepository();
|
var ver = json.required("version").asText();
|
||||||
return Optional.ofNullable(repo.getLatestRelease());
|
return ver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<GHRelease> getLatestSuitableRelease() throws IOException {
|
public static Optional<GHRelease> queryLatestRelease(boolean first, boolean securityOnly) throws Exception {
|
||||||
try {
|
try {
|
||||||
var preIncluding = getTopReleaseIncludingPreRelease();
|
var ver = queryLatestVersion(first, securityOnly);
|
||||||
// If we are currently running a prerelease, always return this as the suitable release!
|
var repo = getRepository();
|
||||||
if (preIncluding.isPresent()
|
var rel = repo.getReleaseByTagName(ver);
|
||||||
&& preIncluding.get().isPrerelease()
|
return Optional.ofNullable(rel);
|
||||||
&& AppProperties.get()
|
} catch (Exception e) {
|
||||||
.getVersion()
|
|
||||||
.equals(preIncluding.get().getTagName())) {
|
|
||||||
return preIncluding;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMarkedLatestRelease();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw ErrorEvent.expected(e);
|
throw ErrorEvent.expected(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.update;
|
||||||
import io.xpipe.app.core.AppLogs;
|
import io.xpipe.app.core.AppLogs;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.ext.LocalStore;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
@ -79,7 +78,6 @@ public class AppInstaller {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void installLocal(Path file) throws Exception {
|
public void installLocal(Path file) throws Exception {
|
||||||
var shellProcessControl = new LocalStore().control().start();
|
|
||||||
var exec = (AppProperties.get().isDevelopmentEnvironment()
|
var exec = (AppProperties.get().isDevelopmentEnvironment()
|
||||||
? Path.of(XPipeInstallation.getLocalDefaultInstallationBasePath())
|
? Path.of(XPipeInstallation.getLocalDefaultInstallationBasePath())
|
||||||
: XPipeInstallation.getCurrentInstallationBasePath())
|
: XPipeInstallation.getCurrentInstallationBasePath())
|
||||||
|
@ -98,7 +96,7 @@ public class AppInstaller {
|
||||||
+ ScriptHelper.createLocalExecScript(command) + "`\"\"";
|
+ ScriptHelper.createLocalExecScript(command) + "`\"\"";
|
||||||
|
|
||||||
runAndClose(() -> {
|
runAndClose(() -> {
|
||||||
shellProcessControl.executeSimpleCommand(toRun);
|
LocalShell.getShell().executeSimpleCommand(toRun);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
package io.xpipe.app.update;
|
|
||||||
|
|
||||||
import io.xpipe.app.core.AppProperties;
|
|
||||||
import io.xpipe.app.ext.LocalStore;
|
|
||||||
import io.xpipe.app.fxcomps.impl.CodeSnippet;
|
|
||||||
import io.xpipe.app.fxcomps.impl.CodeSnippetComp;
|
|
||||||
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class ChocoUpdater extends UpdateHandler {
|
|
||||||
|
|
||||||
public ChocoUpdater() {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Region createInterface() {
|
|
||||||
var snippet = CodeSnippet.builder()
|
|
||||||
.keyword("choco")
|
|
||||||
.space()
|
|
||||||
.identifier("install")
|
|
||||||
.space()
|
|
||||||
.string("xpipe")
|
|
||||||
.space()
|
|
||||||
.keyword("--version=" + getPreparedUpdate().getValue().getVersion())
|
|
||||||
.build();
|
|
||||||
return new CodeSnippetComp(false, new SimpleObjectProperty<>(snippet)).createRegion();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvailableRelease refreshUpdateCheckImpl() throws Exception {
|
|
||||||
try (var sc = new LocalStore().control().start()) {
|
|
||||||
var latest = sc.executeSimpleStringCommand("choco outdated -r --nocolor")
|
|
||||||
.lines()
|
|
||||||
.filter(s -> s.startsWith("xpipe"))
|
|
||||||
.findAny()
|
|
||||||
.map(string -> string.split("\\|")[2]);
|
|
||||||
if (latest.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isUpdate = isUpdate(latest.get());
|
|
||||||
var rel = new AvailableRelease(
|
|
||||||
AppProperties.get().getVersion(),
|
|
||||||
XPipeDistributionType.get().getId(),
|
|
||||||
latest.get(),
|
|
||||||
"https://community.chocolatey.org/packages/xpipe/" + latest,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Instant.now(),
|
|
||||||
isUpdate);
|
|
||||||
lastUpdateCheckResult.setValue(rel);
|
|
||||||
return lastUpdateCheckResult.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -65,8 +65,8 @@ public class GitHubUpdater extends UpdateHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized AvailableRelease refreshUpdateCheckImpl() throws Exception {
|
public synchronized AvailableRelease refreshUpdateCheckImpl(boolean first, boolean securityOnly) throws Exception {
|
||||||
var rel = AppDownloads.getLatestSuitableRelease();
|
var rel = AppDownloads.queryLatestRelease(first, securityOnly);
|
||||||
event("Determined latest suitable release "
|
event("Determined latest suitable release "
|
||||||
+ rel.map(GHRelease::getName).orElse(null));
|
+ rel.map(GHRelease::getName).orElse(null));
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ public class PortableUpdater extends UpdateHandler {
|
||||||
.createRegion();
|
.createRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized AvailableRelease refreshUpdateCheckImpl() throws Exception {
|
public synchronized AvailableRelease refreshUpdateCheckImpl(boolean first, boolean securityOnly) throws Exception {
|
||||||
var rel = AppDownloads.getLatestSuitableRelease();
|
var rel = AppDownloads.queryLatestRelease(first, securityOnly);
|
||||||
event("Determined latest suitable release "
|
event("Determined latest suitable release "
|
||||||
+ rel.map(GHRelease::getName).orElse(null));
|
+ rel.map(GHRelease::getName).orElse(null));
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.update;
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
|
@ -22,7 +23,7 @@ public class UpdateAvailableAlert {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether we still have the latest version prepared
|
// Check whether we still have the latest version prepared
|
||||||
uh.refreshUpdateCheckSilent();
|
uh.refreshUpdateCheckSilent(false, !AppPrefs.get().automaticallyUpdate().get());
|
||||||
if (uh.getPreparedUpdate().getValue() == null) {
|
if (uh.getPreparedUpdate().getValue() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public abstract class UpdateHandler {
|
||||||
protected final boolean updateSucceeded;
|
protected final boolean updateSucceeded;
|
||||||
|
|
||||||
protected UpdateHandler(boolean startBackgroundThread) {
|
protected UpdateHandler(boolean startBackgroundThread) {
|
||||||
performedUpdate = AppCache.get("performedUpdate", PerformedUpdate.class, () -> null);
|
performedUpdate = AppCache.getNonNull("performedUpdate", PerformedUpdate.class, () -> null);
|
||||||
var hasUpdated = performedUpdate != null;
|
var hasUpdated = performedUpdate != null;
|
||||||
event("Was updated is " + hasUpdated);
|
event("Was updated is " + hasUpdated);
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
|
@ -48,7 +48,7 @@ public abstract class UpdateHandler {
|
||||||
updateSucceeded = false;
|
updateSucceeded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
preparedUpdate.setValue(AppCache.get("preparedUpdate", PreparedUpdate.class, () -> null));
|
preparedUpdate.setValue(AppCache.getNonNull("preparedUpdate", PreparedUpdate.class, () -> null));
|
||||||
|
|
||||||
// Check if the original version this was downloaded from is still the same
|
// Check if the original version this was downloaded from is still the same
|
||||||
if (preparedUpdate.getValue() != null
|
if (preparedUpdate.getValue() != null
|
||||||
|
@ -99,12 +99,14 @@ public abstract class UpdateHandler {
|
||||||
|
|
||||||
private void startBackgroundUpdater() {
|
private void startBackgroundUpdater() {
|
||||||
ThreadHelper.createPlatformThread("updater", true, () -> {
|
ThreadHelper.createPlatformThread("updater", true, () -> {
|
||||||
|
var checked = false;
|
||||||
ThreadHelper.sleep(Duration.ofMinutes(5).toMillis());
|
ThreadHelper.sleep(Duration.ofMinutes(5).toMillis());
|
||||||
event("Starting background updater thread");
|
event("Starting background updater thread");
|
||||||
while (true) {
|
while (true) {
|
||||||
if (AppPrefs.get().automaticallyUpdate().get()) {
|
if (AppPrefs.get().automaticallyUpdate().get() || AppPrefs.get().checkForSecurityUpdates().get()) {
|
||||||
event("Performing background update");
|
event("Performing background update");
|
||||||
refreshUpdateCheckSilent();
|
refreshUpdateCheckSilent(!checked, !AppPrefs.get().automaticallyUpdate().get());
|
||||||
|
checked = true;
|
||||||
prepareUpdate();
|
prepareUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,17 +136,9 @@ public abstract class UpdateHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void prepareUpdateAsync() {
|
public final AvailableRelease refreshUpdateCheckSilent(boolean first, boolean securityOnly) {
|
||||||
ThreadHelper.runAsync(() -> prepareUpdate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void refreshUpdateCheckAsync() {
|
|
||||||
ThreadHelper.runAsync(() -> refreshUpdateCheckSilent());
|
|
||||||
}
|
|
||||||
|
|
||||||
public final AvailableRelease refreshUpdateCheckSilent() {
|
|
||||||
try {
|
try {
|
||||||
return refreshUpdateCheck();
|
return refreshUpdateCheck(first, securityOnly);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).discard().handle();
|
ErrorEvent.fromThrowable(ex).discard().handle();
|
||||||
return null;
|
return null;
|
||||||
|
@ -214,7 +208,7 @@ public abstract class UpdateHandler {
|
||||||
|
|
||||||
// Check if prepared update is still the latest.
|
// Check if prepared update is still the latest.
|
||||||
// We only do that here to minimize the sent requests by only executing when it's really necessary
|
// We only do that here to minimize the sent requests by only executing when it's really necessary
|
||||||
var available = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
var available = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent(false, !AppPrefs.get().automaticallyUpdate().get());
|
||||||
if (preparedUpdate.getValue() == null) {
|
if (preparedUpdate.getValue() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -233,17 +227,17 @@ public abstract class UpdateHandler {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final AvailableRelease refreshUpdateCheck() throws Exception {
|
public final AvailableRelease refreshUpdateCheck(boolean first, boolean securityOnly) throws Exception {
|
||||||
if (busy.getValue()) {
|
if (busy.getValue()) {
|
||||||
return lastUpdateCheckResult.getValue();
|
return lastUpdateCheckResult.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var ignored = new BooleanScope(busy).start()) {
|
try (var ignored = new BooleanScope(busy).start()) {
|
||||||
return refreshUpdateCheckImpl();
|
return refreshUpdateCheckImpl(first, securityOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract AvailableRelease refreshUpdateCheckImpl() throws Exception;
|
public abstract AvailableRelease refreshUpdateCheckImpl(boolean first, boolean securityOnly) throws Exception;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
|
|
|
@ -24,7 +24,7 @@ public enum XPipeDistributionType {
|
||||||
NATIVE_INSTALLATION("install", true, () -> new GitHubUpdater(true)),
|
NATIVE_INSTALLATION("install", true, () -> new GitHubUpdater(true)),
|
||||||
HOMEBREW("homebrew", true, () -> new HomebrewUpdater()),
|
HOMEBREW("homebrew", true, () -> new HomebrewUpdater()),
|
||||||
WEBTOP("webtop", true, () -> new PortableUpdater(false)),
|
WEBTOP("webtop", true, () -> new PortableUpdater(false)),
|
||||||
CHOCO("choco", true, () -> new ChocoUpdater());
|
CHOCO("choco", true, () -> new PortableUpdater(true));
|
||||||
|
|
||||||
private static XPipeDistributionType type;
|
private static XPipeDistributionType type;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ public enum XPipeDistributionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!XPipeSession.get().isNewBuildSession()) {
|
if (!XPipeSession.get().isNewBuildSession()) {
|
||||||
var cached = AppCache.get("dist", String.class, () -> null);
|
var cached = AppCache.getNonNull("dist", String.class, () -> null);
|
||||||
var cachedType = Arrays.stream(values())
|
var cachedType = Arrays.stream(values())
|
||||||
.filter(xPipeDistributionType ->
|
.filter(xPipeDistributionType ->
|
||||||
xPipeDistributionType.getId().equals(cached))
|
xPipeDistributionType.getId().equals(cached))
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.core.process.ShellDialects;
|
|
||||||
import io.xpipe.core.process.ShellStoreState;
|
|
||||||
import io.xpipe.core.process.ShellTtyState;
|
|
||||||
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -17,45 +10,6 @@ public class DataStoreFormatter {
|
||||||
return String.join(" ", Arrays.stream(elements).filter(s -> s != null).toList());
|
return String.join(" ", Arrays.stream(elements).filter(s -> s != null).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formattedOsName(String osName) {
|
|
||||||
osName = osName.replaceAll("^Microsoft ", "");
|
|
||||||
|
|
||||||
var proRequired = !LicenseProvider.get().checkOsName(osName);
|
|
||||||
if (!proRequired) {
|
|
||||||
return osName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "[Pro] " + osName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObservableValue<String> shellInformation(StoreEntryWrapper w) {
|
|
||||||
return BindingsHelper.map(w.getPersistentState(), o -> {
|
|
||||||
if (o instanceof ShellStoreState s) {
|
|
||||||
if (s.getRunning() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.getShellDialect() != null
|
|
||||||
&& !s.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
|
||||||
if (s.getOsName() != null) {
|
|
||||||
return formattedOsName(s.getOsName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.getShellDialect().equals(ShellDialects.NO_INTERACTION)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.getShellDialect().getDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix = s.getTtyState() != null && s.getTtyState() != ShellTtyState.NONE ? "[PTY] " : "";
|
|
||||||
return s.isRunning() ? prefix + formattedOsName(s.getOsName()) : "Connection failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "?";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String capitalize(String name) {
|
public static String capitalize(String name) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,22 +3,14 @@ package io.xpipe.app.util;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.FixedChildStore;
|
import io.xpipe.core.store.FixedChildStore;
|
||||||
import io.xpipe.core.store.ValidatableStore;
|
|
||||||
import io.xpipe.core.store.ValidationContext;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface FixedHierarchyStore<T extends ValidationContext<?>> extends ValidatableStore<T>, DataStore {
|
public interface FixedHierarchyStore extends DataStore {
|
||||||
|
|
||||||
default boolean removeLeftovers() {
|
default boolean removeLeftovers() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
List<? extends DataStoreEntryRef<? extends FixedChildStore>> listChildren() throws Exception;
|
||||||
default T validate(T context) throws Exception {
|
|
||||||
listChildren(context);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<? extends DataStoreEntryRef<? extends FixedChildStore>> listChildren(T context) throws Exception;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@ public class HostHelper {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int findRandomOpenPortOnAllLocalInterfaces() throws IOException {
|
public static int findRandomOpenPortOnAllLocalInterfaces() {
|
||||||
try (ServerSocket socket = new ServerSocket(0)) {
|
try (ServerSocket socket = new ServerSocket(0)) {
|
||||||
return socket.getLocalPort();
|
return socket.getLocalPort();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return randomPort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ public class Hyperlinks {
|
||||||
public static final String DOUBLE_PROMPT = "https://docs.xpipe.io/two-step-connections";
|
public static final String DOUBLE_PROMPT = "https://docs.xpipe.io/two-step-connections";
|
||||||
public static final String AGENT_SETUP = "https://docs.xpipe.io/ssh-agent-socket";
|
public static final String AGENT_SETUP = "https://docs.xpipe.io/ssh-agent-socket";
|
||||||
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
|
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
|
||||||
public static final String GITHUB_PTB = "https://github.com/xpipe-io/xpipe";
|
public static final String GITHUB_PTB = "https://github.com/xpipe-io/xpipe-ptb";
|
||||||
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
|
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
|
||||||
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
|
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
|
||||||
public static final String SECURITY = "https://docs.xpipe.io/security";
|
public static final String SECURITY = "https://docs.xpipe.io/security";
|
||||||
|
|
|
@ -24,7 +24,7 @@ public abstract class LicenseProvider {
|
||||||
|
|
||||||
public abstract LicensedFeature getFeature(String id);
|
public abstract LicensedFeature getFeature(String id);
|
||||||
|
|
||||||
public abstract boolean checkOsName(String name);
|
public abstract LicensedFeature checkOsName(String name);
|
||||||
|
|
||||||
public abstract void checkOsNameOrThrow(String s);
|
public abstract void checkOsNameOrThrow(String s);
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,28 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface LicensedFeature {
|
public interface LicensedFeature {
|
||||||
|
|
||||||
default Optional<String> getDescriptionSuffix() {
|
Optional<String> getDescriptionSuffix();
|
||||||
if (isSupported()) {
|
|
||||||
return Optional.empty();
|
public default ObservableValue<String> suffixObservable(ObservableValue<String> s) {
|
||||||
|
return s.map(s2 ->
|
||||||
|
getDescriptionSuffix().map(suffix -> s2 + " (" + suffix + "+)").orElse(s2));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPreviewSupported()) {
|
public default ObservableValue<String> suffixObservable(String key) {
|
||||||
return Optional.of("Preview");
|
return AppI18n.observable(key).map(s -> getDescriptionSuffix()
|
||||||
|
.map(suffix -> s + " (" + suffix + "+)")
|
||||||
|
.orElse(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of("Pro");
|
public default String suffix(String s) {
|
||||||
|
return getDescriptionSuffix().map(suffix -> s + " (" + suffix + "+)").orElse(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.GuiDialog;
|
import io.xpipe.app.ext.GuiDialog;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
import io.xpipe.app.fxcomps.impl.*;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.util.InPlaceSecretValue;
|
import io.xpipe.core.util.InPlaceSecretValue;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
@ -147,6 +148,28 @@ public class OptionsBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OptionsBuilder pref(Object property) {
|
||||||
|
var mapping = AppPrefs.get().getMapping(property);
|
||||||
|
var name = mapping.getKey();
|
||||||
|
name(name);
|
||||||
|
if (mapping.isRequiresRestart()) {
|
||||||
|
description(AppI18n.observable(name + "Description").map(s -> s + "\n\n" + AppI18n.get("requiresRestart")));
|
||||||
|
} else {
|
||||||
|
description(AppI18n.observable(name + "Description"));
|
||||||
|
}
|
||||||
|
if (mapping.getLicenseFeatureId() != null) {
|
||||||
|
licenseRequirement(mapping.getLicenseFeatureId());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionsBuilder licenseRequirement(String featureId) {
|
||||||
|
var f = LicenseProvider.get().getFeature(featureId);
|
||||||
|
name = f.suffixObservable(name);
|
||||||
|
lastNameReference = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public OptionsBuilder check(Function<Validator, Check> c) {
|
public OptionsBuilder check(Function<Validator, Check> c) {
|
||||||
lastCompHeadReference.apply(s -> c.apply(ownValidator).decorates(s.get()));
|
lastCompHeadReference.apply(s -> c.apply(ownValidator).decorates(s.get()));
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -2,14 +2,12 @@ package io.xpipe.app.util;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.DialogComp;
|
import io.xpipe.app.comp.base.DialogComp;
|
||||||
import io.xpipe.app.ext.ScanProvider;
|
import io.xpipe.app.ext.ScanProvider;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.process.ShellStoreState;
|
import io.xpipe.core.process.ShellStoreState;
|
||||||
import io.xpipe.core.process.ShellTtyState;
|
import io.xpipe.core.process.ShellTtyState;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.store.ShellValidationContext;
|
|
||||||
import io.xpipe.core.store.ValidationContext;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -17,7 +15,7 @@ import java.util.function.BiFunction;
|
||||||
|
|
||||||
public class ScanAlert {
|
public class ScanAlert {
|
||||||
|
|
||||||
public static void showAsync(DataStoreEntry entry, ValidationContext<?> context) {
|
public static void showAsync(DataStoreEntry entry) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
var showForCon = entry == null
|
var showForCon = entry == null
|
||||||
|| (entry.getStore() instanceof ShellStore
|
|| (entry.getStore() instanceof ShellStore
|
||||||
|
@ -25,15 +23,13 @@ public class ScanAlert {
|
||||||
|| shellStoreState.getTtyState() == null
|
|| shellStoreState.getTtyState() == null
|
||||||
|| shellStoreState.getTtyState() == ShellTtyState.NONE));
|
|| shellStoreState.getTtyState() == ShellTtyState.NONE));
|
||||||
if (showForCon) {
|
if (showForCon) {
|
||||||
showForShellStore(entry, (ShellValidationContext) context);
|
showForShellStore(entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showForShellStore(DataStoreEntry initial, ShellValidationContext context) {
|
public static void showForShellStore(DataStoreEntry initial) {
|
||||||
show(
|
show(initial, (DataStoreEntry entry, ShellControl sc) -> {
|
||||||
initial,
|
|
||||||
(DataStoreEntry entry, ShellControl sc) -> {
|
|
||||||
if (!sc.canHaveSubshells()) {
|
if (!sc.canHaveSubshells()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -47,12 +43,12 @@ public class ScanAlert {
|
||||||
}
|
}
|
||||||
|
|
||||||
var providers = ScanProvider.getAll();
|
var providers = ScanProvider.getAll();
|
||||||
var applicable = new ArrayList<ScanProvider.ScanOperation>();
|
var applicable = new ArrayList<ScanProvider.ScanOpportunity>();
|
||||||
for (ScanProvider scanProvider : providers) {
|
for (ScanProvider scanProvider : providers) {
|
||||||
try {
|
try {
|
||||||
// Previous scan operation could have exited the shell
|
// Previous scan operation could have exited the shell
|
||||||
sc.start();
|
sc.start();
|
||||||
ScanProvider.ScanOperation operation = scanProvider.create(entry, sc);
|
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
|
||||||
if (operation != null) {
|
if (operation != null) {
|
||||||
applicable.add(operation);
|
applicable.add(operation);
|
||||||
}
|
}
|
||||||
|
@ -61,17 +57,14 @@ public class ScanAlert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return applicable;
|
return applicable;
|
||||||
},
|
});
|
||||||
context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void show(
|
private static void show(
|
||||||
DataStoreEntry initialStore,
|
DataStoreEntry initialStore,
|
||||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> applicable,
|
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable) {
|
||||||
ShellValidationContext shellValidationContext) {
|
|
||||||
DialogComp.showWindow(
|
DialogComp.showWindow(
|
||||||
"scanAlertTitle",
|
"scanAlertTitle",
|
||||||
stage -> new ScanDialog(
|
stage -> new ScanDialog(stage, initialStore != null ? initialStore.ref() : null, applicable));
|
||||||
stage, initialStore != null ? initialStore.ref() : null, applicable, shellValidationContext));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.ListSelectorComp;
|
||||||
import io.xpipe.app.comp.store.StoreViewState;
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.ScanProvider;
|
import io.xpipe.app.ext.ScanProvider;
|
||||||
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
|
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
@ -12,8 +13,6 @@ import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.store.ShellValidationContext;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
@ -34,24 +33,21 @@ import static javafx.scene.layout.Priority.ALWAYS;
|
||||||
class ScanDialog extends DialogComp {
|
class ScanDialog extends DialogComp {
|
||||||
|
|
||||||
private final DataStoreEntryRef<ShellStore> initialStore;
|
private final DataStoreEntryRef<ShellStore> initialStore;
|
||||||
private final BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> applicable;
|
private final BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
||||||
private final ListProperty<ScanProvider.ScanOperation> selected =
|
private final ListProperty<ScanProvider.ScanOpportunity> selected =
|
||||||
new SimpleListProperty<>(FXCollections.observableArrayList());
|
new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||||
private ShellValidationContext shellValidationContext;
|
|
||||||
|
|
||||||
ScanDialog(
|
ScanDialog(
|
||||||
Stage window,
|
Stage window,
|
||||||
DataStoreEntryRef<ShellStore> entry,
|
DataStoreEntryRef<ShellStore> entry,
|
||||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> applicable,
|
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOpportunity>> applicable) {
|
||||||
ShellValidationContext shellValidationContext) {
|
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.initialStore = entry;
|
this.initialStore = entry;
|
||||||
this.entry = new SimpleObjectProperty<>(entry);
|
this.entry = new SimpleObjectProperty<>(entry);
|
||||||
this.applicable = applicable;
|
this.applicable = applicable;
|
||||||
this.shellValidationContext = shellValidationContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,7 +58,6 @@ class ScanDialog extends DialogComp {
|
||||||
@Override
|
@Override
|
||||||
protected void finish() {
|
protected void finish() {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
try {
|
|
||||||
if (entry.get() == null) {
|
if (entry.get() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -84,33 +79,20 @@ class ScanDialog extends DialogComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Previous scan operation could have exited the shell
|
// Previous scan operation could have exited the shell
|
||||||
shellValidationContext.get().start();
|
var sc = initialStore.getStore().getOrStartSession();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
a.getScanner().run();
|
a.getProvider().scan(entry.get().getEntry(), sc);
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
if (shellValidationContext != null) {
|
|
||||||
shellValidationContext.close();
|
|
||||||
shellValidationContext = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void discard() {
|
protected void discard() {}
|
||||||
ThreadHelper.runAsync(() -> {
|
|
||||||
if (shellValidationContext != null) {
|
|
||||||
shellValidationContext.close();
|
|
||||||
shellValidationContext = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Comp<?> pane(Comp<?> content) {
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
|
@ -161,22 +143,8 @@ class ScanDialog extends DialogComp {
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.executeExclusive(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (shellValidationContext != null) {
|
var sc = initialStore.getStore().getOrStartSession().withoutLicenseCheck();
|
||||||
shellValidationContext.close();
|
var a = applicable.apply(entry.get().get(), sc);
|
||||||
shellValidationContext = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
shellValidationContext = new ShellValidationContext(
|
|
||||||
newValue.getStore().control().withoutLicenseCheck().start());
|
|
||||||
|
|
||||||
// Handle window close while connection is established
|
|
||||||
if (!window.isShowing()) {
|
|
||||||
discard();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = applicable.apply(entry.get().get(), shellValidationContext.get());
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (a == null) {
|
if (a == null) {
|
||||||
window.close();
|
window.close();
|
||||||
|
@ -186,7 +154,7 @@ class ScanDialog extends DialogComp {
|
||||||
selected.setAll(a.stream()
|
selected.setAll(a.stream()
|
||||||
.filter(scanOperation -> scanOperation.isDefaultSelected() && !scanOperation.isDisabled())
|
.filter(scanOperation -> scanOperation.isDefaultSelected() && !scanOperation.isDisabled())
|
||||||
.toList());
|
.toList());
|
||||||
Function<ScanProvider.ScanOperation, String> nameFunc = (ScanProvider.ScanOperation s) -> {
|
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||||
var n = AppI18n.get(s.getNameKey());
|
var n = AppI18n.get(s.getNameKey());
|
||||||
if (s.getLicensedFeatureId() == null) {
|
if (s.getLicensedFeatureId() == null) {
|
||||||
return n;
|
return n;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue