mirror of
https://github.com/xpipe-io/xpipe.git
synced 2025-04-19 18:53:40 +00:00
Compare commits
No commits in common. "master" and "15.2" have entirely different histories.
159 changed files with 1313 additions and 2739 deletions
|
@ -2,6 +2,6 @@
|
|||
|
||||
Due to its nature, XPipe has to handle a lot of sensitive information. Therefore, the security, integrity, and privacy of your data has topmost priority.
|
||||
|
||||
More information about the security approach of the XPipe application can be found on the documentation website at https://docs.xpipe.io/reference/security.
|
||||
General information about the security approach of the XPipe application can be found on the website at https://xpipe.io/features#security. If you're interested in security implementation details, you can find them at https://docs.xpipe.io/security.
|
||||
|
||||
You can report security vulnerabilities in this GitHub repository in a confidential manner. We will get back to you as soon as possible if you do.
|
||||
|
|
|
@ -48,7 +48,7 @@ dependencies {
|
|||
api 'com.vladsch.flexmark:flexmark-ext-yaml-front-matter:0.64.8'
|
||||
api 'com.vladsch.flexmark:flexmark-ext-toc:0.64.8'
|
||||
|
||||
api("com.github.weisj:jsvg:1.7.1")
|
||||
api("com.github.weisj:jsvg:1.7.0")
|
||||
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
||||
api 'org.bouncycastle:bcprov-jdk18on:1.80'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.beacon.api.DaemonFocusExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
@ -10,11 +9,7 @@ public class DaemonFocusExchangeImpl extends DaemonFocusExchange {
|
|||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
OperationMode.switchUp(OperationMode.GUI);
|
||||
var w = AppMainWindow.getInstance();
|
||||
if (w != null) {
|
||||
w.focus();
|
||||
}
|
||||
OperationMode.switchUp(OperationMode.map(msg.getMode()));
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,13 @@ package io.xpipe.app.beacon.impl;
|
|||
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.TerminalLaunchExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
|
||||
var r = TerminalLauncherManager.launchExchange(msg.getRequest());
|
||||
return Response.builder().targetFile(r).build();
|
||||
}
|
||||
|
|
|
@ -24,9 +24,6 @@ public class BrowserAbstractSessionModel<T extends BrowserSessionTab> {
|
|||
|
||||
public void closeAsync(BrowserSessionTab e) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
// This is a bit ugly
|
||||
// If we die on tab init, wait a bit with closing to avoid removal while it is still being inited/added
|
||||
ThreadHelper.sleep(100);
|
||||
closeSync(e);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ public class BrowserFullSessionComp extends SimpleComp {
|
|||
var pinnedStack = createSplitStack(rightSplit, tabs);
|
||||
|
||||
var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator));
|
||||
loadingStack.apply(struc -> struc.get().setPickOnBounds(false));
|
||||
var splitPane = new LeftSplitPaneComp(vertical, loadingStack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(d -> {
|
||||
|
@ -148,8 +147,8 @@ public class BrowserFullSessionComp extends SimpleComp {
|
|||
var rec = new Rectangle();
|
||||
rec.widthProperty().bind(struc.get().widthProperty());
|
||||
rec.heightProperty().bind(struc.get().heightProperty());
|
||||
rec.setArcHeight(11);
|
||||
rec.setArcWidth(11);
|
||||
rec.setArcHeight(7);
|
||||
rec.setArcWidth(7);
|
||||
struc.get().getChildren().getFirst().setClip(rec);
|
||||
})
|
||||
.vgrow();
|
||||
|
@ -174,7 +173,6 @@ public class BrowserFullSessionComp extends SimpleComp {
|
|||
private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) {
|
||||
var cache = new HashMap<BrowserSessionTab, Region>();
|
||||
var splitStack = new StackComp(List.of());
|
||||
splitStack.apply(struc -> struc.get().setPickOnBounds(false));
|
||||
splitStack.apply(struc -> {
|
||||
model.getEffectiveRightTab().subscribe((newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
|
|
@ -156,8 +156,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
var os = fileList.getFileSystemModel()
|
||||
.getFileSystem()
|
||||
.getShell()
|
||||
.map(shellControl -> shellControl.getOsType())
|
||||
.orElse(null);
|
||||
.orElseThrow()
|
||||
.getOsType();
|
||||
table.widthProperty().subscribe((newValue) -> {
|
||||
if (os != OsType.WINDOWS && os != OsType.MACOS) {
|
||||
ownerCol.setVisible(newValue.doubleValue() > 1000);
|
||||
|
|
|
@ -53,7 +53,7 @@ public class BrowserFileSystemSavedState {
|
|||
|
||||
public BrowserFileSystemSavedState() {
|
||||
lastDirectory = null;
|
||||
recentDirectories = FXCollections.synchronizedObservableList(FXCollections.observableList(new ArrayList<>(STORED)));
|
||||
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
|
||||
}
|
||||
|
||||
static BrowserFileSystemSavedState loadForStore(BrowserFileSystemTabModel model) {
|
||||
|
@ -164,7 +164,7 @@ public class BrowserFileSystemSavedState {
|
|||
.map(recentEntry -> new RecentEntry(FileNames.toDirectory(recentEntry.directory), recentEntry.time))
|
||||
.filter(distinctBy(recentEntry -> recentEntry.getDirectory()))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
return new BrowserFileSystemSavedState(null, FXCollections.synchronizedObservableList(FXCollections.observableList(cleaned)));
|
||||
return new BrowserFileSystemSavedState(null, FXCollections.observableList(cleaned));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ public class BrowserFileSystemTabComp extends SimpleComp {
|
|||
home,
|
||||
model.getCurrentPath().isNull(),
|
||||
fileList,
|
||||
model.getCurrentPath().isNull().not()), false);
|
||||
model.getCurrentPath().isNull().not()));
|
||||
var r = stack.styleClass("browser-content-container").createRegion();
|
||||
r.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.*;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
|
@ -12,10 +11,7 @@ import java.nio.file.Path;
|
|||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -417,60 +413,23 @@ public class BrowserFileTransferOperation {
|
|||
// Initialize progress immediately prior to reading anything
|
||||
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
||||
|
||||
var killStreams = new AtomicBoolean(false);
|
||||
var exception = new AtomicReference<Exception>();
|
||||
var thread = ThreadHelper.createPlatformThread("transfer", true, () -> {
|
||||
try {
|
||||
var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize());
|
||||
byte[] buffer = new byte[bs];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer, 0, bs)) > 0) {
|
||||
if (cancelled()) {
|
||||
killStreams.set(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!checkTransferValidity()) {
|
||||
killStreams.set(true);
|
||||
break;
|
||||
}
|
||||
|
||||
outputStream.write(buffer, 0, read);
|
||||
transferred.addAndGet(read);
|
||||
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
exception.set(ex);
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
while (true) {
|
||||
var alive = thread.isAlive();
|
||||
var cancelled = cancelled();
|
||||
|
||||
if (cancelled) {
|
||||
// Assume that the transfer has stalled if it doesn't finish until then
|
||||
thread.join(1000);
|
||||
var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize());
|
||||
byte[] buffer = new byte[bs];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer, 0, bs)) > 0) {
|
||||
if (cancelled()) {
|
||||
killStreams();
|
||||
break;
|
||||
}
|
||||
|
||||
if (alive) {
|
||||
Thread.sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (killStreams.get()) {
|
||||
if (!checkTransferValidity()) {
|
||||
killStreams();
|
||||
}
|
||||
|
||||
var ex = exception.get();
|
||||
if (ex != null) {
|
||||
throw ex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
outputStream.write(buffer, 0, read);
|
||||
transferred.addAndGet(read);
|
||||
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ public class BrowserHistorySavedStateImpl implements BrowserHistorySavedState {
|
|||
ObservableList<Entry> lastSystems;
|
||||
|
||||
public BrowserHistorySavedStateImpl(List<Entry> lastSystems) {
|
||||
this.lastSystems = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(lastSystems));
|
||||
this.lastSystems = FXCollections.observableArrayList(lastSystems);
|
||||
}
|
||||
|
||||
private static BrowserHistorySavedStateImpl INSTANCE;
|
||||
|
|
|
@ -60,7 +60,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(emptyDisplay, empty);
|
||||
map.put(contentDisplay, empty.not());
|
||||
var stack = new MultiContentComp(map, false);
|
||||
var stack = new MultiContentComp(map);
|
||||
return stack.createRegion();
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||
.accessibleText(e.getPath())
|
||||
.disable(disable)
|
||||
.styleClass("directory-button")
|
||||
.apply(struc -> struc.get().setMaxWidth(20000))
|
||||
.apply(struc -> struc.get().setMaxWidth(2000))
|
||||
.styleClass(Styles.RIGHT_PILL)
|
||||
.hgrow()
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
|
|
|
@ -38,7 +38,7 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
|
||||
var commonPlatform = FXCollections.<FileEntry>synchronizedObservableList(FXCollections.observableArrayList());
|
||||
var commonPlatform = FXCollections.<FileEntry>observableArrayList();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var common = sc.getOsType().determineInterestingPaths(sc).stream()
|
||||
.filter(s -> !s.isBlank())
|
||||
|
|
|
@ -4,11 +4,8 @@ import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
|||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.browser.BrowserSessionTab;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.AppMainWindowContentComp;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.terminal.TerminalDockComp;
|
||||
|
@ -21,7 +18,6 @@ import javafx.application.Platform;
|
|||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Optional;
|
||||
|
@ -138,13 +134,6 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
|||
dockModel.toggleView(aBoolean);
|
||||
});
|
||||
});
|
||||
AppDialog.getModalOverlay().addListener((ListChangeListener<? super ModalOverlay>) c -> {
|
||||
if (c.getList().size() > 0) {
|
||||
dockModel.toggleView(false);
|
||||
} else {
|
||||
dockModel.toggleView(viewActive.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshShowingState() {
|
||||
|
|
|
@ -65,14 +65,13 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
var p = sourceItem.get().getProgress().getValue();
|
||||
var hideProgress = sourceItem
|
||||
.get()
|
||||
.downloadFinished()
|
||||
.get();
|
||||
var share = p != null ? (p.getTransferred() * 100 / p.getTotal()) : 0;
|
||||
var progressSuffix = hideProgress
|
||||
var progressSuffix = p == null
|
||||
|| sourceItem
|
||||
.get()
|
||||
.downloadFinished()
|
||||
.get()
|
||||
? ""
|
||||
: " " + share + "%";
|
||||
: " " + (p.getTransferred() * 100 / p.getTotal()) + "%";
|
||||
return entry.getFileName() + progressSuffix;
|
||||
},
|
||||
sourceItem.get().getProgress());
|
||||
|
@ -82,14 +81,14 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
var dragNotice = new LabelComp(AppI18n.observable("dragLocalFiles"))
|
||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
|
||||
.apply(struc -> struc.get().setWrapText(true))
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()));
|
||||
.hide(model.getEmpty());
|
||||
|
||||
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
model.clear(true);
|
||||
});
|
||||
})
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()))
|
||||
.hide(model.getEmpty())
|
||||
.tooltipKey("clearTransferDescription");
|
||||
|
||||
var downloadButton = new IconButtonComp("mdi2f-folder-move-outline", () -> {
|
||||
|
@ -97,7 +96,7 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
model.transferToDownloads();
|
||||
});
|
||||
})
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()))
|
||||
.hide(model.getEmpty())
|
||||
.tooltipKey("downloadStageDescription");
|
||||
|
||||
var bottom = new HorizontalComp(
|
||||
|
|
|
@ -8,9 +8,7 @@ import io.xpipe.app.util.ShellTemp;
|
|||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -36,7 +34,6 @@ public class BrowserTransferModel {
|
|||
BrowserFullSessionModel browserSessionModel;
|
||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||
ObservableBooleanValue empty = Bindings.createBooleanBinding(() -> items.isEmpty(), items);
|
||||
BooleanProperty transferring = new SimpleBooleanProperty();
|
||||
|
||||
public BrowserTransferModel(BrowserFullSessionModel browserSessionModel) {
|
||||
this.browserSessionModel = browserSessionModel;
|
||||
|
@ -50,9 +47,8 @@ public class BrowserTransferModel {
|
|||
}
|
||||
if (toDownload.isPresent()) {
|
||||
downloadSingle(toDownload.get());
|
||||
} else {
|
||||
ThreadHelper.sleep(20);
|
||||
}
|
||||
ThreadHelper.sleep(20);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
@ -130,7 +126,6 @@ public class BrowserTransferModel {
|
|||
}
|
||||
|
||||
try {
|
||||
transferring.setValue(true);
|
||||
var op = new BrowserFileTransferOperation(
|
||||
BrowserLocalFileSystem.getLocalFileEntry(TEMP),
|
||||
List.of(item.getBrowserEntry().getRawFileEntry()),
|
||||
|
@ -155,8 +150,6 @@ public class BrowserTransferModel {
|
|||
synchronized (items) {
|
||||
items.remove(item);
|
||||
}
|
||||
} finally {
|
||||
transferring.setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,15 +154,7 @@ public abstract class Comp<S extends CompStructure<?>> {
|
|||
}
|
||||
|
||||
public Comp<S> disable(ObservableValue<Boolean> o) {
|
||||
return apply(struc -> {
|
||||
var region = struc.get();
|
||||
BindingsHelper.preserve(region, o);
|
||||
o.subscribe(n -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
region.setDisable(n);
|
||||
});
|
||||
});
|
||||
});
|
||||
return apply(struc -> struc.get().disableProperty().bind(o));
|
||||
}
|
||||
|
||||
public Comp<S> padding(Insets insets) {
|
||||
|
|
|
@ -22,6 +22,7 @@ public class AnchorComp extends Comp<CompStructure<AnchorPane>> {
|
|||
for (var c : comps) {
|
||||
pane.getChildren().add(c.createRegion());
|
||||
}
|
||||
pane.setPickOnBounds(false);
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
|
|||
return model.getSelected().getValue().equals(entry);
|
||||
},
|
||||
model.getSelected())));
|
||||
var multi = new MultiContentComp(map, true);
|
||||
var multi = new MultiContentComp(map);
|
||||
multi.styleClass("background");
|
||||
|
||||
var pane = new BorderPane();
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.core.AppFontSizes;
|
|||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
@ -83,7 +82,6 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
|
||||
loaded.subscribe(struc -> {
|
||||
if (struc != null) {
|
||||
TrackEvent.info("Window content node set");
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
anim.stop();
|
||||
struc.prepareAddition();
|
||||
|
@ -92,7 +90,6 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
pane.getStyleClass().remove("background");
|
||||
pane.getChildren().remove(vbox);
|
||||
struc.show();
|
||||
TrackEvent.info("Window content node shown");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -110,6 +107,14 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
}
|
||||
});
|
||||
|
||||
loaded.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(() -> {
|
||||
stage.requestFocus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return pane;
|
||||
});
|
||||
var modal = new ModalOverlayStackComp(bg, overlay);
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ComboTextFieldComp extends Comp<CompStructure<ComboBox<String>>> {
|
|||
});
|
||||
});
|
||||
text.setEditable(true);
|
||||
text.setMaxWidth(20000);
|
||||
text.setMaxWidth(2000);
|
||||
text.setValue(value.getValue() != null ? value.getValue() : null);
|
||||
text.valueProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? n : null);
|
||||
|
|
|
@ -41,7 +41,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
|||
});
|
||||
var filter = new CustomTextField();
|
||||
filter.setMinHeight(0);
|
||||
filter.setMaxHeight(20000);
|
||||
filter.setMaxHeight(2000);
|
||||
filter.getStyleClass().add("filter-comp");
|
||||
filter.promptTextProperty().bind(AppI18n.observable("searchFilter"));
|
||||
filter.rightProperty()
|
||||
|
@ -67,7 +67,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
|||
filterText.subscribe(val -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
clear.setVisible(val != null);
|
||||
if (!Objects.equals(filter.getText(), val) && !(val == null && "".equals(filter.getText()))) {
|
||||
if (!Objects.equals(filter.getText(), val)) {
|
||||
filter.setText(val);
|
||||
}
|
||||
});
|
||||
|
|
29
app/src/main/java/io/xpipe/app/comp/base/GrowPaneComp.java
Normal file
29
app/src/main/java/io/xpipe/app/comp/base/GrowPaneComp.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GrowPaneComp extends Comp<CompStructure<Pane>> {
|
||||
|
||||
private final List<Comp<?>> comps;
|
||||
|
||||
public GrowPaneComp(List<Comp<?>> comps) {
|
||||
this.comps = List.copyOf(comps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<Pane> createBase() {
|
||||
var pane = new BorderPane();
|
||||
for (var c : comps) {
|
||||
pane.setCenter(c.createRegion());
|
||||
}
|
||||
pane.setPickOnBounds(false);
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ public class IntComboFieldComp extends Comp<CompStructure<ComboBox<String>>> {
|
|||
text.setValue(value.getValue() != null ? value.getValue().toString() : null);
|
||||
text.setItems(FXCollections.observableList(
|
||||
predefined.stream().map(integer -> "" + integer).toList()));
|
||||
text.setMaxWidth(20000);
|
||||
text.setMaxWidth(2000);
|
||||
text.getStyleClass().add("int-combo-field-comp");
|
||||
text.setSkin(new ComboBoxListViewSkin<>(text));
|
||||
text.setVisibleRowCount(Math.min(10, predefined.size()));
|
||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.comp.CompStructure;
|
|||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.TextField;
|
||||
|
@ -14,8 +13,6 @@ import javafx.scene.input.KeyEvent;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||
public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
|
@ -37,38 +34,29 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var field = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
var text = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
|
||||
value.addListener((ChangeListener<Number>) (observableValue, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if ((newValue == null && field.getText().isEmpty())
|
||||
|| Objects.equals(field.getText(), newValue != null ? newValue.toString() : null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue == null) {
|
||||
Platform.runLater(() -> {
|
||||
field.setText(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
text.setText("");
|
||||
} else {
|
||||
if (newValue.intValue() < minValue) {
|
||||
value.setValue(minValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() < minValue) {
|
||||
value.setValue(minValue);
|
||||
return;
|
||||
}
|
||||
if (newValue.intValue() > maxValue) {
|
||||
value.setValue(maxValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() > maxValue) {
|
||||
value.setValue(maxValue);
|
||||
return;
|
||||
text.setText(newValue.toString());
|
||||
}
|
||||
|
||||
field.setText(newValue.toString());
|
||||
});
|
||||
});
|
||||
|
||||
field.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
|
||||
text.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
|
||||
if (minValue < 0) {
|
||||
if (!"-0123456789".contains(keyEvent.getCharacter())) {
|
||||
keyEvent.consume();
|
||||
|
@ -80,7 +68,7 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
}
|
||||
});
|
||||
|
||||
field.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
text.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
if (newValue == null
|
||||
|| newValue.isEmpty()
|
||||
|| (minValue < 0 && "-".equals(newValue))
|
||||
|
@ -91,12 +79,12 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
|
||||
int intValue = Integer.parseInt(newValue);
|
||||
if (minValue > intValue || intValue > maxValue) {
|
||||
field.textProperty().setValue(oldValue);
|
||||
text.textProperty().setValue(oldValue);
|
||||
}
|
||||
|
||||
value.setValue(Integer.parseInt(field.textProperty().get()));
|
||||
value.setValue(Integer.parseInt(text.textProperty().get()));
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(field);
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,17 +4,12 @@ import io.xpipe.app.browser.BrowserFullSessionModel;
|
|||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
|
@ -27,7 +22,6 @@ import javafx.scene.layout.VBox;
|
|||
import lombok.Setter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
@ -43,7 +37,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
private final boolean scrollBar;
|
||||
|
||||
@Setter
|
||||
private boolean visibilityControl = false;
|
||||
private int platformPauseInterval = -1;
|
||||
|
||||
public ListBoxViewComp(
|
||||
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
||||
|
@ -62,24 +56,16 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
vbox.setFocusTraversable(false);
|
||||
var scroll = new ScrollPane(vbox);
|
||||
|
||||
refresh(scroll, vbox, shown, all, cache, false);
|
||||
|
||||
var hadScene = new AtomicBoolean(false);
|
||||
scroll.sceneProperty().subscribe(scene -> {
|
||||
if (scene != null) {
|
||||
hadScene.set(true);
|
||||
refresh(scroll, vbox, shown, all, cache, true);
|
||||
}
|
||||
});
|
||||
refresh(scroll, vbox, shown, all, cache, false, false);
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
Platform.runLater(() -> {
|
||||
if (scroll.getScene() == null && hadScene.get()) {
|
||||
return;
|
||||
}
|
||||
refresh(scroll, vbox, c.getList(), all, cache, true, true);
|
||||
});
|
||||
|
||||
refresh(scroll, vbox, c.getList(), all, cache, true);
|
||||
});
|
||||
all.addListener((ListChangeListener<? super T>) c -> {
|
||||
synchronized (cache) {
|
||||
cache.keySet().retainAll(c.getList());
|
||||
}
|
||||
});
|
||||
|
||||
if (scrollBar) {
|
||||
|
@ -106,84 +92,50 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
scroll.setFitToWidth(true);
|
||||
scroll.getStyleClass().add("list-box-view-comp");
|
||||
|
||||
registerVisibilityListeners(scroll, vbox);
|
||||
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private void registerVisibilityListeners(ScrollPane scroll, VBox vbox) {
|
||||
if (!visibilityControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dirty = new SimpleBooleanProperty();
|
||||
var animationTimer = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (!dirty.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateVisibilities(scroll, vbox);
|
||||
dirty.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
scroll.vvalueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
scroll.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
vbox.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
Platform.runLater(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
});
|
||||
|
||||
// We can't directly listen to any parent element changing visibility, so this is a compromise
|
||||
if (AppLayoutModel.get() != null) {
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
});
|
||||
}
|
||||
BrowserFullSessionModel.DEFAULT.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
});
|
||||
if (StoreViewState.get() != null) {
|
||||
StoreViewState.get().getSortMode().addListener((observable, oldValue, newValue) -> {
|
||||
// This is very ugly, but it just takes multiple iterations for the order to apply
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
dirty.set(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
vbox.sceneProperty().addListener((observable, oldValue, newValue) -> {
|
||||
dirty.set(true);
|
||||
|
||||
if (newValue != null) {
|
||||
animationTimer.start();
|
||||
} else {
|
||||
animationTimer.stop();
|
||||
}
|
||||
|
||||
Node c = vbox;
|
||||
do {
|
||||
c.boundsInParentProperty().addListener((change, oldBounds,newBounds) -> {
|
||||
dirty.set(true);
|
||||
while ((c = c.getParent()) != null) {
|
||||
c.boundsInParentProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
// Don't listen to root node changes, that seemingly can cause exceptions
|
||||
} while ((c = c.getParent()) != null && c.getParent() != null);
|
||||
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
if (newValue != null) {
|
||||
newValue.heightProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
dirty.set(true);
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private boolean isVisible(ScrollPane pane, VBox box, Node node) {
|
||||
|
@ -226,20 +178,9 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
}
|
||||
|
||||
private void updateVisibilities(ScrollPane scroll, VBox vbox) {
|
||||
if (!visibilityControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (Node child : vbox.getChildren()) {
|
||||
var v = isVisible(scroll, vbox, child);
|
||||
child.setVisible(v);
|
||||
if (v) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 10) {
|
||||
// System.out.println("Visible: " + count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,42 +190,44 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
List<? extends T> shown,
|
||||
List<? extends T> all,
|
||||
Map<T, Region> cache,
|
||||
boolean asynchronous,
|
||||
boolean refreshVisibilities) {
|
||||
Runnable update = () -> {
|
||||
synchronized (cache) {
|
||||
var set = new HashSet<T>();
|
||||
// These lists might diverge on updates, so add both
|
||||
synchronized (shown) {
|
||||
set.addAll(shown);
|
||||
}
|
||||
synchronized (all) {
|
||||
set.addAll(all);
|
||||
}
|
||||
// These lists might diverge on updates
|
||||
set.addAll(shown);
|
||||
set.addAll(all);
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !set.contains(t));
|
||||
}
|
||||
|
||||
// Use copy to prevent concurrent modifications and to not synchronize to long
|
||||
List<T> shownCopy;
|
||||
synchronized (shown) {
|
||||
shownCopy = new ArrayList<>(shown);
|
||||
}
|
||||
List<Region> newShown = shownCopy.stream().map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
if (comp != null) {
|
||||
var r = comp.createRegion();
|
||||
if (visibilityControl) {
|
||||
r.setVisible(false);
|
||||
final long[] lastPause = {System.currentTimeMillis()};
|
||||
// Create copy to reduce chances of concurrent modification
|
||||
var shownCopy = new ArrayList<>(shown);
|
||||
var newShown = shownCopy.stream()
|
||||
.map(v -> {
|
||||
var elapsed = System.currentTimeMillis() - lastPause[0];
|
||||
if (platformPauseInterval != -1 && elapsed > platformPauseInterval) {
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
lastPause[0] = System.currentTimeMillis();
|
||||
}
|
||||
cache.put(v, r);
|
||||
} else {
|
||||
cache.put(v, null);
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
}).filter(region -> region != null).toList();
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
if (comp != null) {
|
||||
var r = comp.createRegion();
|
||||
r.setVisible(false);
|
||||
cache.put(v, r);
|
||||
} else {
|
||||
cache.put(v, null);
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.toList();
|
||||
|
||||
if (listView.getChildren().equals(newShown)) {
|
||||
return;
|
||||
|
@ -304,6 +247,11 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
updateVisibilities(scroll, listView);
|
||||
}
|
||||
};
|
||||
update.run();
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.comp.base;
|
|||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -16,11 +15,9 @@ import java.util.Map;
|
|||
|
||||
public class MultiContentComp extends SimpleComp {
|
||||
|
||||
private final boolean log;
|
||||
private final Map<Comp<?>, ObservableValue<Boolean>> content;
|
||||
|
||||
public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content, boolean log) {
|
||||
this.log = log;
|
||||
public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content) {
|
||||
this.content = FXCollections.observableMap(content);
|
||||
}
|
||||
|
||||
|
@ -37,14 +34,7 @@ public class MultiContentComp extends SimpleComp {
|
|||
});
|
||||
|
||||
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> e : content.entrySet()) {
|
||||
var name = e.getKey().getClass().getSimpleName();
|
||||
if (log) {
|
||||
TrackEvent.trace("Creating content tab region for element " + name);
|
||||
}
|
||||
var r = e.getKey().createRegion();
|
||||
if (log) {
|
||||
TrackEvent.trace("Created content tab region for element " + name);
|
||||
}
|
||||
e.getValue().subscribe(val -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
r.setManaged(val);
|
||||
|
@ -52,9 +42,6 @@ public class MultiContentComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
m.put(e.getKey(), r);
|
||||
if (log) {
|
||||
TrackEvent.trace("Added content tab region for element " + name);
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
|
|
|
@ -10,8 +10,6 @@ import javafx.beans.property.Property;
|
|||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
|
@ -70,32 +68,23 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
var field = new PasswordField();
|
||||
field.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.isControlDown() && e.getCode() == KeyCode.BACK_SPACE) {
|
||||
var sel = field.getSelection();
|
||||
if (sel.getEnd() > 0) {
|
||||
field.setText(field.getText().substring(sel.getEnd()));
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
field.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
field.textProperty().addListener((c, o, n) -> {
|
||||
var text = new PasswordField();
|
||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? encrypt(n.toCharArray()) : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if ((n == null && field.getText().isEmpty())
|
||||
|| Objects.equals(field.getText(), n != null ? n.getSecretValue() : null)) {
|
||||
if ((n == null && text.getText().isEmpty())
|
||||
|| Objects.equals(text.getText(), n != null ? n.getSecretValue() : null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
field.setText(n != null ? n.getSecretValue() : null);
|
||||
text.setText(n != null ? n.getSecretValue() : null);
|
||||
});
|
||||
});
|
||||
HBox.setHgrow(field, Priority.ALWAYS);
|
||||
HBox.setHgrow(text, Priority.ALWAYS);
|
||||
|
||||
var copyButton = new ButtonComp(null, new FontIcon("mdi2c-clipboard-multiple-outline"), () -> {
|
||||
ClipboardHelper.copyPassword(value.getValue());
|
||||
|
@ -104,7 +93,7 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
.tooltipKey("copyPassword")
|
||||
.createRegion();
|
||||
|
||||
var ig = new InputGroup(field);
|
||||
var ig = new InputGroup(text);
|
||||
ig.setFillHeight(true);
|
||||
ig.getStyleClass().add("secret-field-comp");
|
||||
if (allowCopy) {
|
||||
|
@ -114,10 +103,10 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
|
||||
ig.focusedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
field.requestFocus();
|
||||
text.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
return new Structure(ig, field);
|
||||
return new Structure(ig, text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,9 +76,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
var shortcut = e.combination();
|
||||
b.apply(new TooltipAugment<>(e.name(), shortcut));
|
||||
b.apply(struc -> {
|
||||
AppFontSizes.lg(struc.get());
|
||||
struc.get().setAlignment(Pos.CENTER);
|
||||
|
||||
AppFontSizes.xl(struc.get());
|
||||
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
@ -125,7 +123,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
.tooltipKey("updateAvailableTooltip")
|
||||
.accessibleTextKey("updateAvailableTooltip");
|
||||
b.apply(struc -> {
|
||||
AppFontSizes.lg(struc.get());
|
||||
AppFontSizes.xl(struc.get());
|
||||
});
|
||||
b.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
|
|
|
@ -24,6 +24,7 @@ public class StackComp extends Comp<CompStructure<StackPane>> {
|
|||
pane.getChildren().add(c.createRegion());
|
||||
}
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
pane.setPickOnBounds(false);
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.comp.Comp;
|
|||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
|
@ -58,11 +57,6 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return OsType.getLocal() == OsType.WINDOWS ? 38 : 37;
|
||||
}
|
||||
|
||||
protected Region createContent() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(8);
|
||||
|
|
|
@ -82,8 +82,7 @@ public class OsLogoComp extends SimpleComp {
|
|||
}
|
||||
|
||||
return ICONS.entrySet().stream()
|
||||
.filter(e -> name.toLowerCase().contains(e.getKey()) ||
|
||||
name.toLowerCase().replaceAll("\\s+", "").contains(e.getKey()))
|
||||
.filter(e -> name.toLowerCase().contains(e.getKey()))
|
||||
.findAny()
|
||||
.map(e -> e.getValue())
|
||||
.orElse("os/linux.svg");
|
||||
|
|
|
@ -21,11 +21,6 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 57;
|
||||
}
|
||||
|
||||
private Label createSummary() {
|
||||
var summary = new Label();
|
||||
summary.textProperty().bind(getWrapper().getShownSummary());
|
||||
|
|
|
@ -170,7 +170,6 @@ public class StoreCategoryComp extends SimpleComp {
|
|||
new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper), false);
|
||||
children.styleClass("children");
|
||||
children.minHeight(0);
|
||||
children.setVisibilityControl(true);
|
||||
|
||||
var hide = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
|
|
|
@ -24,7 +24,6 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
@ -200,7 +199,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
selected),
|
||||
() -> {});
|
||||
button.apply(struc -> {
|
||||
struc.get().setMaxWidth(20000);
|
||||
struc.get().setMaxWidth(2000);
|
||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||
Comp<?> graphic = PrettyImageHelper.ofFixedSize(
|
||||
Bindings.createStringBinding(
|
||||
|
@ -225,14 +224,6 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
}
|
||||
event.consume();
|
||||
});
|
||||
struc.get().setOnMouseClicked(event -> {
|
||||
if (event.getButton() != MouseButton.SECONDARY) {
|
||||
return;
|
||||
}
|
||||
|
||||
selected.setValue(mode == Mode.PROXY ? DataStorage.get().local().ref() : null);
|
||||
event.consume();
|
||||
});
|
||||
})
|
||||
.styleClass("choice-comp");
|
||||
|
||||
|
@ -250,7 +241,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
StackPane.setMargin(icon, new Insets(10));
|
||||
pane.setPickOnBounds(false);
|
||||
StackPane.setAlignment(icon, Pos.CENTER_RIGHT);
|
||||
pane.setMaxWidth(20000);
|
||||
pane.setMaxWidth(2000);
|
||||
r.prefWidthProperty().bind(pane.widthProperty());
|
||||
r.maxWidthProperty().bind(pane.widthProperty());
|
||||
return pane;
|
||||
|
|
|
@ -183,10 +183,6 @@ public class StoreCreationComp extends DialogComp {
|
|||
}
|
||||
|
||||
public static void showEdit(DataStoreEntry e) {
|
||||
showEdit(e, dataStoreEntry -> {});
|
||||
}
|
||||
|
||||
public static void showEdit(DataStoreEntry e, Consumer<DataStoreEntry> consumer) {
|
||||
show(
|
||||
e.getName(),
|
||||
e.getProvider(),
|
||||
|
@ -208,7 +204,6 @@ public class StoreCreationComp extends DialogComp {
|
|||
}
|
||||
}
|
||||
}
|
||||
consumer.accept(e);
|
||||
});
|
||||
},
|
||||
true,
|
||||
|
|
|
@ -68,10 +68,10 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
}
|
||||
}
|
||||
|
||||
public static StoreEntryComp customSection(StoreSection e) {
|
||||
public static StoreEntryComp customSection(StoreSection e, boolean topLevel) {
|
||||
var prov = e.getWrapper().getEntry().getProvider();
|
||||
if (prov != null) {
|
||||
return prov.customEntryComp(e, e.getDepth() == 1);
|
||||
return prov.customEntryComp(e, topLevel);
|
||||
} else {
|
||||
var forceCondensed = AppPrefs.get() != null
|
||||
&& AppPrefs.get().condenseConnectionDisplay().get();
|
||||
|
@ -81,8 +81,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
|
||||
public abstract boolean isFullSize();
|
||||
|
||||
public abstract int getHeight();
|
||||
|
||||
@Override
|
||||
protected final Region createSimple() {
|
||||
var r = createContent();
|
||||
|
@ -359,7 +357,9 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
getWrapper().moveTo(storeCategoryWrapper.getCategory());
|
||||
event.consume();
|
||||
});
|
||||
if (storeCategoryWrapper.getParent() == null) {
|
||||
if (storeCategoryWrapper.getParent() == null
|
||||
|| storeCategoryWrapper.equals(
|
||||
getWrapper().getCategory().getValue())) {
|
||||
m.setDisable(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,11 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
.getAllChildren()
|
||||
.getList(),
|
||||
(StoreSection e) -> {
|
||||
var custom = StoreSection.customSection(e).hgrow();
|
||||
var custom = StoreSection.customSection(e, true).hgrow();
|
||||
return custom;
|
||||
},
|
||||
true);
|
||||
content.setVisibilityControl(true);
|
||||
content.setPlatformPauseInterval(50);
|
||||
content.apply(struc -> {
|
||||
// Reset scroll
|
||||
StoreViewState.get().getActiveCategory().addListener((observable, oldValue, newValue) -> {
|
||||
|
@ -142,6 +142,6 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro);
|
||||
map.put(new StoreIdentitiesIntroComp(), showIdentitiesIntro);
|
||||
|
||||
return new MultiContentComp(map, false).createRegion();
|
||||
return new MultiContentComp(map).createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,23 @@ import java.util.function.Function;
|
|||
|
||||
public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
|
||||
private final Property<StoreSortMode> sortMode;
|
||||
|
||||
public StoreEntryListOverviewComp() {
|
||||
this.sortMode = new SimpleObjectProperty<>();
|
||||
StoreViewState.get().getActiveCategory().subscribe(val -> {
|
||||
sortMode.setValue(val.getSortMode().getValue());
|
||||
});
|
||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||
var cat = StoreViewState.get().getActiveCategory().getValue();
|
||||
if (cat == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cat.getSortMode().setValue(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
private Region createGroupListHeader() {
|
||||
var label = new Label();
|
||||
var name = BindingsHelper.flatMap(
|
||||
|
@ -125,7 +142,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private Comp<?> createAlphabeticalSortButton() {
|
||||
var sortMode = StoreViewState.get().getSortMode();
|
||||
var icon = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
|
||||
|
@ -166,7 +182,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private Comp<?> createDateSortButton() {
|
||||
var sortMode = StoreViewState.get().getSortMode();
|
||||
var icon = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
|
||||
|
|
|
@ -32,6 +32,7 @@ public class StoreEntryWrapper {
|
|||
private final Property<String> name;
|
||||
private final DataStoreEntry entry;
|
||||
private final Property<Instant> lastAccess;
|
||||
private final Property<Instant> lastAccessApplied = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty disabled = new SimpleBooleanProperty();
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
private final Property<DataStoreEntry.Validity> validity = new SimpleObjectProperty<>();
|
||||
|
@ -103,6 +104,10 @@ public class StoreEntryWrapper {
|
|||
setupListeners();
|
||||
}
|
||||
|
||||
public void applyLastAccess() {
|
||||
this.lastAccessApplied.setValue(lastAccess.getValue());
|
||||
}
|
||||
|
||||
public void moveTo(DataStoreCategory category) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().moveEntryToCategory(entry, category);
|
||||
|
@ -125,7 +130,8 @@ public class StoreEntryWrapper {
|
|||
|
||||
public void delete() {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().deleteWithChildren(this.entry);
|
||||
DataStorage.get().deleteChildren(this.entry);
|
||||
DataStorage.get().deleteStoreEntry(this.entry);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,10 @@ import io.xpipe.app.core.AppI18n;
|
|||
import io.xpipe.app.icon.SystemIcon;
|
||||
import io.xpipe.app.icon.SystemIconCache;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
|
@ -108,29 +106,19 @@ public class StoreIconChoiceComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private void updateData(TableView<List<SystemIcon>> table, String filterString) {
|
||||
var available = icons.stream()
|
||||
.filter(systemIcon -> AppImages.hasNormalImage("icons/" + systemIcon.getSource().getId() + "/" + systemIcon.getId() + "-40.png"))
|
||||
.sorted(Comparator.comparing(systemIcon -> systemIcon.getId()))
|
||||
.toList();
|
||||
table.getPlaceholder().setVisible(available.size() == 0);
|
||||
var filtered = available;
|
||||
if (filterString != null && !filterString.isBlank() && filterString.length() >= 2) {
|
||||
filtered = available.stream().filter(icon -> containsString(icon.getId(), filterString)).toList();
|
||||
}
|
||||
var data = partitionList(filtered, columns);
|
||||
table.getItems().setAll(data);
|
||||
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < 2
|
||||
? icons.stream()
|
||||
.sorted(Comparator.comparing(systemIcon -> systemIcon.getId()))
|
||||
.toList()
|
||||
: icons.stream()
|
||||
.filter(icon -> containsString(icon.getId(), filterString))
|
||||
.toList();
|
||||
|
||||
var selectMatch = filtered.size() == 1 || filtered.stream().anyMatch(systemIcon -> systemIcon.getId().equals(filterString));
|
||||
// Table updates seem to not always be instant, sometimes the column is not there yet
|
||||
if (selectMatch && table.getColumns().size() > 0) {
|
||||
table.getSelectionModel().select(0, table.getColumns().getFirst());
|
||||
selected.setValue(filtered.getFirst());
|
||||
} else {
|
||||
selected.setValue(null);
|
||||
}
|
||||
var data = partitionList(displayedIcons, columns);
|
||||
table.getItems().setAll(data);
|
||||
}
|
||||
|
||||
private <T> List<List<T>> partitionList(List<T> list, int size) {
|
||||
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
|
||||
List<List<T>> partitions = new ArrayList<>();
|
||||
if (list.size() == 0) {
|
||||
return partitions;
|
||||
|
|
|
@ -53,12 +53,12 @@ public class StoreSection {
|
|||
}
|
||||
}
|
||||
|
||||
public static Comp<?> customSection(StoreSection e) {
|
||||
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
|
||||
var prov = e.getWrapper().getEntry().getProvider();
|
||||
if (prov != null) {
|
||||
return prov.customSectionComp(e);
|
||||
return prov.customSectionComp(e, topLevel);
|
||||
} else {
|
||||
return new StoreSectionComp(e);
|
||||
return new StoreSectionComp(e, topLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class StoreSection {
|
|||
|
||||
var current = mappedSortMode.getValue();
|
||||
if (current != null) {
|
||||
return current.comparator().compare(o1, o2);
|
||||
return current.comparator().compare(current.representative(o1), current.representative(o2));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class StoreSectionBaseComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
private static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
|
||||
private static final PseudoClass TOP = PseudoClass.getPseudoClass("top");
|
||||
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
|
||||
protected final StoreSection section;
|
||||
|
||||
public StoreSectionBaseComp(StoreSection section) {
|
||||
this.section = section;
|
||||
}
|
||||
|
||||
protected ObservableBooleanValue effectiveExpanded(ObservableBooleanValue expanded) {
|
||||
return section.getWrapper() != null ? Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return expanded.get()
|
||||
&& section.getShownChildren().getList().size() > 0;
|
||||
},
|
||||
expanded,
|
||||
section.getShownChildren().getList()) : new SimpleBooleanProperty(true);
|
||||
}
|
||||
|
||||
protected void addPseudoClassListeners(VBox vbox, ObservableBooleanValue expanded) {
|
||||
var observable = effectiveExpanded(expanded);
|
||||
BindingsHelper.preserve(this, observable);
|
||||
observable.subscribe(val -> {
|
||||
vbox.pseudoClassStateChanged(EXPANDED, val);
|
||||
});
|
||||
|
||||
vbox.pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||
vbox.pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
|
||||
vbox.pseudoClassStateChanged(ROOT, section.getDepth() == 0);
|
||||
vbox.pseudoClassStateChanged(SUB, section.getDepth() > 1);
|
||||
vbox.pseudoClassStateChanged(TOP, section.getDepth() == 1);
|
||||
|
||||
if (section.getWrapper() != null) {
|
||||
if (section.getDepth() == 1) {
|
||||
section.getWrapper().getColor().subscribe(val -> {
|
||||
var newList = new ArrayList<>(vbox.getStyleClass());
|
||||
newList.removeIf(s -> Arrays.stream(DataColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
|
||||
newList.remove("gray");
|
||||
newList.add("color-box");
|
||||
if (val != null) {
|
||||
newList.add(val.getId());
|
||||
} else {
|
||||
newList.add("gray");
|
||||
}
|
||||
vbox.getStyleClass().setAll(newList);
|
||||
});
|
||||
}
|
||||
|
||||
section.getWrapper().getPerUser().subscribe(val -> {
|
||||
vbox.pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void addVisibilityListeners(VBox root, HBox hbox) {
|
||||
var children = new ArrayList<>(hbox.getChildren());
|
||||
hbox.getChildren().clear();
|
||||
root.visibleProperty().subscribe((newValue) -> {
|
||||
if (newValue) {
|
||||
hbox.getChildren().addAll(children);
|
||||
} else {
|
||||
hbox.getChildren().removeAll(children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected ListBoxViewComp<StoreSection> createChildrenList(Function<StoreSection, Comp<?>> function, ObservableBooleanValue hide) {
|
||||
var content = new ListBoxViewComp<>(
|
||||
section.getShownChildren().getList(),
|
||||
section.getAllChildren().getList(),
|
||||
(StoreSection e) -> {
|
||||
return function.apply(e).grow(true, false);
|
||||
},
|
||||
section.getWrapper() == null);
|
||||
content.setVisibilityControl(true);
|
||||
content.minHeight(0);
|
||||
content.hgrow();
|
||||
content.styleClass("children-content");
|
||||
content.hide(hide);
|
||||
return content;
|
||||
}
|
||||
|
||||
protected Comp<CompStructure<Button>> createExpandButton(Runnable action, int width, ObservableBooleanValue expanded) {
|
||||
var icon = Bindings.createObjectBinding(() -> new LabelGraphic.IconGraphic(
|
||||
expanded.get() && section.getShownChildren().getList().size() > 0 ?
|
||||
"mdal-keyboard_arrow_down" :
|
||||
"mdal-keyboard_arrow_right"), expanded, section.getShownChildren().getList());
|
||||
var expandButton = new IconButtonComp(icon,
|
||||
action);
|
||||
expandButton
|
||||
.minWidth(width)
|
||||
.prefWidth(width)
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.styleClass("expand-button")
|
||||
.maxHeight(100);
|
||||
return expandButton;
|
||||
}
|
||||
|
||||
protected Comp<CompStructure<Button>> createQuickAccessButton(int width, Consumer<StoreSection> r) {
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
},
|
||||
section.getShownChildren().getList());
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, r)
|
||||
.styleClass("quick-access-button")
|
||||
.minWidth(width)
|
||||
.prefWidth(width)
|
||||
.maxHeight(100)
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Access " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(quickAccessDisabled);
|
||||
return quickAccessButton;
|
||||
}
|
||||
}
|
|
@ -12,14 +12,11 @@ import io.xpipe.app.util.LabelGraphic;
|
|||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -27,24 +24,103 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StoreSectionComp extends StoreSectionBaseComp {
|
||||
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
public StoreSectionComp(StoreSection section) {
|
||||
super(section);
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
|
||||
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
private final StoreSection section;
|
||||
private final boolean topLevel;
|
||||
|
||||
public StoreSectionComp(StoreSection section, boolean topLevel) {
|
||||
this.section = section;
|
||||
this.topLevel = topLevel;
|
||||
}
|
||||
|
||||
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
},
|
||||
section.getShownChildren().getList());
|
||||
Consumer<StoreSection> quickAccessAction = w -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
w.getWrapper().executeDefaultAction();
|
||||
});
|
||||
};
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||
.vgrow()
|
||||
.styleClass("quick-access-button")
|
||||
.apply(struc -> struc.get().setMinWidth(30))
|
||||
.apply(struc -> struc.get().setPrefWidth(30))
|
||||
.maxHeight(100)
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Access " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(quickAccessDisabled)
|
||||
.focusTraversableForAccessibility()
|
||||
.tooltipKey("accessSubConnections", new KeyCodeCombination(KeyCode.RIGHT));
|
||||
return quickAccessButton;
|
||||
}
|
||||
|
||||
private Comp<CompStructure<Button>> createExpandButton() {
|
||||
var expandButton = new IconButtonComp(
|
||||
Bindings.createObjectBinding(
|
||||
() -> new LabelGraphic.IconGraphic(
|
||||
section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren()
|
||||
.getList()
|
||||
.size()
|
||||
> 0
|
||||
? "mdal-keyboard_arrow_down"
|
||||
: "mdal-keyboard_arrow_right"),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren().getList()),
|
||||
() -> {
|
||||
section.getWrapper().toggleExpanded();
|
||||
});
|
||||
expandButton
|
||||
.apply(struc -> struc.get().setMinWidth(30))
|
||||
.apply(struc -> struc.get().setPrefWidth(30))
|
||||
.focusTraversableForAccessibility()
|
||||
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE))
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.styleClass("expand-button")
|
||||
.maxHeight(100)
|
||||
.vgrow();
|
||||
return expandButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var entryButton = StoreEntryComp.customSection(section);
|
||||
entryButton.hgrow();
|
||||
entryButton.apply(struc -> {
|
||||
struc.get().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
var entryButton = StoreEntryComp.customSection(section, topLevel);
|
||||
var quickAccessButton = createQuickAccessButton();
|
||||
var expandButton = createExpandButton();
|
||||
var buttonList = new ArrayList<Comp<?>>();
|
||||
if (entryButton.isFullSize()) {
|
||||
buttonList.add(quickAccessButton);
|
||||
}
|
||||
buttonList.add(expandButton);
|
||||
var buttons = new VerticalComp(buttonList);
|
||||
var topEntryList = new HorizontalComp(List.of(buttons, entryButton.hgrow()));
|
||||
topEntryList.apply(struc -> {
|
||||
var mainButton = struc.get().getChildren().get(1);
|
||||
mainButton.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (event.getCode() == KeyCode.SPACE) {
|
||||
section.getWrapper().toggleExpanded();
|
||||
event.consume();
|
||||
}
|
||||
if (event.getCode() == KeyCode.RIGHT) {
|
||||
var ref = (VBox) ((HBox) struc.get().getParent()).getChildren().getFirst();
|
||||
var ref = (VBox) struc.get().getChildren().getFirst();
|
||||
if (entryButton.isFullSize()) {
|
||||
var btn = (Button) ref.getChildren().getFirst();
|
||||
btn.fire();
|
||||
|
@ -54,45 +130,72 @@ public class StoreSectionComp extends StoreSectionBaseComp {
|
|||
});
|
||||
});
|
||||
|
||||
var quickAccessButton = createQuickAccessButton(30, c -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
c.getWrapper().executeDefaultAction();
|
||||
});
|
||||
});
|
||||
quickAccessButton.vgrow();
|
||||
quickAccessButton.focusTraversableForAccessibility();
|
||||
quickAccessButton.tooltipKey("accessSubConnections", new KeyCodeCombination(KeyCode.RIGHT));
|
||||
|
||||
var expandButton = createExpandButton(() -> section.getWrapper().toggleExpanded(), 30, section.getWrapper().getExpanded());
|
||||
expandButton.vgrow();
|
||||
expandButton.focusTraversableForAccessibility();
|
||||
expandButton.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
|
||||
var buttonList = new ArrayList<Comp<?>>();
|
||||
if (entryButton.isFullSize()) {
|
||||
buttonList.add(quickAccessButton);
|
||||
}
|
||||
buttonList.add(expandButton);
|
||||
var buttons = new VerticalComp(buttonList);
|
||||
var topEntryList = new HorizontalComp(List.of(buttons, entryButton));
|
||||
topEntryList.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
topEntryList.minHeight(entryButton.getHeight());
|
||||
topEntryList.maxHeight(entryButton.getHeight());
|
||||
topEntryList.prefHeight(entryButton.getHeight());
|
||||
|
||||
var effectiveExpanded = effectiveExpanded(section.getWrapper().getExpanded());
|
||||
var content = createChildrenList(c -> StoreSection.customSection(c), Bindings.not(effectiveExpanded));
|
||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = section.getShownChildren()
|
||||
.filtered(
|
||||
storeSection -> section.getAllChildren().getList().size() <= 20
|
||||
|| section.getWrapper().getExpanded().get(),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getAllChildren().getList());
|
||||
var content = new ListBoxViewComp<>(
|
||||
listSections.getList(),
|
||||
section.getAllChildren().getList(),
|
||||
(StoreSection e) -> {
|
||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||
},
|
||||
false);
|
||||
content.minHeight(0).hgrow();
|
||||
|
||||
var expanded = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().getList().size() > 0;
|
||||
},
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren().getList());
|
||||
var full = new VerticalComp(List.of(
|
||||
topEntryList,
|
||||
Comp.separator().hide(Bindings.not(effectiveExpanded)),
|
||||
content));
|
||||
full.styleClass("store-entry-section-comp");
|
||||
full.apply(struc -> {
|
||||
Comp.separator().hide(expanded.not()),
|
||||
content.styleClass("children-content")
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(section.getWrapper().getExpanded()),
|
||||
Bindings.size(section.getShownChildren().getList())
|
||||
.isEqualTo(0)))));
|
||||
return full.styleClass("store-entry-section-comp")
|
||||
.apply(struc -> {
|
||||
struc.get().setFillWidth(true);
|
||||
var hbox = ((HBox) struc.get().getChildren().getFirst());
|
||||
addPseudoClassListeners(struc.get(), section.getWrapper().getExpanded());
|
||||
addVisibilityListeners(struc.get(), hbox);
|
||||
});
|
||||
return full.createStructure();
|
||||
expanded.subscribe(val -> {
|
||||
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
||||
});
|
||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
|
||||
struc.get().pseudoClassStateChanged(ROOT, topLevel);
|
||||
struc.get().pseudoClassStateChanged(SUB, !topLevel);
|
||||
|
||||
section.getWrapper().getColor().subscribe(val -> {
|
||||
if (!topLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newList = new ArrayList<>(struc.get().getStyleClass());
|
||||
newList.removeIf(s -> Arrays.stream(DataColor.values())
|
||||
.anyMatch(
|
||||
dataStoreColor -> dataStoreColor.getId().equals(s)));
|
||||
newList.remove("gray");
|
||||
newList.add("color-box");
|
||||
if (val != null) {
|
||||
newList.add(val.getId());
|
||||
} else {
|
||||
newList.add("gray");
|
||||
}
|
||||
struc.get().getStyleClass().setAll(newList);
|
||||
});
|
||||
|
||||
section.getWrapper().getPerUser().subscribe(val -> {
|
||||
struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val);
|
||||
});
|
||||
})
|
||||
.createStructure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,23 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StoreSectionMiniComp extends StoreSectionBaseComp {
|
||||
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
private final BooleanProperty expanded;
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
|
||||
private static final PseudoClass TOP = PseudoClass.getPseudoClass("top");
|
||||
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
|
||||
|
||||
private final StoreSection section;
|
||||
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment;
|
||||
private final Consumer<StoreSection> action;
|
||||
|
||||
|
@ -31,61 +36,142 @@ public class StoreSectionMiniComp extends StoreSectionBaseComp {
|
|||
StoreSection section,
|
||||
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment,
|
||||
Consumer<StoreSection> action) {
|
||||
super(section);
|
||||
this.section = section;
|
||||
this.augment = augment;
|
||||
this.action = action;
|
||||
this.expanded = new SimpleBooleanProperty(section.getWrapper() == null || section.getWrapper().getExpanded().getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var list = new ArrayList<Comp<?>>();
|
||||
BooleanProperty expanded;
|
||||
if (section.getWrapper() != null) {
|
||||
var root = new ButtonComp(section.getWrapper().getShownName(), () -> {
|
||||
action.accept(section);
|
||||
});
|
||||
root.hgrow();
|
||||
root.maxWidth(2000);
|
||||
root.styleClass("item");
|
||||
root.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||
struc.get().setGraphic(PrettyImageHelper.ofFixedSize(
|
||||
var root = new ButtonComp(section.getWrapper().getShownName(), () -> {})
|
||||
.apply(struc -> {
|
||||
struc.get()
|
||||
.setGraphic(PrettyImageHelper.ofFixedSize(
|
||||
section.getWrapper().getIconFile(), 16, 16)
|
||||
.createRegion());
|
||||
struc.get().setMnemonicParsing(false);
|
||||
});
|
||||
})
|
||||
.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||
})
|
||||
.apply(struc -> {
|
||||
struc.get().setOnAction(event -> {
|
||||
action.accept(section);
|
||||
event.consume();
|
||||
});
|
||||
})
|
||||
.grow(true, false)
|
||||
.apply(struc -> struc.get().setMnemonicParsing(false))
|
||||
.styleClass("item");
|
||||
augment.accept(section, root);
|
||||
|
||||
var expandButton = createExpandButton(() -> expanded.set(!expanded.get()), 20, expanded);
|
||||
expandButton.focusTraversable();
|
||||
expanded =
|
||||
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().getList().size() > 0);
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createObjectBinding(
|
||||
() -> new LabelGraphic.IconGraphic(
|
||||
expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right"),
|
||||
expanded),
|
||||
() -> {
|
||||
expanded.set(!expanded.get());
|
||||
})
|
||||
.apply(struc -> struc.get().setMinWidth(20))
|
||||
.apply(struc -> struc.get().setPrefWidth(20))
|
||||
.focusTraversable()
|
||||
.accessibleText(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return "Expand "
|
||||
+ section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.grow(false, true)
|
||||
.styleClass("expand-button");
|
||||
|
||||
var quickAccessButton = createQuickAccessButton(20, action);
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
},
|
||||
section.getShownChildren().getList());
|
||||
Consumer<StoreSection> quickAccessAction = action;
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||
.vgrow()
|
||||
.styleClass("quick-access-button")
|
||||
.maxHeight(100)
|
||||
.disable(quickAccessDisabled);
|
||||
|
||||
var buttonList = new ArrayList<Comp<?>>();
|
||||
buttonList.add(expandButton);
|
||||
buttonList.add(button);
|
||||
buttonList.add(root);
|
||||
if (section.getDepth() == 1) {
|
||||
buttonList.add(quickAccessButton);
|
||||
}
|
||||
var h = new HorizontalComp(buttonList);
|
||||
h.apply(struc -> struc.get().setFillHeight(true));
|
||||
h.prefHeight(28);
|
||||
list.add(h);
|
||||
list.add(new HorizontalComp(buttonList).apply(struc -> struc.get().setFillHeight(true)));
|
||||
} else {
|
||||
expanded = new SimpleBooleanProperty(true);
|
||||
}
|
||||
|
||||
var content = createChildrenList(c -> new StoreSectionMiniComp(c, this.augment, this.action), Bindings.not(expanded));
|
||||
list.add(content);
|
||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = section.getWrapper() != null
|
||||
? section.getShownChildren()
|
||||
.filtered(
|
||||
storeSection ->
|
||||
section.getAllChildren().getList().size() <= 20 || expanded.get(),
|
||||
expanded,
|
||||
section.getAllChildren().getList())
|
||||
: section.getShownChildren();
|
||||
var content = new ListBoxViewComp<>(
|
||||
listSections.getList(),
|
||||
section.getAllChildren().getList(),
|
||||
(StoreSection e) -> {
|
||||
return new StoreSectionMiniComp(e, this.augment, this.action);
|
||||
},
|
||||
section.getWrapper() == null)
|
||||
.minHeight(0)
|
||||
.hgrow();
|
||||
|
||||
var full = new VerticalComp(list);
|
||||
full.styleClass("store-section-mini-comp");
|
||||
full.apply(struc -> {
|
||||
list.add(content.styleClass("children-content")
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(expanded),
|
||||
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
|
||||
|
||||
var vert = new VerticalComp(list);
|
||||
return vert.styleClass("store-section-mini-comp")
|
||||
.apply(struc -> {
|
||||
struc.get().setFillWidth(true);
|
||||
addPseudoClassListeners(struc.get(), expanded);
|
||||
expanded.subscribe(val -> {
|
||||
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
||||
});
|
||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
|
||||
struc.get().pseudoClassStateChanged(ROOT, section.getDepth() == 0);
|
||||
struc.get().pseudoClassStateChanged(TOP, section.getDepth() == 1);
|
||||
struc.get().pseudoClassStateChanged(SUB, section.getDepth() > 1);
|
||||
})
|
||||
.apply(struc -> {
|
||||
if (section.getWrapper() != null) {
|
||||
var hbox = ((HBox) struc.get().getChildren().getFirst());
|
||||
addVisibilityListeners(struc.get(), hbox);
|
||||
section.getWrapper().getColor().subscribe(val -> {
|
||||
if (section.getDepth() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataColor.values())
|
||||
.anyMatch(dataStoreColor ->
|
||||
dataStoreColor.getId().equals(s)));
|
||||
struc.get().getStyleClass().remove("gray");
|
||||
struc.get().getStyleClass().add("color-box");
|
||||
if (val != null) {
|
||||
struc.get().getStyleClass().add(val.getId());
|
||||
} else {
|
||||
struc.get().getStyleClass().add("gray");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return full.createStructure();
|
||||
})
|
||||
.createStructure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface StoreSortMode {
|
||||
|
||||
StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
|
||||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
|
@ -20,6 +27,11 @@ public interface StoreSortMode {
|
|||
}
|
||||
};
|
||||
StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
|
||||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "alphabetical-asc";
|
||||
|
@ -32,10 +44,10 @@ public interface StoreSortMode {
|
|||
.reversed();
|
||||
}
|
||||
};
|
||||
StoreSortMode DATE_DESC = new StoreSortMode.DateSortMode() {
|
||||
StoreSortMode DATE_DESC = new StoreSortMode() {
|
||||
|
||||
protected Instant date(StoreSection s) {
|
||||
var la = s.getWrapper().getLastAccess().getValue();
|
||||
private Instant date(StoreSection s) {
|
||||
var la = s.getWrapper().getLastAccessApplied().getValue();
|
||||
if (la == null) {
|
||||
return Instant.MAX;
|
||||
}
|
||||
|
@ -44,19 +56,35 @@ public interface StoreSortMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected int compare(Instant s1, Instant s2) {
|
||||
return s1.compareTo(s2);
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().getList().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
.isUsable())
|
||||
.map(this::representative),
|
||||
Stream.of(s))
|
||||
.max(Comparator.comparing(section -> date(section)))
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "date-desc";
|
||||
}
|
||||
};
|
||||
StoreSortMode DATE_ASC = new StoreSortMode.DateSortMode() {
|
||||
|
||||
protected Instant date(StoreSection s) {
|
||||
var la = s.getWrapper().getLastAccess().getValue();
|
||||
@Override
|
||||
public Comparator<StoreSection> comparator() {
|
||||
return Comparator.comparing(e -> {
|
||||
return date(e);
|
||||
});
|
||||
}
|
||||
};
|
||||
StoreSortMode DATE_ASC = new StoreSortMode() {
|
||||
|
||||
private Instant date(StoreSection s) {
|
||||
var la = s.getWrapper().getLastAccessApplied().getValue();
|
||||
if (la == null) {
|
||||
return Instant.MIN;
|
||||
}
|
||||
|
@ -65,16 +93,32 @@ public interface StoreSortMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected int compare(Instant s1, Instant s2) {
|
||||
return s2.compareTo(s1);
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().getList().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
.isUsable())
|
||||
.map(this::representative),
|
||||
Stream.of(s))
|
||||
.max(Comparator.comparing(section -> date(section)))
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "date-asc";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Comparator<StoreSection> comparator() {
|
||||
return Comparator.<StoreSection, Instant>comparing(e -> {
|
||||
return date(e);
|
||||
})
|
||||
.reversed();
|
||||
}
|
||||
};
|
||||
List<StoreSortMode> ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC);
|
||||
|
||||
static Optional<StoreSortMode> fromId(String id) {
|
||||
|
@ -87,54 +131,9 @@ public interface StoreSortMode {
|
|||
return DATE_ASC;
|
||||
}
|
||||
|
||||
StoreSection representative(StoreSection s);
|
||||
|
||||
String getId();
|
||||
|
||||
Comparator<StoreSection> comparator();
|
||||
|
||||
abstract class DateSortMode implements StoreSortMode {
|
||||
|
||||
private int entriesListOberservableIndex = -1;
|
||||
private final Map<StoreSection, StoreSection> cachedRepresentatives = new IdentityHashMap<>();
|
||||
|
||||
private StoreSection computeRepresentative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().getList().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
.isUsable())
|
||||
.map(this::getRepresentative),
|
||||
Stream.of(s))
|
||||
.max(Comparator.comparing(section -> date(section)))
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private StoreSection getRepresentative(StoreSection s) {
|
||||
if (StoreViewState.get().getEntriesListUpdateObservable().get() != entriesListOberservableIndex) {
|
||||
cachedRepresentatives.clear();
|
||||
entriesListOberservableIndex = StoreViewState.get().getEntriesListUpdateObservable().get();
|
||||
}
|
||||
|
||||
if (cachedRepresentatives.containsKey(s)) {
|
||||
return cachedRepresentatives.get(s);
|
||||
}
|
||||
|
||||
var r = computeRepresentative(s);
|
||||
cachedRepresentatives.put(s, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
protected abstract Instant date(StoreSection s);
|
||||
|
||||
protected abstract int compare(Instant s1, Instant s2);
|
||||
|
||||
@Override
|
||||
public Comparator<StoreSection> comparator() {
|
||||
return (o1, o2) -> {
|
||||
var r1 = getRepresentative(o1);
|
||||
var r2 = getRepresentative(o2);
|
||||
return DateSortMode.this.compare(date(r1), date(r2));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,6 @@ public class StoreViewState {
|
|||
@Getter
|
||||
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
||||
|
||||
@Getter
|
||||
private final Property<StoreSortMode> sortMode = new SimpleObjectProperty<>();
|
||||
|
||||
@Getter
|
||||
private StoreSection currentTopLevelSection;
|
||||
|
||||
|
@ -121,23 +118,15 @@ public class StoreViewState {
|
|||
.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||
.map(StoreEntryWrapper::new)
|
||||
.toList()));
|
||||
allEntries.getList().forEach(e -> e.applyLastAccess());
|
||||
categories
|
||||
.getList()
|
||||
.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
||||
.map(StoreCategoryWrapper::new)
|
||||
.toList()));
|
||||
|
||||
sortMode.addListener((observable, oldValue, newValue) -> {
|
||||
var cat = getActiveCategory().getValue();
|
||||
if (cat == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cat.getSortMode().setValue(newValue);
|
||||
});
|
||||
activeCategory.addListener((observable, oldValue, newValue) -> {
|
||||
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
||||
sortMode.setValue(newValue.getSortMode().getValue());
|
||||
});
|
||||
var selected = AppCache.getNonNull("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
||||
activeCategory.setValue(categories.getList().stream()
|
||||
|
@ -152,6 +141,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public void updateDisplay() {
|
||||
allEntries.getList().forEach(e -> e.applyLastAccess());
|
||||
toggleStoreListUpdate();
|
||||
}
|
||||
|
||||
|
@ -190,6 +180,7 @@ public class StoreViewState {
|
|||
var l = Arrays.stream(entry)
|
||||
.map(StoreEntryWrapper::new)
|
||||
.peek(storeEntryWrapper -> storeEntryWrapper.update())
|
||||
.peek(wrapper -> wrapper.applyLastAccess())
|
||||
.toList();
|
||||
|
||||
// Don't update anything if we have already reset
|
||||
|
|
|
@ -20,6 +20,10 @@ public class AppFont {
|
|||
// Load ikonli fonts
|
||||
TrackEvent.info("Loading ikonli fonts ...");
|
||||
new FontIcon("mdi2s-stop");
|
||||
new FontIcon("mdi2m-magnify");
|
||||
new FontIcon("mdi2d-database-plus");
|
||||
new FontIcon("mdi2p-professional-hexagon");
|
||||
new FontIcon("mdi2c-chevron-double-right");
|
||||
|
||||
TrackEvent.info("Loading bundled fonts ...");
|
||||
AppResources.with(
|
||||
|
|
|
@ -56,7 +56,10 @@ public class AppInstance {
|
|||
try {
|
||||
var inputs = AppProperties.get().getArguments().getOpenArgs();
|
||||
// Assume that we want to open the GUI if we launched again
|
||||
client.get().performRequest(DaemonFocusExchange.Request.builder().build());
|
||||
client.get()
|
||||
.performRequest(DaemonFocusExchange.Request.builder()
|
||||
.mode(XPipeDaemonMode.GUI)
|
||||
.build());
|
||||
if (!inputs.isEmpty()) {
|
||||
client.get()
|
||||
.performRequest(DaemonOpenExchange.Request.builder()
|
||||
|
|
|
@ -19,7 +19,6 @@ import javafx.application.ColorScheme;
|
|||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
@ -84,40 +83,33 @@ public class AppTheme {
|
|||
|
||||
public static void init() {
|
||||
if (init) {
|
||||
TrackEvent.trace("Theme init requested again");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppPrefs.get() == null) {
|
||||
TrackEvent.trace("Theme init prior to prefs init, setting theme to default");
|
||||
Theme.getDefaultLightTheme().apply();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var lastSystemDark = AppCache.getBoolean("lastDarkTheme", false);
|
||||
var nowDark = isDarkMode();
|
||||
var nowDark = Platform.getPreferences().getColorScheme() == ColorScheme.DARK;
|
||||
AppCache.update("lastDarkTheme", nowDark);
|
||||
if (AppPrefs.get().theme().getValue() == null || lastSystemDark != nowDark) {
|
||||
TrackEvent.trace("Updating theme to system theme");
|
||||
setDefault();
|
||||
}
|
||||
|
||||
Platform.getPreferences().addListener((MapChangeListener<? super String, ? super Object>) change -> {
|
||||
TrackEvent.withTrace("Platform preference changed").tag("change", change.toString()).handle();
|
||||
});
|
||||
|
||||
Platform.getPreferences().addListener((MapChangeListener<? super String, ? super Object>) change -> {
|
||||
if (change.getKey().equals("GTK.theme_name")) {
|
||||
Platform.runLater(() -> {
|
||||
updateThemeToThemeName(change.getValueRemoved(), change.getValueAdded());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> {
|
||||
Platform.runLater(() -> {
|
||||
updateThemeToColorScheme(t1);
|
||||
if (t1 == ColorScheme.DARK
|
||||
&& !AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||
}
|
||||
|
||||
if (t1 != ColorScheme.DARK
|
||||
&& AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (IllegalStateException ex) {
|
||||
|
@ -138,52 +130,12 @@ public class AppTheme {
|
|||
init = true;
|
||||
}
|
||||
|
||||
private static void updateThemeToThemeName(Object oldName, Object newName) {
|
||||
if (OsType.getLocal() == OsType.LINUX && newName != null) {
|
||||
var toDark = (oldName == null || !oldName.toString().contains("-dark")) &&
|
||||
newName.toString().contains("-dark");
|
||||
var toLight = (oldName == null || oldName.toString().contains("-dark")) &&
|
||||
!newName.toString().contains("-dark");
|
||||
if (toDark) {
|
||||
updateThemeToColorScheme(ColorScheme.DARK);
|
||||
} else if (toLight) {
|
||||
updateThemeToColorScheme(ColorScheme.LIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDarkMode() {
|
||||
var nowDark = Platform.getPreferences().getColorScheme() == ColorScheme.DARK;
|
||||
if (nowDark) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var gtkTheme = Platform.getPreferences().get("GTK.theme_name");
|
||||
return gtkTheme != null && gtkTheme.toString().contains("-dark");
|
||||
}
|
||||
|
||||
private static void updateThemeToColorScheme(ColorScheme colorScheme) {
|
||||
if (colorScheme == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (colorScheme == ColorScheme.DARK
|
||||
&& !AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||
}
|
||||
|
||||
if (colorScheme != ColorScheme.DARK
|
||||
&& AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
if (!init) {
|
||||
return;
|
||||
}
|
||||
|
||||
var nowDark = isDarkMode();
|
||||
var nowDark = Platform.getPreferences().getColorScheme() == ColorScheme.DARK;
|
||||
AppCache.update("lastDarkTheme", nowDark);
|
||||
}
|
||||
|
||||
|
@ -210,26 +162,19 @@ public class AppTheme {
|
|||
}
|
||||
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var window = AppMainWindow.getInstance();
|
||||
if (window == null) {
|
||||
if (AppMainWindow.getInstance() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackEvent.debug("Setting theme " + newTheme.getId() + " for scene");
|
||||
|
||||
// Don't animate transition in performance mode
|
||||
if (AppPrefs.get() == null || AppPrefs.get().performanceMode().get()) {
|
||||
newTheme.apply();
|
||||
return;
|
||||
}
|
||||
|
||||
var stage = window.getStage();
|
||||
var scene = stage.getScene();
|
||||
var window = AppMainWindow.getInstance().getStage();
|
||||
var scene = window.getScene();
|
||||
Pane root = (Pane) scene.getRoot();
|
||||
Image snapshot = scene.snapshot(null);
|
||||
ImageView imageView = new ImageView(snapshot);
|
||||
root.getChildren().add(imageView);
|
||||
|
||||
newTheme.apply();
|
||||
TrackEvent.debug("Set theme " + newTheme.getId() + " for scene");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
// Animate!
|
||||
|
@ -355,7 +300,7 @@ public class AppTheme {
|
|||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2),
|
||||
91);
|
||||
115);
|
||||
|
||||
// Adjust this to create your own theme
|
||||
public static final Theme CUSTOM = new DerivedTheme(
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package io.xpipe.app.core.check;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AppBundledToolsCheck {
|
||||
|
||||
private static boolean getResult() {
|
||||
var fc = new ProcessBuilder("where", "ssh")
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
try {
|
||||
var proc = fc.start();
|
||||
proc.waitFor(2, TimeUnit.SECONDS);
|
||||
return proc.exitValue() == 0;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
if (AppPrefs.get().useBundledTools().get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getResult()) {
|
||||
AppPrefs.get().useBundledTools.set(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,6 +56,9 @@ public class BaseMode extends OperationMode {
|
|||
TrackEvent.info("Initializing base mode components ...");
|
||||
AppMainWindow.loadingText("initializingApp");
|
||||
LicenseProvider.get().init();
|
||||
// We no longer need this
|
||||
// AppCertutilCheck.check();
|
||||
AppBundledToolsCheck.check();
|
||||
AppHomebrewCoreutilsCheck.check();
|
||||
AppAvCheck.check();
|
||||
AppJavaOptionsCheck.check();
|
||||
|
@ -91,7 +94,6 @@ public class BaseMode extends OperationMode {
|
|||
AppPrefs.setLocalDefaultsIfNeeded();
|
||||
PlatformInit.init(true);
|
||||
AppMainWindow.addUpdateTitleListener();
|
||||
TrackEvent.info("Shell initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
shellLoaded.await();
|
||||
|
@ -104,16 +106,15 @@ public class BaseMode extends OperationMode {
|
|||
DataStorage.init();
|
||||
storageLoaded.countDown();
|
||||
StoreViewState.init();
|
||||
AppMainWindow.loadingText("loadingUserInterface");
|
||||
AppLayoutModel.init();
|
||||
PlatformInit.init(true);
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
AppGreetingsDialog.showIfNeeded();
|
||||
AppMainWindow.loadingText("initializingApp");
|
||||
});
|
||||
imagesLoaded.await();
|
||||
browserLoaded.await();
|
||||
iconsLoaded.await();
|
||||
TrackEvent.info("Waiting for startup dialogs to close");
|
||||
AppDialog.waitForAllDialogsClose();
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
try {
|
||||
|
@ -123,7 +124,6 @@ public class BaseMode extends OperationMode {
|
|||
}
|
||||
});
|
||||
UpdateChangelogAlert.showIfNeeded();
|
||||
TrackEvent.info("Connection storage initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
AppFileWatcher.init();
|
||||
|
@ -131,7 +131,6 @@ public class BaseMode extends OperationMode {
|
|||
BlobManager.init();
|
||||
TerminalView.init();
|
||||
TerminalLauncherManager.init();
|
||||
TrackEvent.info("File/Watcher initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
PlatformInit.init(true);
|
||||
|
@ -140,16 +139,13 @@ public class BaseMode extends OperationMode {
|
|||
storageLoaded.await();
|
||||
SystemIconManager.init();
|
||||
iconsLoaded.countDown();
|
||||
TrackEvent.info("Platform initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
BrowserIconManager.loadIfNecessary();
|
||||
shellLoaded.await();
|
||||
BrowserLocalFileSystem.init();
|
||||
storageLoaded.await();
|
||||
BrowserFullSessionModel.init();
|
||||
browserLoaded.countDown();
|
||||
TrackEvent.info("Browser initialization thread completed");
|
||||
});
|
||||
ActionProvider.initProviders();
|
||||
DataStoreProviders.init();
|
||||
|
|
|
@ -180,8 +180,6 @@ public abstract class OperationMode {
|
|||
|
||||
var startupMode = getStartupMode();
|
||||
switchToSyncOrThrow(map(startupMode));
|
||||
// If it doesn't find time, the JVM will not gc the startup workload
|
||||
System.gc();
|
||||
inStartup = false;
|
||||
AppOpenArguments.init();
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public class AppDialog {
|
|||
var transition = new PauseTransition(Duration.millis(200));
|
||||
transition.setOnFinished(e -> {
|
||||
if (wait) {
|
||||
PlatformThread.exitNestedEventLoop(key);
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
|
@ -95,7 +95,7 @@ public class AppDialog {
|
|||
}
|
||||
});
|
||||
if (wait) {
|
||||
PlatformThread.enterNestedEventLoop(key);
|
||||
Platform.enterNestedEventLoop(key);
|
||||
waitForDialogClose(o);
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,6 @@ public class AppDialog {
|
|||
public static Comp<?> dialogText(String s) {
|
||||
return Comp.of(() -> {
|
||||
var text = new Text(s);
|
||||
text.getStyleClass().add("dialog-text");
|
||||
text.setWrappingWidth(450);
|
||||
var sp = new StackPane(text);
|
||||
return sp;
|
||||
|
@ -119,7 +118,6 @@ public class AppDialog {
|
|||
public static Comp<?> dialogText(ObservableValue<String> s) {
|
||||
return Comp.of(() -> {
|
||||
var text = new Text();
|
||||
text.getStyleClass().add("dialog-text");
|
||||
text.textProperty().bind(s);
|
||||
text.setWrappingWidth(450);
|
||||
var sp = new StackPane(text);
|
||||
|
|
|
@ -138,10 +138,8 @@ public class AppMainWindow {
|
|||
}
|
||||
|
||||
public static synchronized void initContent() {
|
||||
TrackEvent.info("Window content node creation started");
|
||||
var content = new AppLayoutComp();
|
||||
var s = content.createStructure();
|
||||
TrackEvent.info("Window content node structure created");
|
||||
loadedContent.setValue(s);
|
||||
}
|
||||
|
||||
|
@ -152,13 +150,6 @@ public class AppMainWindow {
|
|||
}
|
||||
}
|
||||
|
||||
public void focus() {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
stage.setIconified(false);
|
||||
stage.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
private static String createTitle() {
|
||||
var t = LicenseProvider.get().licenseTitle();
|
||||
var base =
|
||||
|
|
|
@ -124,13 +124,10 @@ public class ModifiedStage extends Stage {
|
|||
var transition = new PauseTransition(Duration.millis(300));
|
||||
transition.setOnFinished(e -> {
|
||||
applyModes(stage);
|
||||
// We only need to update the frame by resizing on Windows
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
stage.setWidth(stage.getWidth() - 1);
|
||||
Platform.runLater(() -> {
|
||||
stage.setWidth(stage.getWidth() + 1);
|
||||
});
|
||||
}
|
||||
stage.setWidth(stage.getWidth() - 1);
|
||||
Platform.runLater(() -> {
|
||||
stage.setWidth(stage.getWidth() + 1);
|
||||
});
|
||||
});
|
||||
transition.play();
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.ext;
|
|||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
|
@ -22,7 +21,6 @@ public interface ActionProvider {
|
|||
List<ActionProvider> ALL_STANDALONE = new ArrayList<>();
|
||||
|
||||
static void initProviders() {
|
||||
TrackEvent.trace("Starting action provider initialization");
|
||||
for (ActionProvider actionProvider : ALL) {
|
||||
try {
|
||||
actionProvider.init();
|
||||
|
@ -30,7 +28,6 @@ public interface ActionProvider {
|
|||
ErrorEvent.fromThrowable(t).handle();
|
||||
}
|
||||
}
|
||||
TrackEvent.trace("Finished action provider initialization");
|
||||
}
|
||||
|
||||
default void init() throws Exception {}
|
||||
|
|
|
@ -93,8 +93,8 @@ public interface DataStoreProvider {
|
|||
return StoreEntryComp.create(s, null, preferLarge);
|
||||
}
|
||||
|
||||
default StoreSectionComp customSectionComp(StoreSection section) {
|
||||
return new StoreSectionComp(section);
|
||||
default StoreSectionComp customSectionComp(StoreSection section, boolean topLevel) {
|
||||
return new StoreSectionComp(section, topLevel);
|
||||
}
|
||||
|
||||
default boolean shouldShowScan() {
|
||||
|
|
|
@ -7,7 +7,6 @@ import com.github.weisj.jsvg.SVGDocument;
|
|||
import com.github.weisj.jsvg.SVGRenderingHints;
|
||||
import com.github.weisj.jsvg.attributes.ViewBox;
|
||||
import com.github.weisj.jsvg.parser.SVGLoader;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.*;
|
||||
|
@ -22,14 +21,6 @@ import javax.imageio.ImageIO;
|
|||
|
||||
public class SystemIconCache {
|
||||
|
||||
private static enum ImageColorScheme {
|
||||
|
||||
TRANSPARENT,
|
||||
MIXED,
|
||||
LIGHT,
|
||||
DARK
|
||||
}
|
||||
|
||||
private static final Path DIRECTORY =
|
||||
AppProperties.get().getDataDir().resolve("cache").resolve("icons").resolve("raster");
|
||||
private static final int[] sizes = new int[] {16, 24, 40, 80};
|
||||
|
@ -59,31 +50,11 @@ public class SystemIconCache {
|
|||
Files.createDirectories(target);
|
||||
|
||||
for (var icon : e.getValue().getIcons()) {
|
||||
var dark = icon.getColorSchemeData() == SystemIconSourceFile.ColorSchemeData.DARK;
|
||||
if (refreshChecksum(icon.getFile(), target, icon.getName(), dark)) {
|
||||
if (refreshChecksum(icon.getFile(), target, icon.getName(), icon.isDark())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var scheme = rasterizeSizes(icon.getFile(), target, icon.getName(), dark);
|
||||
if (scheme == ImageColorScheme.TRANSPARENT) {
|
||||
var message = "Failed to rasterize icon icon " + icon.getFile().getFileName().toString() + ": Rasterized image is transparent";
|
||||
ErrorEvent.fromMessage(message).omit().expected().handle();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scheme != ImageColorScheme.DARK || icon.getColorSchemeData() != SystemIconSourceFile.ColorSchemeData.DEFAULT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasExplicitDark = e.getValue().getIcons().stream().anyMatch(
|
||||
systemIconSourceFile -> systemIconSourceFile.getSource().equals(icon.getSource()) &&
|
||||
systemIconSourceFile.getName().equals(icon.getName()) &&
|
||||
systemIconSourceFile.getColorSchemeData() == SystemIconSourceFile.ColorSchemeData.DARK);
|
||||
if (hasExplicitDark) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rasterizeSizesInverted(icon.getFile(), target, icon.getName(), true);
|
||||
rasterizeSizes(icon.getFile(), target, icon.getName(), icon.isDark());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -106,60 +77,28 @@ public class SystemIconCache {
|
|||
}
|
||||
}
|
||||
|
||||
private static ImageColorScheme rasterizeSizes(Path path, Path dir, String name, boolean dark) throws IOException {
|
||||
TrackEvent.trace("Rasterizing image " + path.getFileName().toString());
|
||||
private static boolean rasterizeSizes(Path path, Path dir, String name, boolean dark) throws IOException {
|
||||
try {
|
||||
ImageColorScheme c = null;
|
||||
for (var size : sizes) {
|
||||
var image = rasterize(path, size);
|
||||
if (image == null) {
|
||||
continue;
|
||||
}
|
||||
if (c == null) {
|
||||
c = determineColorScheme(image);
|
||||
if (c == ImageColorScheme.TRANSPARENT) {
|
||||
return ImageColorScheme.TRANSPARENT;
|
||||
}
|
||||
}
|
||||
write(dir, name, dark, size, image);
|
||||
rasterize(path, dir, name, dark, size);
|
||||
}
|
||||
return c;
|
||||
} catch (Exception ex) {
|
||||
var message = "Failed to rasterize icon icon " + path.getFileName().toString() + ": " + ex.getMessage();
|
||||
ErrorEvent.fromThrowable(ex).description(message).omit().expected().handle();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageColorScheme rasterizeSizesInverted(Path path, Path dir, String name, boolean dark) throws IOException {
|
||||
try {
|
||||
ImageColorScheme c = null;
|
||||
for (var size : sizes) {
|
||||
var image = rasterize(path, size);
|
||||
if (image == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var invert = invert(image);
|
||||
write(dir, name, dark, size, invert);
|
||||
}
|
||||
return c;
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
if (ex instanceof IOException) {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
ErrorEvent.fromThrowable(ex).omit().expected().handle();
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedImage rasterize(Path path, int px) throws IOException {
|
||||
private static void rasterize(Path path, Path dir, String name, boolean dark, int px) throws IOException {
|
||||
SVGLoader loader = new SVGLoader();
|
||||
URL svgUrl = path.toUri().toURL();
|
||||
SVGDocument svgDocument = loader.load(svgUrl);
|
||||
if (svgDocument == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage image = new BufferedImage(px, px, BufferedImage.TYPE_INT_ARGB);
|
||||
|
@ -170,67 +109,8 @@ public class SystemIconCache {
|
|||
g.setRenderingHint(SVGRenderingHints.KEY_SOFT_CLIPPING, SVGRenderingHints.VALUE_SOFT_CLIPPING_ON);
|
||||
svgDocument.render((Component) null, g, new ViewBox(0, 0, px, px));
|
||||
g.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
private static BufferedImage write(Path dir, String name, boolean dark, int px, BufferedImage image) throws IOException {
|
||||
var out = dir.resolve(name + "-" + px + (dark ? "-dark" : "") + ".png");
|
||||
ImageIO.write(image, "png", out.toFile());
|
||||
return image;
|
||||
}
|
||||
|
||||
private static BufferedImage invert(BufferedImage image) {
|
||||
var buffer = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
int clr = image.getRGB(x, y);
|
||||
int alpha = (clr >> 24) & 0xff;
|
||||
int red = (clr & 0x00ff0000) >> 16;
|
||||
int green = (clr & 0x0000ff00) >> 8;
|
||||
int blue = clr & 0x000000ff;
|
||||
buffer.setRGB(x, y, new Color(255- red, 255- green, 255- blue, alpha).getRGB());
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static ImageColorScheme determineColorScheme(BufferedImage image) {
|
||||
var transparent = true;
|
||||
var counter = 0;
|
||||
var mean = 0.0;
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
int clr = image.getRGB(x, y);
|
||||
int alpha = (clr >> 24) & 0xff;
|
||||
int red = (clr & 0x00ff0000) >> 16;
|
||||
int green = (clr & 0x0000ff00) >> 8;
|
||||
int blue = clr & 0x000000ff;
|
||||
|
||||
if (alpha > 0) {
|
||||
transparent = false;
|
||||
}
|
||||
|
||||
if (alpha < 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mean += red + green + blue;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (transparent) {
|
||||
return ImageColorScheme.TRANSPARENT;
|
||||
}
|
||||
|
||||
mean /= counter * 3;
|
||||
if (mean < 50) {
|
||||
return ImageColorScheme.DARK;
|
||||
} else if (mean > 205) {
|
||||
return ImageColorScheme.LIGHT;
|
||||
} else {
|
||||
return ImageColorScheme.MIXED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,18 +80,7 @@ public class SystemIconManager {
|
|||
});
|
||||
}
|
||||
|
||||
private static void reloadImages() {
|
||||
AppImages.remove(s -> s.startsWith("icons/"));
|
||||
try {
|
||||
for (var source : getEffectiveSources()) {
|
||||
AppImages.loadRasterImages(SystemIconCache.getDirectory(source), "icons/" + source.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearInvalidImages() {
|
||||
public static void reloadImages() {
|
||||
AppImages.remove(s -> s.startsWith("icons/"));
|
||||
try {
|
||||
for (var source : getEffectiveSources()) {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package io.xpipe.app.icon;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.Validators;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
|
@ -92,14 +90,7 @@ public interface SystemIconSource {
|
|||
@Override
|
||||
public void refresh() throws Exception {
|
||||
try (var sc =
|
||||
ProcessControlProvider.get().createLocalProcessControl(true).start()) {
|
||||
var present = sc.view().findProgram("git").isPresent();
|
||||
if (!present) {
|
||||
var msg = "Git command-line tools are not available in the PATH but are required to use icons from a git repository. For more details, see https://git-scm.com/downloads.";
|
||||
ErrorEvent.fromMessage(msg).expected().handle();
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessControlProvider.get().createLocalProcessControl(true).start()) {
|
||||
var dir = SystemIconManager.getPoolPath().resolve(id);
|
||||
if (!Files.exists(dir)) {
|
||||
sc.command(CommandBuilder.of()
|
||||
|
|
|
@ -31,53 +31,25 @@ public class SystemIconSourceData {
|
|||
}
|
||||
|
||||
var files = Files.walk(dir).toList();
|
||||
List<Path> flatFiles = files.stream()
|
||||
.filter(path -> Files.isRegularFile(path))
|
||||
.filter(path -> path.toString().endsWith(".svg"))
|
||||
.map(path -> {
|
||||
var name = FilenameUtils.getBaseName(path.getFileName().toString());
|
||||
var cleanedName = name.replaceFirst("-light$", "").replaceFirst("-dark$", "");
|
||||
var cleanedPath = path.getParent().resolve(cleanedName + ".svg");
|
||||
return cleanedPath;
|
||||
}).toList();
|
||||
for (var file : flatFiles) {
|
||||
var name = FilenameUtils.getBaseName(file.getFileName().toString());
|
||||
var displayName = name.toLowerCase(Locale.ROOT);
|
||||
var baseFile = file.getParent().resolve(name + ".svg");
|
||||
var hasBaseVariant = Files.exists(baseFile);
|
||||
var darkModeFile = file.getParent().resolve(name + "-light.svg");
|
||||
var hasDarkModeVariant = Files.exists(darkModeFile);
|
||||
var lightModeFile = file.getParent().resolve(name + "-dark.svg");
|
||||
var hasLightModeVariant = Files.exists(lightModeFile);
|
||||
|
||||
if (hasBaseVariant && hasDarkModeVariant) {
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, baseFile, SystemIconSourceFile.ColorSchemeData.DEFAULT));
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, darkModeFile, SystemIconSourceFile.ColorSchemeData.DARK));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasBaseVariant && hasLightModeVariant) {
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, baseFile, SystemIconSourceFile.ColorSchemeData.DARK));
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, lightModeFile, SystemIconSourceFile.ColorSchemeData.DEFAULT));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasBaseVariant) {
|
||||
if (hasLightModeVariant) {
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, lightModeFile, SystemIconSourceFile.ColorSchemeData.DEFAULT));
|
||||
if (hasDarkModeVariant) {
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, darkModeFile, SystemIconSourceFile.ColorSchemeData.DARK));
|
||||
}
|
||||
} else {
|
||||
if (hasDarkModeVariant) {
|
||||
sourceFiles.add(
|
||||
new SystemIconSourceFile(source, displayName, darkModeFile, SystemIconSourceFile.ColorSchemeData.DEFAULT));
|
||||
}
|
||||
for (var file : files) {
|
||||
if (file.getFileName().toString().endsWith(".svg")) {
|
||||
var name = FilenameUtils.getBaseName(file.getFileName().toString());
|
||||
var cleanedName = name.replaceFirst("-light$", "").replaceFirst("-dark$", "");
|
||||
var hasLightVariant = Files.exists(file.getParent().resolve(cleanedName + "-light.svg"));
|
||||
var hasDarkVariant = Files.exists(file.getParent().resolve(cleanedName + "-dark.svg"));
|
||||
if (hasLightVariant && !hasDarkVariant && name.endsWith("-light")) {
|
||||
var s = new SystemIconSourceFile(source, cleanedName.toLowerCase(Locale.ROOT), file, true);
|
||||
sourceFiles.add(s);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
sourceFiles.add(new SystemIconSourceFile(source, displayName, baseFile, SystemIconSourceFile.ColorSchemeData.DEFAULT));
|
||||
if (hasLightVariant && hasDarkVariant && (name.endsWith("-dark") || name.endsWith("-light"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var s = new SystemIconSourceFile(source, cleanedName.toLowerCase(Locale.ROOT), file, false);
|
||||
sourceFiles.add(s);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
|
|
|
@ -7,13 +7,8 @@ import java.nio.file.Path;
|
|||
@Value
|
||||
public class SystemIconSourceFile {
|
||||
|
||||
public static enum ColorSchemeData {
|
||||
DARK,
|
||||
DEFAULT;
|
||||
}
|
||||
|
||||
SystemIconSource source;
|
||||
String name;
|
||||
Path file;
|
||||
ColorSchemeData colorSchemeData;
|
||||
boolean dark;
|
||||
}
|
||||
|
|
|
@ -39,14 +39,6 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
return hasEmail || hasText;
|
||||
}
|
||||
|
||||
private static boolean doesExceedCommentSize(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return text.length() > 5000;
|
||||
}
|
||||
|
||||
private static Throwable adjustCopy(Throwable throwable, boolean clear) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
|
@ -147,16 +139,6 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
atts.forEach(attachment -> s.addAttachment(attachment));
|
||||
}
|
||||
|
||||
if (doesExceedCommentSize(ee.getUserReport())) {
|
||||
try {
|
||||
var report = Files.createTempFile("report", ".txt");
|
||||
Files.writeString(report, ee.getUserReport());
|
||||
s.addAttachment(new Attachment(report.toString()));
|
||||
} catch (Exception ex) {
|
||||
AppLogs.get().logException("Unable to create report file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
s.setTag(
|
||||
"hasLicense",
|
||||
String.valueOf(
|
||||
|
@ -194,7 +176,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
AppPrefs.get() != null
|
||||
? String.valueOf(AppPrefs.get().useLocalFallbackShell().get())
|
||||
: "unknown");
|
||||
s.setTag("initial", AppProperties.get() != null ? AppProperties.get().isInitialLaunch() + "" : "false");
|
||||
s.setTag("initial", AppProperties.get() != null ? AppProperties.get().isInitialLaunch() + "" : null);
|
||||
|
||||
var exMessage = ee.getThrowable() != null ? ee.getThrowable().getMessage() : null;
|
||||
if (ee.getDescription() != null
|
||||
|
@ -249,11 +231,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
if (hasEmail) {
|
||||
fb.setEmail(email);
|
||||
}
|
||||
if (doesExceedCommentSize(text)) {
|
||||
fb.setComments("<Attachment>");
|
||||
} else {
|
||||
fb.setComments(text);
|
||||
}
|
||||
fb.setComments(text);
|
||||
Sentry.captureUserFeedback(fb);
|
||||
}
|
||||
Sentry.flush(3000);
|
||||
|
|
|
@ -32,8 +32,8 @@ public class AboutCategory extends AppPrefsCategory {
|
|||
.grow(true, false),
|
||||
null)
|
||||
.addComp(
|
||||
new TileButtonComp("documentation", "documentationDescription", "mdi2b-book-open-variant", e -> {
|
||||
Hyperlinks.open(Hyperlinks.DOCS);
|
||||
new TileButtonComp("slack", "slackDescription", "mdi2s-slack", e -> {
|
||||
Hyperlinks.open(Hyperlinks.SLACK);
|
||||
e.consume();
|
||||
})
|
||||
.grow(true, false),
|
||||
|
@ -45,6 +45,13 @@ public class AboutCategory extends AppPrefsCategory {
|
|||
})
|
||||
.grow(true, false),
|
||||
null)
|
||||
.addComp(
|
||||
new TileButtonComp("securityPolicy", "securityPolicyDescription", "mdrmz-security", e -> {
|
||||
Hyperlinks.open(Hyperlinks.DOCS_SECURITY);
|
||||
e.consume();
|
||||
})
|
||||
.grow(true, false),
|
||||
null)
|
||||
.addComp(
|
||||
new TileButtonComp("privacy", "privacyDescription", "mdomz-privacy_tip", e -> {
|
||||
Hyperlinks.open(Hyperlinks.DOCS_PRIVACY);
|
||||
|
@ -59,8 +66,7 @@ public class AboutCategory extends AppPrefsCategory {
|
|||
.styleClass("open-source-notices");
|
||||
var modal = ModalOverlay.of("openSourceNotices", comp);
|
||||
modal.show();
|
||||
})
|
||||
.grow(true, false))
|
||||
}))
|
||||
.addComp(
|
||||
new TileButtonComp("eula", "eulaDescription", "mdi2c-card-text-outline", e -> {
|
||||
Hyperlinks.open(Hyperlinks.DOCS_EULA);
|
||||
|
|
|
@ -9,7 +9,6 @@ import io.xpipe.app.icon.SystemIconSource;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
|
||||
|
@ -58,6 +57,8 @@ public class AppPrefs {
|
|||
mapVaultShared(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class, false);
|
||||
public final BooleanProperty performanceMode =
|
||||
mapLocal(new SimpleBooleanProperty(), "performanceMode", Boolean.class, false);
|
||||
public final BooleanProperty useBundledTools =
|
||||
mapLocal(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class, true);
|
||||
public final ObjectProperty<AppTheme.Theme> theme =
|
||||
mapLocal(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class, false);
|
||||
final BooleanProperty useSystemFont =
|
||||
|
@ -117,8 +118,6 @@ public class AppPrefs {
|
|||
mapLocal(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class, false);
|
||||
final StringProperty customEditorCommand =
|
||||
mapLocal(new SimpleStringProperty(""), "customEditorCommand", String.class, false);
|
||||
final BooleanProperty customEditorCommandInTerminal =
|
||||
mapLocal(new SimpleBooleanProperty(false), "customEditorCommandInTerminal", Boolean.class, false);
|
||||
final BooleanProperty automaticallyCheckForUpdates =
|
||||
mapLocal(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class, false);
|
||||
final BooleanProperty encryptAllVaultData =
|
||||
|
@ -273,7 +272,7 @@ public class AppPrefs {
|
|||
INSTANCE = new AppPrefs();
|
||||
PrefsProvider.getAll().forEach(prov -> prov.addPrefs(INSTANCE.extensionHandler));
|
||||
INSTANCE.loadLocal();
|
||||
INSTANCE.adjustLocalValues();
|
||||
INSTANCE.fixInvalidLocalValues();
|
||||
INSTANCE.vaultStorageHandler = new AppPrefsStorageHandler(
|
||||
INSTANCE.storageDirectory().getValue().resolve("preferences.json"));
|
||||
}
|
||||
|
@ -327,6 +326,10 @@ public class AppPrefs {
|
|||
return performanceMode;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue useBundledTools() {
|
||||
return useBundledTools;
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> useSystemFont() {
|
||||
return useSystemFont;
|
||||
}
|
||||
|
@ -407,10 +410,6 @@ public class AppPrefs {
|
|||
return customEditorCommand;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue customEditorCommandInTerminal() {
|
||||
return customEditorCommandInTerminal;
|
||||
}
|
||||
|
||||
public final ReadOnlyIntegerProperty editorReloadTimeout() {
|
||||
return editorReloadTimeout;
|
||||
}
|
||||
|
@ -532,7 +531,7 @@ public class AppPrefs {
|
|||
}
|
||||
}
|
||||
|
||||
private void adjustLocalValues() {
|
||||
private void fixInvalidLocalValues() {
|
||||
// You can set the directory to empty in the settings
|
||||
if (storageDirectory.get() == null || storageDirectory.get().toString().isBlank()) {
|
||||
storageDirectory.setValue(DEFAULT_STORAGE_DIR);
|
||||
|
@ -544,11 +543,6 @@ public class AppPrefs {
|
|||
ErrorEvent.fromThrowable(e).expected().build().handle();
|
||||
storageDirectory.setValue(DEFAULT_STORAGE_DIR);
|
||||
}
|
||||
|
||||
if (AppProperties.get().isInitialLaunch()) {
|
||||
var f = PlatformState.determineDefaultScalingFactor();
|
||||
uiScale.setValue(f.isPresent() ? f.getAsInt() : null);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSharedRemote() {
|
||||
|
|
|
@ -52,6 +52,7 @@ public class AppPrefsComp extends SimpleComp {
|
|||
split.setFillHeight(true);
|
||||
split.getStyleClass().add("prefs");
|
||||
var stack = new StackPane(split);
|
||||
stack.setPickOnBounds(false);
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,25 +14,24 @@ public class ConnectionsCategory extends AppPrefsCategory {
|
|||
@Override
|
||||
protected Comp<?> create() {
|
||||
var prefs = AppPrefs.get();
|
||||
var connectionsBuilder = new OptionsBuilder().pref(prefs.condenseConnectionDisplay).addToggle(prefs.condenseConnectionDisplay).pref(
|
||||
prefs.showChildCategoriesInParentCategory).addToggle(prefs.showChildCategoriesInParentCategory).pref(
|
||||
prefs.openConnectionSearchWindowOnConnectionCreation).addToggle(prefs.openConnectionSearchWindowOnConnectionCreation).pref(
|
||||
prefs.requireDoubleClickForConnections).addToggle(prefs.requireDoubleClickForConnections);
|
||||
var localShellBuilder = new OptionsBuilder().pref(prefs.useLocalFallbackShell).addToggle(prefs.useLocalFallbackShell);
|
||||
// Change order to prioritize fallback shell on macOS
|
||||
var options = OsType.getLocal() == OsType.MACOS ? new OptionsBuilder()
|
||||
.addTitle("localShell")
|
||||
.sub(localShellBuilder)
|
||||
var options = new OptionsBuilder()
|
||||
.addTitle("connections")
|
||||
.sub(connectionsBuilder) :
|
||||
new OptionsBuilder()
|
||||
.addTitle("connections")
|
||||
.sub(connectionsBuilder)
|
||||
.sub(new OptionsBuilder()
|
||||
.pref(prefs.condenseConnectionDisplay)
|
||||
.addToggle(prefs.condenseConnectionDisplay)
|
||||
.pref(prefs.showChildCategoriesInParentCategory)
|
||||
.addToggle(prefs.showChildCategoriesInParentCategory)
|
||||
.pref(prefs.openConnectionSearchWindowOnConnectionCreation)
|
||||
.addToggle(prefs.openConnectionSearchWindowOnConnectionCreation)
|
||||
.pref(prefs.requireDoubleClickForConnections)
|
||||
.addToggle(prefs.requireDoubleClickForConnections))
|
||||
.addTitle("localShell")
|
||||
.sub(localShellBuilder);
|
||||
.sub(new OptionsBuilder().pref(prefs.useLocalFallbackShell).addToggle(prefs.useLocalFallbackShell));
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
options.addTitle("sshConfiguration")
|
||||
.sub(new OptionsBuilder()
|
||||
.pref(prefs.useBundledTools)
|
||||
.addToggle(prefs.useBundledTools)
|
||||
.addComp(prefs.getCustomComp("x11WslInstance")));
|
||||
}
|
||||
return options.buildComp();
|
||||
|
|
|
@ -47,13 +47,9 @@ public class EditorCategory extends AppPrefsCategory {
|
|||
prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false))
|
||||
.nameAndDescription("customEditorCommand")
|
||||
.addComp(new TextFieldComp(prefs.customEditorCommand, true)
|
||||
.apply(struc -> struc.get().setPromptText("myeditor $FILE")))
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))
|
||||
.addComp(terminalTest)
|
||||
.nameAndDescription("customEditorCommandInTerminal")
|
||||
.addToggle(prefs.customEditorCommandInTerminal)
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))
|
||||
)
|
||||
.apply(struc -> struc.get().setPromptText("myeditor $FILE"))
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM)))
|
||||
.addComp(terminalTest))
|
||||
.buildComp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
@ -172,13 +171,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
throw ErrorEvent.expected(new IllegalStateException("No custom editor command specified"));
|
||||
}
|
||||
|
||||
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
var command = CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString()));
|
||||
if (AppPrefs.get().customEditorCommandInTerminal().get()) {
|
||||
TerminalLauncher.openDirect(file.toString(), sc -> command.buildFull(sc), AppPrefs.get().terminalType.get());
|
||||
} else {
|
||||
ExternalApplicationHelper.startAsync(command);
|
||||
}
|
||||
var format =
|
||||
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -108,7 +108,7 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
ThreadHelper.runFailableAsync(() -> {
|
||||
// Startup is slow
|
||||
ThreadHelper.sleep(10000);
|
||||
FileUtils.deleteQuietly(config.toFile());
|
||||
Files.delete(config);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -125,12 +125,8 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
var b = CommandBuilder.of().addFile(file.toString()).add("/cert-ignore");
|
||||
if (configuration.getPassword() != null) {
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
b.add("/p:'" + escapedPw + "'");
|
||||
}
|
||||
launch(configuration.getTitle(), b);
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
launch(configuration.getTitle(), CommandBuilder.of().addFile(file.toString()).add("/cert-ignore").add("/p:'" + escapedPw + "'"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,7 +15,6 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -112,13 +111,8 @@ public class IconsCategory extends AppPrefsCategory {
|
|||
return;
|
||||
}
|
||||
|
||||
var path = Path.of(dir.get());
|
||||
if (Files.isRegularFile(path)) {
|
||||
throw new IllegalArgumentException("A custom icon directory requires to be a directory of .svg files, not a single file");
|
||||
}
|
||||
|
||||
var source = SystemIconSource.Directory.builder()
|
||||
.path(path)
|
||||
.path(Path.of(dir.get()))
|
||||
.id(UUID.randomUUID().toString())
|
||||
.build();
|
||||
if (!sources.contains(source)) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.comp.base.IntegratedTextAreaComp;
|
|||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
|
@ -116,7 +115,6 @@ public class PasswordManagerCategory extends AppPrefsCategory {
|
|||
.minHeight(120);
|
||||
var templates = Comp.of(() -> {
|
||||
var cb = new MenuButton();
|
||||
AppFontSizes.base(cb);
|
||||
cb.textProperty().bind(BindingsHelper.flatMap(prefs.passwordManager, externalPasswordManager -> {
|
||||
return externalPasswordManager != null
|
||||
? AppI18n.observable(externalPasswordManager.getId())
|
||||
|
@ -147,7 +145,6 @@ public class PasswordManagerCategory extends AppPrefsCategory {
|
|||
new TextFieldComp(testPasswordManagerValue)
|
||||
.apply(struc -> struc.get().setPromptText("Enter password key"))
|
||||
.styleClass(Styles.LEFT_PILL)
|
||||
.prefWidth(400)
|
||||
.apply(struc -> struc.get().setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
test.run();
|
||||
|
@ -156,17 +153,14 @@ public class PasswordManagerCategory extends AppPrefsCategory {
|
|||
})),
|
||||
new ButtonComp(null, new FontIcon("mdi2p-play"), test).styleClass(Styles.RIGHT_PILL)));
|
||||
testInput.apply(struc -> {
|
||||
struc.get().setFillHeight(true);
|
||||
var first = ((Region) struc.get().getChildren().get(0));
|
||||
var second = ((Region) struc.get().getChildren().get(1));
|
||||
second.minHeightProperty().bind(first.heightProperty());
|
||||
second.maxHeightProperty().bind(first.heightProperty());
|
||||
second.prefHeightProperty().bind(first.heightProperty());
|
||||
});
|
||||
|
||||
var testPasswordManager = new HorizontalComp(List.of(
|
||||
testInput, Comp.hspacer(25), new LabelComp(testPasswordManagerResult).apply(struc -> struc.get()
|
||||
.setOpacity(0.8))))
|
||||
.setOpacity(0.5))))
|
||||
.padding(new Insets(10, 0, 0, 0))
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
||||
.apply(struc -> struc.get().setFillHeight(true));
|
||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.comp.base.*;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
|
@ -28,6 +27,14 @@ public class SyncCategory extends AppPrefsCategory {
|
|||
return "vaultSync";
|
||||
}
|
||||
|
||||
private static void showHelpAlert() {
|
||||
var md = AppI18n.get().getMarkdownDocumentation("vault");
|
||||
var markdown = new MarkdownComp(md, s -> s, true).prefWidth(600);
|
||||
var modal = ModalOverlay.of(markdown);
|
||||
modal.addButton(ModalButton.ok());
|
||||
AppDialog.show(modal);
|
||||
}
|
||||
|
||||
public Comp<?> create() {
|
||||
var prefs = AppPrefs.get();
|
||||
AtomicReference<Region> button = new AtomicReference<>();
|
||||
|
@ -54,7 +61,7 @@ public class SyncCategory extends AppPrefsCategory {
|
|||
|
||||
var remoteRepo = new TextFieldComp(prefs.storageGitRemote).hgrow();
|
||||
var helpButton = new ButtonComp(AppI18n.observable("help"), new FontIcon("mdi2h-help-circle-outline"), () -> {
|
||||
Hyperlinks.open(Hyperlinks.DOCS_SYNC);
|
||||
showHelpAlert();
|
||||
});
|
||||
var remoteRow = new HorizontalComp(List.of(remoteRepo, helpButton)).spacing(10);
|
||||
remoteRow.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
|
|
|
@ -15,7 +15,6 @@ import io.xpipe.app.terminal.ExternalTerminalType;
|
|||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.*;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -59,10 +58,6 @@ public class TerminalCategory extends AppPrefsCategory {
|
|||
var feature = LicenseProvider.get().getFeature("logging");
|
||||
if (newValue && !feature.isSupported()) {
|
||||
try {
|
||||
// Disable it again so people don't forget that they left it on
|
||||
Platform.runLater(() -> {
|
||||
prefs.enableTerminalLogging.set(false);
|
||||
});
|
||||
feature.throwIfUnsupported();
|
||||
} catch (LicenseRequiredException ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
|
|
|
@ -451,7 +451,6 @@ public abstract class DataStorage {
|
|||
newChildren = l.stream()
|
||||
.filter(dataStoreEntryRef -> dataStoreEntryRef != null && dataStoreEntryRef.get() != null)
|
||||
.toList();
|
||||
e.getProvider().onChildrenRefresh(e);
|
||||
} else {
|
||||
newChildren = null;
|
||||
}
|
||||
|
@ -519,35 +518,14 @@ public abstract class DataStorage {
|
|||
.toList());
|
||||
|
||||
toUpdate.removeIf(pair -> {
|
||||
// Children classes might not be the same, the same goes for state classes
|
||||
// This can happen when there are multiple child classes and the ids got switched around
|
||||
var storeClassMatch = pair.getKey()
|
||||
.getStore()
|
||||
.getClass()
|
||||
.equals(pair.getValue().get().getStore().getClass());
|
||||
if (!storeClassMatch) {
|
||||
if (pair.getKey().getStorePersistentState() != null
|
||||
&& pair.getValue().get().getStorePersistentState() != null) {
|
||||
return pair.getKey()
|
||||
.getStorePersistentState()
|
||||
.equals(pair.getValue().get().getStorePersistentState());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
DataStore merged = ((FixedChildStore) pair.getKey().getStore())
|
||||
.merge(pair.getValue().getStore().asNeeded());
|
||||
var mergedStoreChanged = pair.getKey().getStore() != merged;
|
||||
|
||||
if (pair.getKey().getStorePersistentState() == null || pair.getValue().get().getStorePersistentState() == null) {
|
||||
return !mergedStoreChanged;
|
||||
}
|
||||
|
||||
var stateClassMatch = pair.getKey()
|
||||
.getStorePersistentState()
|
||||
.getClass()
|
||||
.equals(pair.getValue().get().getStorePersistentState().getClass());
|
||||
if (!stateClassMatch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var stateChange = !pair.getKey()
|
||||
.getStorePersistentState()
|
||||
.equals(pair.getValue().get().getStorePersistentState());
|
||||
return !mergedStoreChanged && !stateChange;
|
||||
});
|
||||
|
||||
if (toRemove.isEmpty() && toAdd.isEmpty() && toUpdate.isEmpty()) {
|
||||
|
@ -569,18 +547,31 @@ public abstract class DataStorage {
|
|||
}
|
||||
addStoreEntriesIfNotPresent(toAdd.stream().map(DataStoreEntryRef::get).toArray(DataStoreEntry[]::new));
|
||||
toUpdate.forEach(pair -> {
|
||||
DataStore merged = ((FixedChildStore) pair.getKey().getStore())
|
||||
.merge(pair.getValue().getStore().asNeeded());
|
||||
if (merged != pair.getKey().getStore()) {
|
||||
pair.getKey().setStoreInternal(merged, false);
|
||||
}
|
||||
// Update state by merging
|
||||
if (pair.getKey().getStorePersistentState() != null
|
||||
&& pair.getValue().get().getStorePersistentState() != null) {
|
||||
var classMatch = pair.getKey()
|
||||
.getStorePersistentState()
|
||||
.getClass()
|
||||
.equals(pair.getValue().get().getStorePersistentState().getClass());
|
||||
// Children classes might not be the same, the same goes for state classes
|
||||
// This can happen when there are multiple child classes and the ids got switched around
|
||||
if (classMatch) {
|
||||
DataStore merged = ((FixedChildStore) pair.getKey().getStore())
|
||||
.merge(pair.getValue().getStore().asNeeded());
|
||||
if (merged != pair.getKey().getStore()) {
|
||||
pair.getKey().setStoreInternal(merged, false);
|
||||
}
|
||||
|
||||
var s = pair.getKey().getStorePersistentState();
|
||||
var mergedState = s.mergeCopy(pair.getValue().get().getStorePersistentState());
|
||||
pair.getKey().setStorePersistentState(mergedState);
|
||||
var s = pair.getKey().getStorePersistentState();
|
||||
var mergedState = s.mergeCopy(pair.getValue().get().getStorePersistentState());
|
||||
pair.getKey().setStorePersistentState(mergedState);
|
||||
}
|
||||
}
|
||||
});
|
||||
refreshEntries();
|
||||
saveAsync();
|
||||
e.getProvider().onChildrenRefresh(e);
|
||||
toAdd.forEach(
|
||||
dataStoreEntryRef -> dataStoreEntryRef.get().getProvider().onParentRefresh(dataStoreEntryRef.get()));
|
||||
toUpdate.forEach(dataStoreEntryRef ->
|
||||
|
@ -600,6 +591,25 @@ public abstract class DataStorage {
|
|||
}
|
||||
}
|
||||
|
||||
public void deleteChildren(DataStoreEntry e) {
|
||||
var c = getDeepStoreChildren(e);
|
||||
if (c.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
c.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntriesSet.removeAll(c);
|
||||
synchronized (identityStoreEntryMapCache) {
|
||||
identityStoreEntryMapCache.remove(e.getStore());
|
||||
}
|
||||
synchronized (storeEntryMapCache) {
|
||||
storeEntryMapCache.remove(e.getStore());
|
||||
}
|
||||
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
|
||||
refreshEntries();
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public void deleteWithChildren(DataStoreEntry... entries) {
|
||||
List<DataStoreEntry> toDelete = Arrays.stream(entries)
|
||||
.flatMap(entry -> {
|
||||
|
|
|
@ -34,10 +34,7 @@ public class DataStoreEntryRef<T extends DataStore> {
|
|||
}
|
||||
|
||||
public void checkComplete() throws Throwable {
|
||||
var store = getStore();
|
||||
if (store != null) {
|
||||
getStore().checkComplete();
|
||||
}
|
||||
getStore().checkComplete();
|
||||
}
|
||||
|
||||
public DataStoreEntry get() {
|
||||
|
@ -45,7 +42,7 @@ public class DataStoreEntryRef<T extends DataStore> {
|
|||
}
|
||||
|
||||
public T getStore() {
|
||||
return entry.getStore() != null ? entry.getStore().asNeeded() : null;
|
||||
return entry.getStore().asNeeded();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -59,11 +59,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
// };
|
||||
|
||||
static ExternalTerminalType determineFallbackTerminalToOpen(ExternalTerminalType type) {
|
||||
if (type != XSHELL && type != MOBAXTERM && type != SECURECRT && type != TERMIUS && !(type instanceof WaveTerminalType)) {
|
||||
if (type == XSHELL || type == MOBAXTERM || type == SECURECRT) {
|
||||
return ProcessControlProvider.get().getEffectiveLocalDialect() == ShellDialects.CMD ? CMD : POWERSHELL;
|
||||
}
|
||||
|
||||
if (type != TERMIUS && type instanceof WaveTerminalType) {
|
||||
return type;
|
||||
}
|
||||
|
||||
// Fallback to an available default
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Linux linux -> {
|
||||
// This should not be termius or wave as all others take precedence
|
||||
|
@ -640,6 +643,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType WARP = new WarpTerminalType();
|
||||
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
||||
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
||||
WindowsTerminalType.WINDOWS_TERMINAL_CANARY,
|
||||
|
@ -647,7 +651,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
WindowsTerminalType.WINDOWS_TERMINAL,
|
||||
AlacrittyTerminalType.ALACRITTY_WINDOWS,
|
||||
WezTerminalType.WEZTERM_WINDOWS,
|
||||
WarpTerminalType.WINDOWS,
|
||||
CMD,
|
||||
PWSH,
|
||||
POWERSHELL,
|
||||
|
@ -679,11 +682,10 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
DEEPIN_TERMINAL,
|
||||
FOOT,
|
||||
Q_TERMINAL,
|
||||
WarpTerminalType.LINUX,
|
||||
TERMIUS,
|
||||
WaveTerminalType.WAVE_LINUX);
|
||||
List<ExternalTerminalType> MACOS_TERMINALS = List.of(
|
||||
WarpTerminalType.MACOS,
|
||||
WARP,
|
||||
ITERM2,
|
||||
KittyTerminalType.KITTY_MACOS,
|
||||
TabbyTerminalType.TABBY_MAC_OS,
|
||||
|
|
|
@ -2,10 +2,8 @@ package io.xpipe.app.terminal;
|
|||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.app.util.SecretManager;
|
||||
import io.xpipe.app.util.SecretQueryProgress;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
|
@ -79,7 +77,7 @@ public class TerminalLauncherManager {
|
|||
req = entries.get(request);
|
||||
}
|
||||
if (req == null) {
|
||||
return;
|
||||
throw new BeaconClientException("Unknown launch request " + request);
|
||||
}
|
||||
var byPid = ProcessHandle.of(pid);
|
||||
if (byPid.isEmpty()) {
|
||||
|
@ -92,44 +90,33 @@ public class TerminalLauncherManager {
|
|||
req.setPid(shell.pid());
|
||||
}
|
||||
|
||||
public static void waitExchange(UUID request) throws BeaconClientException, BeaconServerException {
|
||||
public static Path waitExchange(UUID request) throws BeaconClientException, BeaconServerException {
|
||||
TerminalLaunchRequest req;
|
||||
synchronized (entries) {
|
||||
req = entries.get(request);
|
||||
}
|
||||
if (req == null) {
|
||||
return;
|
||||
throw new BeaconClientException("Unknown launch request " + request);
|
||||
}
|
||||
|
||||
if (req.isSetupCompleted()) {
|
||||
submitAsync(req.getRequest(), req.getProcessControl(), req.getConfig(), req.getWorkingDirectory());
|
||||
}
|
||||
try {
|
||||
req.waitForCompletion();
|
||||
return req.waitForCompletion();
|
||||
} finally {
|
||||
req.setSetupCompleted(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static Path launchExchange(UUID request) throws BeaconClientException, BeaconServerException {
|
||||
public static Path launchExchange(UUID request) throws BeaconClientException {
|
||||
synchronized (entries) {
|
||||
var e = entries.values().stream()
|
||||
.filter(entry -> entry.getRequest().equals(request))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (e == null) {
|
||||
// It seems like that some terminals might enter a restart loop to try to start an older process again
|
||||
// This would spam XPipe continuously with launch requests if we returned an error here
|
||||
// Therefore, we just return a new local shell session
|
||||
TrackEvent.withTrace("Unknown launch request").tag("request", request.toString()).handle();
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var defaultShell = ProcessControlProvider.get().getEffectiveLocalDialect();
|
||||
var shellExec = defaultShell.getExecutableName();
|
||||
var script = ScriptHelper.createExecScript(sc, shellExec);
|
||||
return Path.of(script.toString());
|
||||
} catch (Exception ex) {
|
||||
throw new BeaconServerException(ex);
|
||||
}
|
||||
throw new BeaconClientException("Unknown launch request " + request);
|
||||
}
|
||||
|
||||
if (!(e.getResult() instanceof TerminalLaunchResult.ResultSuccess)) {
|
||||
|
|
|
@ -1,140 +1,57 @@
|
|||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.process.TerminalInitFunction;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
public class WarpTerminalType extends ExternalTerminalType.MacOsType {
|
||||
|
||||
public interface WarpTerminalType extends ExternalTerminalType, TrackableTerminalType {
|
||||
|
||||
static WarpTerminalType WINDOWS = new Windows();
|
||||
static WarpTerminalType LINUX = new Linux();
|
||||
static WarpTerminalType MACOS = new MacOs();
|
||||
|
||||
class Windows implements WarpTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
if (!configuration.isPreferTabs()) {
|
||||
DesktopHelper.openUrl("warp://action/new_window?path=" + configuration.getScriptFile());
|
||||
} else {
|
||||
DesktopHelper.openUrl("warp://action/new_tab?path=" + configuration.getScriptFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return WindowsRegistry.local().keyExists(WindowsRegistry.HKEY_CURRENT_USER, "Software\\Classes\\warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.warp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
// Warp always opens the new separate window, so we don't want to use it in the file browser for docking
|
||||
// Just say that we don't support new windows, that way it doesn't dock
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Linux implements WarpTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
if (!configuration.isPreferTabs()) {
|
||||
DesktopHelper.openUrl("warp://action/new_window?path=" + configuration.getScriptFile());
|
||||
} else {
|
||||
DesktopHelper.openUrl("warp://action/new_tab?path=" + configuration.getScriptFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return Files.exists(Path.of("/opt/warpdotdev"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.warp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
// Warp always opens the new separate window, so we don't want to use it in the file browser for docking
|
||||
// Just say that we don't support new windows, that way it doesn't dock
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements WarpTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.warp", "Warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Warp.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
public WarpTerminalType() {
|
||||
super("app.warp", "Warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getWebsite() {
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://www.warp.dev/";
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isRecommended() {
|
||||
public boolean isRecommended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean useColoredTitle() {
|
||||
public boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean shouldClear() {
|
||||
public boolean shouldClear() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default TerminalInitFunction additionalInitCommands() {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Warp.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalInitFunction additionalInitCommands() {
|
||||
return TerminalInitFunction.of(sc -> {
|
||||
if (sc.getShellDialect() == ShellDialects.ZSH) {
|
||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"zsh\"}}\\x9c'";
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.comp.base.ModalButton;
|
|||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppDistributionType;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
@ -183,7 +182,7 @@ public abstract class UpdateHandler {
|
|||
prepareUpdateImpl();
|
||||
|
||||
// Show available update in PTB more aggressively
|
||||
if (AppProperties.get().isStaging() && preparedUpdate.getValue() != null && !OperationMode.isInStartup()) {
|
||||
if (AppProperties.get().isStaging() && preparedUpdate.getValue() != null) {
|
||||
UpdateAvailableDialog.showIfNeeded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.util;
|
|||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.process.CountDown;
|
||||
import io.xpipe.core.process.ElevationHandler;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.SecretReference;
|
||||
|
||||
|
@ -20,7 +21,7 @@ public class BaseElevationHandler implements ElevationHandler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean handleRequest(UUID requestId, CountDown countDown, boolean confirmIfNeeded, boolean interactive) {
|
||||
public boolean handleRequest(ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded) {
|
||||
var ref = getSecretRef();
|
||||
if (ref == null) {
|
||||
return false;
|
||||
|
@ -34,7 +35,7 @@ public class BaseElevationHandler implements ElevationHandler {
|
|||
List.of(),
|
||||
List.of(),
|
||||
countDown,
|
||||
interactive);
|
||||
parent.isInteractive());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import lombok.Value;
|
|||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
@ -16,14 +15,12 @@ import java.util.function.Function;
|
|||
@SuppressWarnings("InfiniteLoopStatement")
|
||||
public class BindingsHelper {
|
||||
|
||||
private static final Set<ReferenceEntry> REFERENCES = new HashSet<>();
|
||||
private static final Set<ReferenceEntry> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
static {
|
||||
ThreadHelper.createPlatformThread("referenceGC", true, () -> {
|
||||
while (true) {
|
||||
synchronized (REFERENCES) {
|
||||
REFERENCES.removeIf(ReferenceEntry::canGc);
|
||||
}
|
||||
REFERENCES.removeIf(ReferenceEntry::canGc);
|
||||
ThreadHelper.sleep(1000);
|
||||
|
||||
// Use for testing
|
||||
|
@ -34,9 +31,7 @@ public class BindingsHelper {
|
|||
}
|
||||
|
||||
public static void preserve(Object source, Object target) {
|
||||
synchronized (REFERENCES) {
|
||||
REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target));
|
||||
}
|
||||
REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target));
|
||||
}
|
||||
|
||||
public static <T, U> ObservableValue<U> map(
|
||||
|
|
|
@ -85,19 +85,9 @@ public class DerivedObservableList<T> {
|
|||
target.setAll(newList);
|
||||
}
|
||||
|
||||
private int indexOfFromStart(List<? extends T> list, T value, int start) {
|
||||
for (int i = start; i < list.size(); i++) {
|
||||
if (Objects.equals(list.get(i), value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void setContentUnique(List<? extends T> newList) {
|
||||
var listSet = new HashSet<>(list);
|
||||
var newSet = new HashSet<>(newList);
|
||||
|
||||
// Addition
|
||||
if (newSet.containsAll(list)) {
|
||||
var l = new ArrayList<>(newList);
|
||||
|
@ -110,7 +100,7 @@ public class DerivedObservableList<T> {
|
|||
|
||||
var start = 0;
|
||||
for (int end = 0; end <= list.size(); end++) {
|
||||
var index = end < list.size() ? indexOfFromStart(newList, list.get(end), end) : newList.size();
|
||||
var index = end < list.size() ? newList.indexOf(list.get(end)) : newList.size();
|
||||
for (; start < index; start++) {
|
||||
list.add(start, newList.get(start));
|
||||
}
|
||||
|
@ -143,8 +133,7 @@ public class DerivedObservableList<T> {
|
|||
var cache = new HashMap<T, V>();
|
||||
var l1 = this.<V>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
var listSet = new HashSet<>(list);
|
||||
cache.keySet().removeIf(t -> !listSet.contains(t));
|
||||
cache.keySet().removeIf(t -> !getList().contains(t));
|
||||
l1.setContent(list.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppDistributionType;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -10,48 +8,11 @@ import io.xpipe.core.store.FileKind;
|
|||
import io.xpipe.core.store.FilePath;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DesktopHelper {
|
||||
|
||||
private static final String[] browsers = {
|
||||
"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open"
|
||||
};
|
||||
|
||||
public static void openUrl(String uri) {
|
||||
try {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
var pb = new ProcessBuilder("rundll32", "url.dll,FileProtocolHandler", uri);
|
||||
pb.directory(new File(System.getProperty("user.home")));
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
pb.start();
|
||||
} else if (OsType.getLocal() == OsType.LINUX) {
|
||||
String browser = null;
|
||||
for (String b : browsers) {
|
||||
if (browser == null
|
||||
&& Runtime.getRuntime()
|
||||
.exec(new String[] {"which", b})
|
||||
.getInputStream()
|
||||
.read()
|
||||
!= -1) {
|
||||
Runtime.getRuntime().exec(new String[] {browser = b, uri});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var pb = new ProcessBuilder("open", uri);
|
||||
pb.directory(new File(System.getProperty("user.home")));
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
pb.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static Path getDesktopDirectory() throws Exception {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
return Path.of(LocalShell.getLocalPowershell()
|
||||
|
@ -129,46 +90,47 @@ public class DesktopHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Files.exists(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var xdg = OsType.getLocal() == OsType.LINUX;
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.OPEN) && AppDistributionType.get() != AppDistributionType.WEBTOP) {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.toFile());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().omitted(xdg).handle();
|
||||
}
|
||||
}
|
||||
|
||||
if (xdg) {
|
||||
LocalExec.readStdoutIfPossible("xdg-open", file.toString());
|
||||
try {
|
||||
Desktop.getDesktop().open(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void browseFileInDirectory(Path file) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
|
||||
browsePathLocal(file.getParent());
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
ErrorEvent.fromMessage("Desktop integration unable to open file " + file)
|
||||
.expected()
|
||||
.handle();
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.getParent().toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var xdg = OsType.getLocal() == OsType.LINUX;
|
||||
if (AppDistributionType.get() != AppDistributionType.WEBTOP) {
|
||||
try {
|
||||
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().omitted(xdg).handle();
|
||||
}
|
||||
}
|
||||
|
||||
if (xdg) {
|
||||
LocalExec.readStdoutIfPossible("xdg-open", file.getParent().toString());
|
||||
try {
|
||||
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,11 +18,6 @@ import javax.crypto.SecretKey;
|
|||
public class EncryptionToken {
|
||||
|
||||
private static EncryptionToken vaultToken;
|
||||
private static EncryptionToken userToken;
|
||||
|
||||
public static void invalidateUserToken() {
|
||||
userToken = null;
|
||||
}
|
||||
|
||||
private static EncryptionToken createUserToken() {
|
||||
var userHandler = DataStorageUserHandler.getInstance();
|
||||
|
@ -44,15 +39,12 @@ public class EncryptionToken {
|
|||
}
|
||||
|
||||
public static EncryptionToken ofUser() {
|
||||
if (userToken == null) {
|
||||
var userHandler = DataStorageUserHandler.getInstance();
|
||||
if (userHandler.getActiveUser() == null) {
|
||||
throw new IllegalStateException("No active user available");
|
||||
}
|
||||
|
||||
userToken = createUserToken();
|
||||
var userHandler = DataStorageUserHandler.getInstance();
|
||||
if (userHandler.getActiveUser() == null) {
|
||||
throw new IllegalStateException("No active user available");
|
||||
}
|
||||
return userToken;
|
||||
|
||||
return createUserToken();
|
||||
}
|
||||
|
||||
public static EncryptionToken ofVaultKey() {
|
||||
|
@ -67,12 +59,6 @@ public class EncryptionToken {
|
|||
@JsonIgnore
|
||||
private Boolean isVault;
|
||||
|
||||
@JsonIgnore
|
||||
private Boolean isUser;
|
||||
|
||||
@JsonIgnore
|
||||
private EncryptionToken usedUserToken;
|
||||
|
||||
public boolean canDecrypt() {
|
||||
return isVault() || isUser();
|
||||
}
|
||||
|
@ -93,13 +79,7 @@ public class EncryptionToken {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (userToken == EncryptionToken.ofUser() && isUser != null) {
|
||||
return isUser;
|
||||
}
|
||||
|
||||
usedUserToken = ofUser();
|
||||
isUser = userHandler.getActiveUser().equals(decode(userHandler.getEncryptionKey()));
|
||||
return isUser;
|
||||
return userHandler.getActiveUser().equals(decode(userHandler.getEncryptionKey()));
|
||||
}
|
||||
|
||||
public boolean isVault() {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Hyperlinks {
|
||||
|
||||
|
@ -16,7 +13,6 @@ public class Hyperlinks {
|
|||
public static final String DOCS_EULA = "https://docs.xpipe.io/legal/end-user-license-agreement";
|
||||
public static final String DOCS_SECURITY = "https://docs.xpipe.io/reference/security";
|
||||
public static final String DOCS_WEBTOP_UPDATE = "https://docs.xpipe.io/guide/webtop#updating";
|
||||
public static final String DOCS_SYNC = "https://docs.xpipe.io/guide/sync";
|
||||
|
||||
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
|
||||
public static final String GITHUB_PTB = "https://github.com/xpipe-io/xpipe-ptb";
|
||||
|
@ -28,7 +24,38 @@ public class Hyperlinks {
|
|||
public static final String SLACK =
|
||||
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
|
||||
|
||||
static final String[] browsers = {
|
||||
"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open"
|
||||
};
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void open(String uri) {
|
||||
DesktopHelper.openUrl(uri);
|
||||
String osName = System.getProperty("os.name");
|
||||
try {
|
||||
if (osName.startsWith("Mac OS")) {
|
||||
Runtime.getRuntime().exec("open " + uri);
|
||||
} else if (osName.startsWith("Windows")) {
|
||||
Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + uri);
|
||||
} else { // assume Unix or Linux
|
||||
String browser = null;
|
||||
for (String b : browsers) {
|
||||
if (browser == null
|
||||
&& Runtime.getRuntime()
|
||||
.exec(new String[] {"which", b})
|
||||
.getInputStream()
|
||||
.read()
|
||||
!= -1) {
|
||||
Runtime.getRuntime().exec(new String[] {browser = b, uri});
|
||||
}
|
||||
}
|
||||
if (browser == null) {
|
||||
throw new Exception("No web browser or URL opener found to open " + uri);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// should not happen
|
||||
// dump stack for debug purpose
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ public class LocalExec {
|
|||
if (process.exitValue() != 0) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
var s = new String(out, StandardCharsets.UTF_8).trim();
|
||||
var s = new String(out, StandardCharsets.UTF_8);
|
||||
TrackEvent.withTrace("Local command finished")
|
||||
.tag("command", String.join(" ", command))
|
||||
.tag("stdout", s)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.prefs.ExternalEditorType;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -14,7 +15,7 @@ public class LocalShellCache extends ShellControlCache {
|
|||
super(shellControl);
|
||||
}
|
||||
|
||||
public Optional<Path> getVsCodeCliPath() {
|
||||
public Optional<Path> getVsCodePath() {
|
||||
if (!has("codePath")) {
|
||||
try {
|
||||
var app =
|
||||
|
@ -24,8 +25,8 @@ public class LocalShellCache extends ShellControlCache {
|
|||
.map(s -> Path.of(s));
|
||||
}
|
||||
case OsType.MacOs macOs -> {
|
||||
yield CommandSupport.findProgram(getShellControl(), "code")
|
||||
.map(s -> Path.of(s));
|
||||
yield new ExternalApplicationType.MacApplication(
|
||||
"app.vscode", "Visual Studio Code") {}.findApp();
|
||||
}
|
||||
case OsType.Windows windows -> {
|
||||
yield ExternalEditorType.VSCODE_WINDOWS.findExecutable();
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.core.check.AppSystemFontCheck;
|
|||
import io.xpipe.app.core.window.ModifiedStage;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
@ -15,7 +14,6 @@ import lombok.Setter;
|
|||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public enum PlatformState {
|
||||
|
@ -168,40 +166,4 @@ public enum PlatformState {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static OptionalInt determineDefaultScalingFactor() {
|
||||
if (OsType.getLocal() != OsType.LINUX) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
var factor = LocalExec.readStdoutIfPossible("gsettings", "get", "org.gnome.desktop.interface", "scaling-factor");
|
||||
if (factor.isEmpty()) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
var readCustom = factor.get().equals("uint32 1") || factor.get().equals("uint32 2");
|
||||
if (!readCustom) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
var textFactor = LocalExec.readStdoutIfPossible("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor");
|
||||
if (textFactor.isEmpty()) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
var s = textFactor.get();
|
||||
if (s.equals("1.0")) {
|
||||
return OptionalInt.empty();
|
||||
} else if (s.equals("2.0")) {
|
||||
return OptionalInt.of(200);
|
||||
} else if (s.equals("1.25")) {
|
||||
return OptionalInt.of(125);
|
||||
} else if (s.equals("1.5")) {
|
||||
return OptionalInt.of(150);
|
||||
} else if (s.equals("1.75")) {
|
||||
return OptionalInt.of(175);
|
||||
} else {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,29 +274,6 @@ public class PlatformThread {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static void enterNestedEventLoop(Object key) {
|
||||
if (!Platform.canStartNestedEventLoop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Platform.enterNestedEventLoop(key);
|
||||
} catch (IllegalStateException ex) {
|
||||
// We might be in an animation or layout call
|
||||
ErrorEvent.fromThrowable(ex).omit().expected().handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static void exitNestedEventLoop(Object key) {
|
||||
try {
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// The event loop might have died somehow
|
||||
// Or we passed an invalid key
|
||||
ErrorEvent.fromThrowable(ex).omit().expected().handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static void runNestedLoopIteration() {
|
||||
if (!Platform.canStartNestedEventLoop()) {
|
||||
return;
|
||||
|
@ -304,9 +281,9 @@ public class PlatformThread {
|
|||
|
||||
var key = new Object();
|
||||
Platform.runLater(() -> {
|
||||
exitNestedEventLoop(key);
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
});
|
||||
enterNestedEventLoop(key);
|
||||
Platform.enterNestedEventLoop(key);
|
||||
}
|
||||
|
||||
public static void runLaterIfNeeded(Runnable r) {
|
||||
|
|
|
@ -23,7 +23,7 @@ public enum SecretQueryState {
|
|||
yield "Session is not interactive but required user input for authentication";
|
||||
}
|
||||
case FIXED_SECRET_WRONG -> {
|
||||
yield "Authentication failed: Provided authentication secret was not accepted by the server, probably because it is incorrect";
|
||||
yield "Authentication failed: Provided authentication secret is wrong";
|
||||
}
|
||||
case RETRIEVAL_FAILURE -> {
|
||||
yield "Failed to retrieve secret for authentication";
|
||||
|
|
|
@ -201,12 +201,19 @@ public class SshLocalBridge {
|
|||
}
|
||||
|
||||
private static String getSshd(ShellControl sc) throws Exception {
|
||||
var exec = CommandSupport.findProgram(sc, "sshd");
|
||||
if (exec.isEmpty()) {
|
||||
throw ErrorEvent.expected(new IllegalStateException(
|
||||
"No sshd executable found in PATH. The SSH terminal bridge for SSH clients requires a local ssh server to be installed"));
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
return XPipeInstallation.getLocalBundledToolsDirectory()
|
||||
.resolve("openssh")
|
||||
.resolve("sshd")
|
||||
.toString();
|
||||
} else {
|
||||
var exec = CommandSupport.findProgram(sc, "sshd");
|
||||
if (exec.isEmpty()) {
|
||||
throw ErrorEvent.expected(new IllegalStateException(
|
||||
"No sshd executable found in PATH. The SSH terminal bridge requires a local ssh server"));
|
||||
}
|
||||
return exec.get();
|
||||
}
|
||||
return exec.get();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
|
|
|
@ -11,7 +11,7 @@ public class Validators {
|
|||
|
||||
public static <T extends DataStore> void isType(DataStoreEntryRef<? extends T> ref, Class<T> c)
|
||||
throws ValidationException {
|
||||
if (ref == null || ref.getStore() == null || !c.isAssignableFrom(ref.getStore().getClass())) {
|
||||
if (ref == null || !c.isAssignableFrom(ref.getStore().getClass())) {
|
||||
throw new ValidationException("Value must be an instance of " + c.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
|
||||
.bookmarks-container {
|
||||
-fx-background-radius: 4 2 2 4;
|
||||
-fx-background-radius: 4 0 0 0;
|
||||
-fx-background-insets: 0 7 4 4, 1 8 5 5;
|
||||
-fx-padding: 1 0 5 5;
|
||||
-fx-background-color: -color-border-default, -color-bg-default;
|
||||
|
@ -38,7 +38,3 @@
|
|||
-fx-max-height: 2.8em;
|
||||
-fx-padding: 6 6 6 4;
|
||||
}
|
||||
|
||||
.bookmarks-header .ikonli-font-icon {
|
||||
-fx-icon-color: -color-fg-default;
|
||||
}
|
||||
|
|
|
@ -2,18 +2,6 @@
|
|||
-fx-focus-color: transparent;
|
||||
}
|
||||
|
||||
.combo-box-base:hover .arrow-button:hover .arrow {
|
||||
-fx-background-color: -color-accent-fg;
|
||||
}
|
||||
|
||||
.identity-select-comp .clear-button:hover .ikonli-font-icon {
|
||||
-fx-icon-color: -color-accent-fg;
|
||||
}
|
||||
|
||||
.identity-select-comp .clear-button {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-cell:hover, .combo-box-popup .list-cell:focused {
|
||||
-fx-background-color: -color-context-menu;
|
||||
}
|
||||
|
|
|
@ -62,10 +62,6 @@
|
|||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
.root:dark:seamless-frame.primer .layout > .background, .root:dark:seamless-frame.mocha .layout > .background {
|
||||
-fx-border-color: #444;
|
||||
}
|
||||
|
||||
.root:macos:seamless-frame .layout > .background {
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-insets: 0;
|
||||
|
|
|
@ -32,7 +32,3 @@
|
|||
.options-comp .titled-pane > .title {
|
||||
-fx-padding: 8 20 8 10;
|
||||
}
|
||||
|
||||
.options-comp .titled-pane > .title .text {
|
||||
-fx-font-size: 0.95em;
|
||||
}
|
|
@ -24,11 +24,15 @@
|
|||
-fx-text-fill: -color-fg-muted;
|
||||
}
|
||||
|
||||
.root:dark .store-entry-grid:incomplete .name, .root:dark .store-entry-grid:failed .name {
|
||||
.store-entry-grid:failed .jfx-text-field {
|
||||
-fx-text-fill: #ee4829;
|
||||
}
|
||||
|
||||
.root:dark .store-entry-grid:incomplete .name {
|
||||
-fx-text-fill: #aa473c;
|
||||
}
|
||||
|
||||
.root:light .store-entry-grid:incomplete .name, .root:light .store-entry-grid:failed .name {
|
||||
.root:light .store-entry-grid:incomplete .name {
|
||||
-fx-text-fill: #88352b;
|
||||
}
|
||||
|
||||
|
@ -133,7 +137,7 @@
|
|||
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2, 0.5, 0, 1);
|
||||
}
|
||||
|
||||
.store-entry-section-comp:top {
|
||||
.store-entry-section-comp:root {
|
||||
-fx-border-radius: 4px;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.third-party-dependency-list-comp .titled-pane {
|
||||
.titled-pane {
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.third-party-dependency-list-comp .titled-pane .content {
|
||||
.titled-pane .content {
|
||||
-fx-padding: 4;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue