Merge branch 1.7.3-fixes into master

This commit is contained in:
crschnick committed 2023-11-15 03:25:18 +00:00
1 parent 36a9a78896
commit b91eb0fda5
147 files changed
+1059 -3717

No files matched your search

@@ -2,6 +2,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.util.FailableRunnable;
import javafx.beans.property.Property;
@@ -19,6 +20,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class BrowserClipboard {
@@ -27,6 +29,11 @@ public class BrowserClipboard {
UUID uuid;
FileSystem.FileEntry baseDirectory;
List<FileSystem.FileEntry> entries;
public String toClipboardString() {
return entries.stream().map(fileEntry -> "\"" + fileEntry.getPath() + "\"").collect(
Collectors.joining(ShellDialects.getPlatformDefault().getNewLine().getNewLineString()));
}
}
public static final Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
@@ -67,9 +74,9 @@ public class BrowserClipboard {
@SneakyThrows
public static ClipboardContent startDrag(FileSystem.FileEntry base, List<FileSystem.FileEntry> selected) {
var content = new ClipboardContent();
var idea = UUID.randomUUID();
currentDragClipboard = new Instance(idea, base, new ArrayList<>(selected));
content.putString(idea.toString());
var id = UUID.randomUUID();
currentDragClipboard = new Instance(id, base, new ArrayList<>(selected));
content.putString(currentDragClipboard.toClipboardString());
return content;
}
@@ -88,9 +95,13 @@ public class BrowserClipboard {
return null;
}
if (currentDragClipboard == null) {
return null;
}
try {
var idea = UUID.fromString(dragboard.getString());
if (idea.equals(currentDragClipboard.uuid)) {
var s = dragboard.getString();
if (s != null && s.equals(currentDragClipboard.toClipboardString())) {
var current = currentDragClipboard;
currentDragClipboard = null;
return current;
@@ -7,10 +7,13 @@ import io.xpipe.app.browser.icon.DirectoryType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.browser.icon.FileType;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
@@ -26,12 +29,12 @@ import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -55,42 +58,35 @@ public class BrowserComp extends SimpleComp {
FileIconManager.loadIfNecessary();
});
var bookmarksList = new BrowserBookmarkList(model).createRegion();
VBox.setVgrow(bookmarksList, Priority.ALWAYS);
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
var bookmarksList = new BrowserBookmarkList(model).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
return false;
},
model.getOpenFileSystems(),
model.getSelected())))
.createRegion();
localDownloadStage.setPrefHeight(200);
localDownloadStage.setMaxHeight(200);
var vertical = new VBox(bookmarksList, localDownloadStage);
vertical.setFillWidth(true);
return false;
}, model.getOpenFileSystems(), model.getSelected())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var splitPane = new SplitPane(vertical, createTabs());
splitPane
.widthProperty()
.addListener(
// set sidebar width in pixels depending on split pane width
(obs, old, val) -> splitPane.setDividerPosition(0, 360 / splitPane.getWidth()));
var r = addBottomBar(splitPane);
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
// AppFont.small(r);
return r;
@@ -108,16 +104,12 @@ public class BrowserComp extends SimpleComp {
selected.setSpacing(10);
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(400);
return field;
})
.toList());
selected.getChildren().setAll(c.getList().stream().map(s -> {
var field = new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
}).toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
@@ -135,15 +127,14 @@ public class BrowserComp extends SimpleComp {
return layout;
}
private Node createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
private Comp<?> createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(() -> {
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
}, model.getOpenFileSystems())));
return multi.createRegion();
return multi;
}
private TabPane createTabPane() {
@@ -163,8 +154,7 @@ public class BrowserComp extends SimpleComp {
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel()
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
var modifying = new SimpleBooleanProperty();
@@ -180,9 +170,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(newValue))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
@@ -197,9 +187,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var toSelect = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getKey().equals(newValue))
var toSelect = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
.findAny()
.map(Map.Entry::getValue)
.orElse(null);
@@ -238,9 +228,9 @@ public class BrowserComp extends SimpleComp {
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
while (c.next()) {
for (var r : c.getRemoved()) {
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(r))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
.findAny()
.orElse(null);
@@ -263,19 +253,14 @@ public class BrowserComp extends SimpleComp {
ring.setMinSize(16, 16);
ring.setPrefSize(16, 16);
ring.setMaxSize(16, 16);
ring.progressProperty()
.bind(Bindings.createDoubleBinding(
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
tab.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
return model.getBusy().get() ? ring : logo;
},
PlatformThread.sync(model.getBusy())));
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
return model.getBusy().get() ? ring : logo;
}, PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(new OpenFileSystemComp(model).createSimple());
@@ -301,9 +286,7 @@ public class BrowserComp extends SimpleComp {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
});
}
});
@@ -88,9 +88,6 @@ public final class BrowserFileListModel {
.toList()
: all.getValue();
Comparator<BrowserEntry> tableComparator = comparatorProperty.getValue();
var comparator =
tableComparator != null ? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) : FILE_TYPE_COMPARATOR;
var listCopy = new ArrayList<>(filtered);
sort(listCopy);
shown.setValue(listCopy);
@@ -18,7 +18,6 @@ import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@Getter
@@ -38,6 +37,7 @@ public class BrowserModel {
selected.addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
selection.clear();
return;
}
@@ -60,11 +60,13 @@ public class BrowserModel {
public void reset() {
var list = new ArrayList<BrowserSavedState.Entry>();
openFileSystems.forEach(model -> {
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
}
});
synchronized (BrowserModel.this) {
openFileSystems.forEach(model -> {
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
}
});
}
// Don't override state if it is empty
if (list.size() == 0) {
@@ -86,8 +88,11 @@ public class BrowserModel {
}
var chosen = new ArrayList<>(selection);
for (OpenFileSystemModel openFileSystem : openFileSystems) {
closeFileSystemAsync(openFileSystem);
synchronized (BrowserModel.this) {
for (OpenFileSystemModel openFileSystem : openFileSystems) {
closeFileSystemAsync(openFileSystem);
}
}
if (chosen.size() == 0) {
@@ -101,9 +106,6 @@ public class BrowserModel {
public void closeFileSystemAsync(OpenFileSystemModel open) {
ThreadHelper.runAsync(() -> {
if (Objects.equals(selected.getValue(), open)) {
selected.setValue(null);
}
open.closeSync();
synchronized (BrowserModel.this) {
openFileSystems.remove(open);
@@ -111,33 +113,7 @@ public class BrowserModel {
});
}
public void openExistingFileSystemIfPresent(DataStoreEntryRef<? extends FileSystemStore> store) {
var found = openFileSystems.stream().filter(model -> Objects.equals(model.getEntry(), store)).findFirst();
if (found.isPresent()) {
selected.setValue(found.get());
} else {
openFileSystemAsync(store, null, null);
}
}
public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, String path, BooleanProperty externalBusy) {
// // Prevent multiple tabs in non browser modes
// if (!mode.equals(Mode.BROWSER)) {
// ThreadHelper.runFailableAsync(() -> {
// var open = openFileSystems.size() > 0 ? openFileSystems.get(0) : null;
// if (open != null) {
// open.closeSync();
// openFileSystems.remove(open);
// }
//
// var model = new OpenFileSystemModel(this, store);
// openFileSystems.add(model);
// selected.setValue(model);
// model.switchSync(store);
// });
// return;
// }
if (store == null) {
return;
}
@@ -146,15 +122,15 @@ public class BrowserModel {
OpenFileSystemModel model;
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new OpenFileSystemModel(this, store);
model.initFileSystem();
model.initSavedState();
// Prevent multiple calls from interfering with each other
synchronized (BrowserModel.this) {
model = new OpenFileSystemModel(this, store);
model.initFileSystem();
model.initSavedState();
openFileSystems.add(model);
// The tab pane doesn't automatically select new tabs
selected.setValue(model);
}
openFileSystems.add(model);
selected.setValue(model);
}
if (path != null) {
model.initWithGivenDirectory(path);
@@ -79,7 +79,7 @@ public class BrowserNavBar extends SimpleComp {
struc.get().setPromptText("Overview of " + model.getName());
})
.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN), s -> {
s.get().requestFocus();
})
.accessibleText("Current path");
@@ -6,8 +6,10 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@@ -27,22 +29,33 @@ public class BrowserOverviewComp extends SimpleComp {
@Override
@SneakyThrows
protected Region createSimple() {
// The open file system might have already been closed
if (model.getFileSystem() == null) {
return new Region();
}
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
// TODO: May be move this into another thread
var common = sc.getOsType().determineInterestingPaths(sc).stream()
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.filter(entry -> {
try {
return sc.getShellDialect()
.directoryExists(sc, entry.getPath())
.executeAndCheck();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList();
var commonOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(common), false);
var commonPlatform = FXCollections.<FileSystem.FileEntry>observableArrayList();
ThreadHelper.runFailableAsync(() -> {
var common = sc.getOsType().determineInterestingPaths(sc).stream()
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.filter(entry -> {
try {
return sc.getShellDialect()
.directoryExists(sc, entry.getPath())
.executeAndCheck();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList();
Platform.runLater(() -> {
commonPlatform.setAll(common);
});
});
var commonOverview = new BrowserFileOverviewComp(model, commonPlatform, false);
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview)
.apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));
@@ -73,8 +73,8 @@ public class BrowserTransferComp extends SimpleComp {
.apply(struc -> struc.get().setSpacing(10)),
button -> {
var p = new AnchorPane(button);
AnchorPane.setRightAnchor(button, 20.0);
AnchorPane.setTopAnchor(button, 20.0);
AnchorPane.setRightAnchor(button, 10.0);
AnchorPane.setTopAnchor(button, 10.0);
p.setPickOnBounds(false);
return p;
});
@@ -155,6 +155,6 @@ public class BrowserTransferComp extends SimpleComp {
});
}),
PlatformThread.sync(stage.getDownloading()));
return stack.createRegion();
return stack.styleClass("transfer").createRegion();
}
}
@@ -83,7 +83,7 @@ public class BrowserWelcomeComp extends SimpleComp {
ThreadHelper.runAsync(() -> {
model.restoreState(e, disable);
});
}).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
}).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
}).apply(struc -> {
VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10);
@@ -102,7 +102,7 @@ public class BrowserWelcomeComp extends SimpleComp {
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
model.restoreState(state);
actionEvent.consume();
}).grow(true, false);
}).grow(true, false).accessibleTextKey("restoreAllSessions");
layout.getChildren().add(tile.createRegion());
return layout;
@@ -1,12 +1,11 @@
package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.LocalStore;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -15,28 +14,6 @@ import java.util.List;
public class FileSystemHelper {
public static String getStartDirectory(OpenFileSystemModel model) throws Exception {
// Handle special case when file system creation has failed
if (model.getFileSystem() == null) {
return null;
}
ConnectionFileSystem fileSystem = (ConnectionFileSystem) model.getFileSystem();
var current = !model.isLocal()
? fileSystem
.getShellControl()
.executeSimpleStringCommand(
fileSystem.getShellControl().getShellDialect().getPrintWorkingDirectoryCommand())
: fileSystem
.getShell()
.get()
.getOsType()
.getHomeDirectory(fileSystem.getShell().get());
var r = resolveDirectoryPath(model, evaluatePath(model, adjustPath(model, current)));
validateDirectoryPath(model, r);
return r;
}
public static String adjustPath(OpenFileSystemModel model, String path) {
if (path == null) {
return null;
@@ -9,9 +9,8 @@ import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
public interface FileType {
@@ -45,10 +44,10 @@ public interface FileType {
return "." + r;
})
.toList();
.collect(Collectors.toSet());
var darkIcon = split[2].trim();
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
ALL.add(new FileType.Simple(id, lightIcon, darkIcon, filter.toArray(String[]::new)));
ALL.add(new FileType.Simple(id, lightIcon, darkIcon, filter));
}
}
});
@@ -59,9 +58,9 @@ public interface FileType {
private final String id;
private final IconVariant icon;
private final String[] endings;
private final Set<String> endings;
public Simple(String id, String lightIcon, String darkIcon, String... endings) {
public Simple(String id, String lightIcon, String darkIcon, Set<String> endings) {
this.icon = new IconVariant(lightIcon, darkIcon);
this.id = id;
this.endings = endings;
@@ -73,8 +72,7 @@ public interface FileType {
return false;
}
return Arrays.stream(endings)
.anyMatch(ending -> entry.getPath().toLowerCase().endsWith(ending.toLowerCase()));
return endings.contains(entry.getPath().toLowerCase(Locale.ROOT));
}
@Override
@@ -49,6 +49,7 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
button.setGraphic(graphic);
button.getStyleClass().add("dropdown-comp");
button.setAccessibleText("Dropdown actions");
return new SimpleCompStructure<>(button);
}
@@ -35,6 +35,7 @@ public class ListSelectorComp<T> extends SimpleComp {
for (var v : values) {
var cb = new CheckBox(null);
cbs.add(cb);
cb.setAccessibleText(toString.apply(v));
cb.setSelected(selected.contains(v));
cb.selectedProperty().addListener((c, o, n) -> {
if (n) {
@@ -34,11 +34,6 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
var loadingOverlay = new StackPane(loading);
loadingOverlay.getStyleClass().add("loading-comp");
loadingOverlay.getStyleClass().add("modal-pane");
loadingOverlay.visibleProperty().addListener((observable, oldValue, newValue) -> {
r.setOpacity(newValue ? r.getOpacity() * 0.25 : Math.min(1.0, r.getOpacity() * 4));
});
loadingOverlay.setVisible(showLoading.getValue());
var listener = new ChangeListener<Boolean>() {
@@ -6,8 +6,8 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.core.process.OsNameState;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.process.ShellStoreState;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@@ -41,11 +41,11 @@ public class OsLogoComp extends SimpleComp {
}
var ps = wrapper.getPersistentState().getValue();
if (!(ps instanceof ShellStoreState sss)) {
if (!(ps instanceof OsNameState ons)) {
return null;
}
return getImage(sss.getOsName());
return getImage(ons.getOsName());
},
wrapper.getPersistentState(), state));
var hide = BindingsHelper.map(img, s -> s != null);
@@ -51,6 +51,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
});
});
});
b.accessibleText(e.name());
vbox.getChildren().add(b.createRegion());
});
@@ -64,7 +65,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
}
UserReportComp.show(event.build());
})
.apply(new FancyTooltipAugment<>("reportIssue"));
.apply(new FancyTooltipAugment<>("reportIssue")).accessibleTextKey("reportIssue");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@@ -73,7 +74,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
.apply(new FancyTooltipAugment<>("visitGithubRepository"));
.apply(new FancyTooltipAugment<>("visitGithubRepository")).accessibleTextKey("visitGithubRepository");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@@ -92,7 +93,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
.apply(new FancyTooltipAugment<>("discord"));
.apply(new FancyTooltipAugment<>("discord")).accessibleTextKey("discord");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@@ -101,7 +102,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"));
.apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@@ -0,0 +1,81 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Region;
import lombok.Value;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
private final Comp<?> left;
private final Comp<?> center;
private Double initialWidth;
private Consumer<Double> onDividerChange;
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
this.left = left;
this.center = center;
}
@Override
public Structure createBase() {
var c = center.createRegion();
var sidebar = left.createRegion();
if (initialWidth != null) {
sidebar.setPrefWidth(initialWidth);
}
var r = new SplitPane(sidebar, c);
AtomicBoolean setInitial = new AtomicBoolean(false);
r.widthProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.doubleValue() <= 0) {
return;
}
if (!setInitial.get() && initialWidth != null) {
r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue());
setInitial.set(true);
}
});
SplitPane.setResizableWithParent(sidebar, false);
r.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
if (r.getWidth() <= 0) {
return;
}
if (onDividerChange != null) {
onDividerChange.accept(newValue.doubleValue() * r.getWidth());
}
});
r.getStyleClass().add("side-split-pane-comp");
return new Structure(sidebar, c, r, r.getDividers().get(0));
}
public SideSplitPaneComp withInitialWidth(double val) {
this.initialWidth = val;
return this;
}
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
this.onDividerChange = onDividerChange;
return this;
}
@Value
public static class Structure implements CompStructure<SplitPane> {
Region left;
Region center;
SplitPane pane;
SplitPane.Divider divider;
@Override
public SplitPane get() {
return pane;
}
}
}
@@ -60,6 +60,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("data-source-type");
cb.getStyleClass().add("choice-comp");
cb.setAccessibleText("Choose connection type");
return new SimpleCompStructure<>(cb);
}
}
@@ -1,6 +1,5 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
@@ -52,7 +51,7 @@ public class StoreCreationMenu {
cmd.setGraphic(new FontIcon("mdi2c-code-greater-than"));
cmd.textProperty().bind(AppI18n.observable("addCommand"));
cmd.setOnAction(event -> {
GuiDsStoreCreator.showCreation(null,
GuiDsStoreCreator.showCreation(DataStoreProviders.byName("cmd").orElseThrow(),
v -> DataStoreProvider.CreationCategory.COMMAND.equals(v.getCreationCategory()));
event.consume();
});
@@ -85,7 +84,7 @@ public class StoreCreationMenu {
script.setGraphic(new FontIcon("mdi2s-script-text-outline"));
script.textProperty().bind(AppI18n.observable("addScript"));
script.setOnAction(event -> {
GuiDsStoreCreator.showCreation(null,
GuiDsStoreCreator.showCreation(DataStoreProviders.byName("script").orElseThrow(),
v -> DataStoreProvider.CreationCategory.SCRIPT.equals(v.getCreationCategory()));
event.consume();
});
@@ -209,6 +209,7 @@ public abstract class StoreEntryComp extends SimpleComp {
action.execute();
});
});
button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(wrapper.getEntry().ref())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
@@ -22,7 +22,7 @@ public class StoreEntryListComp extends SimpleComp {
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10)))
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
.styleClass("top");
})
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
@@ -3,12 +3,17 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@@ -16,11 +21,25 @@ import javafx.scene.control.MenuButton;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListStatusComp extends SimpleComp {
private final Property<StoreSortMode> sortMode;
public StoreEntryListStatusComp() {
this.sortMode = new SimpleObjectProperty<>();
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
sortMode.unbind();
sortMode.bindBidirectional(val.getSortMode());
});
}
private Region createGroupListHeader() {
var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
@@ -44,12 +63,11 @@ public class StoreEntryListStatusComp extends SimpleComp {
StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, all);
var spacer = new Region();
var topBar = new HBox(label, spacer, count.createRegion());
AppFont.setSize(topBar, 1);
var c = count.createRegion();
var topBar = new HBox(label, c, Comp.hspacer().createRegion(), createDateSortButton().createRegion(), Comp.hspacer(2).createRegion(), createAlphabeticalSortButton().createRegion());
AppFont.setSize(label, 3);
AppFont.setSize(c, 3);
topBar.setAlignment(Pos.CENTER);
HBox.setHgrow(spacer, Priority.ALWAYS);
topBar.getStyleClass().add("top");
return topBar;
}
@@ -65,25 +83,117 @@ public class StoreEntryListStatusComp extends SimpleComp {
filter.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
s.getText().requestFocus();
});
var r = new StackPane(filter.createRegion());
r.setAlignment(Pos.CENTER);
r.getStyleClass().add("filter-bar");
AppFont.medium(r);
return r;
filter.apply(struc -> struc.get().sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
struc.getText().requestFocus();
}
}));
var f = filter.createRegion();
var hbox = new HBox(createButtons(), f);
hbox.setSpacing(8);
hbox.setAlignment(Pos.CENTER);
HBox.setHgrow(f, Priority.ALWAYS);
f.getStyleClass().add("filter-bar");
AppFont.medium(hbox);
return hbox;
}
private Region createButtons() {
var menu = new MenuButton(AppI18n.get("addConnections"), new FontIcon("mdi2p-plus-thick"));
menu.setAlignment(Pos.CENTER);
menu.setTextAlignment(TextAlignment.CENTER);
AppFont.medium(menu);
GrowAugment.create(true, false).augment(menu);
StoreCreationMenu.addButtons(menu);
menu.setOpacity(0.85);
menu.setMinWidth(Region.USE_PREF_SIZE);
return menu;
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
sortMode);
var alphabetical = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_DESC);
} else if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
} else {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC
|| sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
alphabetical.accessibleTextKey("sortAlphabetical");
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
sortMode);
var date = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
sortMode.setValue(StoreSortMode.DATE_DESC);
} else if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
sortMode.setValue(StoreSortMode.DATE_ASC);
} else {
sortMode.setValue(StoreSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC
|| sortMode.getValue() == StoreSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
date.accessibleTextKey("sortLastUsed");
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
@Override
public Region createSimple() {
var bar = new VBox(createGroupListHeader(), createGroupListFilter(), createButtons());
var bar = new VBox(createGroupListHeader(), createGroupListFilter());
bar.setFillWidth(true);
bar.getStyleClass().add("bar");
bar.getStyleClass().add("store-header-bar");
@@ -190,6 +190,10 @@ public class StoreEntryWrapper {
}
public void executeDefaultAction() throws Exception {
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return;
}
if (getEntry().getValidity() == DataStoreEntry.Validity.INCOMPLETE) {
editDialog();
return;
@@ -1,12 +1,12 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppActionLinkDetector;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
public class StoreLayoutComp extends SimpleComp {
@@ -19,14 +19,13 @@ public class StoreLayoutComp extends SimpleComp {
@Override
protected Region createSimple() {
var listComp = new StoreEntryListComp().apply(GrowAugment.create(false, true));
var r = new BorderPane();
var listR = listComp.createRegion();
var groupHeader = new StoreSidebarComp().createRegion();
r.setLeft(groupHeader);
r.setCenter(listR);
r.getStyleClass().add("layout");
return r;
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
}).createStructure();
struc.getLeft().setMinWidth(260);
struc.getLeft().setMaxWidth(500);
struc.get().getStyleClass().add("store-layout");
return struc.get();
}
}
@@ -53,7 +53,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(30))
.apply(struc -> struc.get().setPrefWidth(30))
.focusTraversable()
.accessibleText("Expand")
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getShownChildren()).isEqualTo(0)))
.grow(false, true)
@@ -85,7 +87,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
return new VerticalComp(List.of(
new HorizontalComp(topEntryList)
.apply(struc -> struc.get().setFillHeight(true)),
Comp.separator().visible(expanded),
Comp.separator().hide(BindingsHelper.persist(expanded.not())),
new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
@@ -74,7 +74,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(20))
.apply(struc -> struc.get().setPrefWidth(20))
.focusTraversable()
.accessibleText("Expand")
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getAllChildren()).isEqualTo(0)))
.grow(false, true)
@@ -13,11 +13,10 @@ public class StoreSidebarComp extends SimpleComp {
@Override
protected Region createSimple() {
var sideBar = new VerticalComp(List.of(
new StoreEntryListStatusComp(),
new StoreSortComp(),
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()),
Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar").vgrow()));
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()).styleClass("color-box").styleClass("gray"),
Comp.of(() -> new Region()).styleClass("bar").styleClass("color-box").styleClass("gray").styleClass("filler-bar").vgrow()));
sideBar.apply(struc -> struc.get().setFillWidth(true));
sideBar.styleClass("sidebar");
sideBar.prefWidth(240);
@@ -1,122 +0,0 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import java.util.List;
public class StoreSortComp extends SimpleComp {
private final Property<StoreSortMode> sortMode;
public StoreSortComp() {
this.sortMode = new SimpleObjectProperty<>();
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
sortMode.unbind();
sortMode.bindBidirectional(val.getSortMode());
});
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
sortMode);
var alphabetical = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_DESC);
} else if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
} else {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC
|| sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
sortMode);
var date = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
sortMode.setValue(StoreSortMode.DATE_DESC);
} else if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
sortMode.setValue(StoreSortMode.DATE_ASC);
} else {
sortMode.setValue(StoreSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC
|| sortMode.getValue() == StoreSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
private Comp<?> createSortButtonBar() {
return new HorizontalComp(List.of(createDateSortButton(), createAlphabeticalSortButton())).apply(struc -> {
struc.get().setMinHeight(40);
struc.get().setPrefHeight(40);
struc.get().setMaxHeight(40);
}).styleClass("bar").styleClass("store-sort-bar");
}
@Override
protected Region createSimple() {
return createSortButtonBar().createRegion();
}
}
@@ -50,7 +50,7 @@ public class AppCache {
var path = getPath(key);
if (Files.exists(path)) {
try {
var tree = JsonConfigHelper.readConfig(path);
var tree = JsonConfigHelper.readRaw(path);
if (tree.isMissingNode()) {
return notPresent.get();
}
@@ -2,6 +2,8 @@ package io.xpipe.app.core;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableSupplier;
import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream;
@@ -17,7 +19,7 @@ public class AppCharsetter extends Charsetter {
Charsetter.INSTANCE = new AppCharsetter();
}
public Result read(FailableSupplier<InputStream, Exception> in, FailableConsumer<InputStreamReader, Exception> con)
public Result read(FailableSupplier<InputStream> in, FailableConsumer<InputStreamReader, Exception> con)
throws Exception {
checkInit();
@@ -47,7 +47,7 @@ public class AppExtensionManager {
}
if (load) {
INSTANCE.addNativeLibrariesToPath();
// INSTANCE.addNativeLibrariesToPath();
try {
XPipeServiceProviders.load(INSTANCE.extendedLayer);
MessageExchangeImpls.loadAll();
@@ -5,18 +5,13 @@ import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.MarkdownHelper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Modality;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.function.UnaryOperator;
@@ -39,14 +34,14 @@ public class AppGreetings {
return tp;
}
private static TitledPane createTos() {
private static TitledPane createEula() {
var tp = new TitledPane();
tp.setExpanded(false);
tp.setText(AppI18n.get("tos"));
tp.setText(AppI18n.get("eula"));
tp.setAlignment(Pos.CENTER_LEFT);
AppFont.normal(tp);
AppResources.with(AppResources.XPIPE_MODULE, "misc/tos.md", file -> {
AppResources.with(AppResources.XPIPE_MODULE, "misc/eula.md", file -> {
var md = Files.readString(file);
var markdown = new MarkdownComp(md, UnaryOperator.identity()).createRegion();
tp.setContent(markdown);
@@ -67,7 +62,7 @@ public class AppGreetings {
alert.setAlertType(Alert.AlertType.NONE);
alert.initModality(Modality.APPLICATION_MODAL);
var content = List.of(createIntroduction(), createTos());
var content = List.of(createIntroduction(), createEula());
var accordion = new Accordion(content.toArray(TitledPane[]::new));
accordion.setExpandedPane(content.get(0));
accordion.expandedPaneProperty().addListener((observable, oldValue, newValue) -> {
@@ -98,26 +93,6 @@ public class AppGreetings {
alert.getDialogPane().setContent(layout);
{
var view = new ButtonType(AppI18n.get("print"), ButtonBar.ButtonData.OTHER);
alert.getButtonTypes().add(view);
Button button = (Button) alert.getDialogPane().lookupButton(view);
button.visibleProperty().bind(read);
button.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
try {
var temp = Files.createTempFile("tos", ".html");
AppResources.with(AppResources.XPIPE_MODULE, "misc/tos.md", file -> {
Files.writeString(
temp,
MarkdownHelper.toHtml(Files.readString(file), UnaryOperator.identity()));
});
Hyperlinks.open(temp.toUri().toString());
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
event.consume();
});
}
{
var buttonType = new ButtonType(AppI18n.get("confirm"), ButtonBar.ButtonData.OK_DONE);
alert.getButtonTypes().add(buttonType);
@@ -10,7 +10,10 @@ import io.xpipe.app.util.LicenseProvider;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +21,15 @@ import java.util.List;
@Getter
public class AppLayoutModel {
@Data
@Builder
@Jacksonized
public static class SavedState {
double sidebarWidth;
double browserConnectionsWidth;
}
private static AppLayoutModel INSTANCE;
public static AppLayoutModel get() {
@@ -25,13 +37,22 @@ public class AppLayoutModel {
}
public static void init() {
INSTANCE = new AppLayoutModel();
var state = AppCache.get("layoutState", SavedState.class, () -> new SavedState(260, 300));
INSTANCE = new AppLayoutModel(state);
}
public static void reset() {
AppCache.update("layoutState", INSTANCE.savedState);
INSTANCE = null;
}
@Getter
private final SavedState savedState;
private final List<Entry> entries;
private final Property<Entry> selected;
public AppLayoutModel() {
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
}
@@ -182,7 +182,7 @@ public class AppMainWindow {
}
private void saveState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return;
}
@@ -201,7 +201,7 @@ public class AppMainWindow {
}
private WindowState loadState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return null;
}
@@ -230,6 +230,9 @@ public class AppMainWindow {
}
public void initialize() {
stage.setMinWidth(550);
stage.setMinHeight(400);
initializeWindow();
setupListeners();
windowActive.set(true);
@@ -13,23 +13,49 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class AppResources {
public static final String XPIPE_MODULE = "io.xpipe.app";
private static ModuleFileSystem openFileSystem(String module) throws IOException {
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
public static void reset() {
fileSystems.forEach((s, moduleFileSystem) -> {
try {
moduleFileSystem.close();
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
});
fileSystems.clear();
}
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
var layer = AppExtensionManager.getInstance() != null
? AppExtensionManager.getInstance().getExtendedLayer()
: null;
// Only cache file systems with extended layer
if (layer != null && fileSystems.containsKey(module)) {
return fileSystems.get(module);
}
if (layer == null) {
layer = ModuleLayer.boot();
}
return (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
if (AppExtensionManager.getInstance() != null) {
fileSystems.put(module, fs);
}
return fs;
}
public static Optional<URL> getResourceURL(String module, String file) {
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
var url = f.getWrappedPath().toUri().toURL();
return Optional.of(url);
@@ -64,7 +90,8 @@ public class AppResources {
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(path);
con.accept(f);
} catch (IOException e) {
@@ -73,7 +100,8 @@ public class AppResources {
}
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var url = fs.getPath("").getWrappedPath().toUri().toURL();
if (!url.getProtocol().equals("jar")) {
return false;
@@ -45,8 +45,9 @@ public class AppSocketServer {
}
public static void init() {
int port = -1;
try {
var port = BeaconConfig.getUsedPort();
port = BeaconConfig.getUsedPort();
INSTANCE = new AppSocketServer(port);
INSTANCE.createSocketListener();
@@ -56,7 +57,7 @@ public class AppSocketServer {
.handle();
} catch (Exception ex) {
// Not terminal!
ErrorEvent.fromThrowable(ex).build().handle();
ErrorEvent.fromThrowable(ex).description("Unable to start local socket server on port " + port).build().handle();
}
}
@@ -35,6 +35,7 @@ public class AppTheme {
private static final PseudoClass DARK = PseudoClass.getPseudoClass("dark");
private static final PseudoClass PRETTY = PseudoClass.getPseudoClass("pretty");
private static final PseudoClass PERFORMANCE = PseudoClass.getPseudoClass("performance");
private static boolean init;
public static void initThemeHandlers(Window stage) {
if (AppPrefs.get() == null) {
@@ -60,6 +61,10 @@ public class AppTheme {
}
public static void init() {
if (init) {
return;
}
if (AppPrefs.get() == null) {
Theme.getDefaultLightTheme().apply();
return;
@@ -97,6 +102,8 @@ public class AppTheme {
AppPrefs.get().theme.addListener((c, o, n) -> {
changeTheme(n);
});
init = true;
}
private static void setDefault(boolean dark) {
@@ -68,6 +68,7 @@ public class BaseMode extends OperationMode {
StoreViewState.reset();
DataStorage.reset();
AppPrefs.reset();
AppResources.reset();
AppExtensionManager.reset();
AppDataLock.unlock();
// Shut down socket server last to keep a non-daemon thread running
@@ -6,7 +6,6 @@ import io.xpipe.app.core.AppMainWindow;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.update.CommercializationAlert;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.util.UnlockAlert;
import javafx.stage.Stage;
@@ -24,7 +23,6 @@ public class GuiMode extends PlatformMode {
UnlockAlert.showIfNeeded();
UpdateChangelogAlert.showIfNeeded();
CommercializationAlert.showIfNeeded();
AppGreetings.showIfNeeded();
TrackEvent.info("mode", "Waiting for window setup completion ...");
@@ -62,6 +62,7 @@ public abstract class PlatformMode extends OperationMode {
TrackEvent.info("mode", "Shutting down platform components");
onSwitchFrom();
StoreViewState.reset();
AppLayoutModel.reset();
PlatformState.teardown();
TrackEvent.info("mode", "Platform shutdown finished");
BACKGROUND.finalTeardown();
@@ -22,6 +22,10 @@ import java.util.List;
public interface DataStoreProvider {
default boolean editByDefault() {
return false;
}
default boolean canMoveCategories() {
return true;
}
@@ -1,6 +1,7 @@
package io.xpipe.app.fxcomps;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.Shortcuts;
@@ -83,6 +84,15 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> struc.get().setPrefHeight(height));
}
public Comp<S> maxWidth(int width) {
return apply(struc -> struc.get().setMaxWidth(width));
}
public Comp<S> maxHeight(int height) {
return apply(struc -> struc.get().setMaxHeight(height));
}
public Comp<S> hgrow() {
return apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
}
@@ -130,10 +140,18 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> struc.get().getStyleClass().add(styleClass));
}
public Comp<S> accessibleText(ObservableValue<String> text) {
return apply(struc -> struc.get().accessibleTextProperty().bind(text));
}
public Comp<S> accessibleText(String text) {
return apply(struc -> struc.get().setAccessibleText(text));
}
public Comp<S> accessibleTextKey(String key) {
return apply(struc -> struc.get().accessibleTextProperty().bind(AppI18n.observable(key)));
}
public Comp<S> grow(boolean width, boolean height) {
return apply(GrowAugment.create(width, height));
}
@@ -6,10 +6,8 @@ import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
@@ -23,7 +21,6 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@@ -32,7 +29,6 @@ import lombok.RequiredArgsConstructor;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@RequiredArgsConstructor
@@ -128,6 +124,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
StoreCreationMenu.addButtons(m);
return m;
})
.accessibleTextKey("addConnection")
.padding(new Insets(-2))
.styleClass(Styles.RIGHT_PILL)
.grow(false, true);
@@ -155,26 +152,6 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return popover;
}
protected Region createGraphic(T s) {
var provider = DataStoreProviders.byStore(s);
var imgView = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s), 16)
.createRegion();
var name = DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(s))
.findAny()
.flatMap(store -> {
if (mode == Mode.PROXY && ShellStore.isLocal(store.asNeeded())) {
return Optional.of(AppI18n.get("none"));
}
return DataStorage.get().getStoreDisplayName(store);
})
.orElse(AppI18n.get("unknown"));
return new Label(name, imgView);
}
private String toName(DataStoreEntry entry) {
if (entry == null) {
return null;
@@ -226,7 +203,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
})
.styleClass("choice-comp");
var r = button.grow(true, false).createRegion();
var r = button.grow(true, false).accessibleText("Select connection").createRegion();
var icon = new FontIcon("mdal-keyboard_arrow_down");
icon.setDisable(true);
icon.setPickOnBounds(false);
@@ -28,7 +28,7 @@ public class FilterComp extends Comp<FilterComp.Structure> {
@Override
public Structure createBase() {
var fi = new FontIcon("mdi2m-magnify");
var bgLabel = new Label("Search ...", fi);
var bgLabel = new Label("Search", fi);
bgLabel.getStyleClass().add("filter-background");
var filter = new TextField();
filter.setAccessibleText("Filter");
@@ -97,6 +97,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
extendedDescription.getStyleClass().add(Styles.ACCENT);
extendedDescription.setPadding(new Insets(0, 6, 0, 6));
extendedDescription.getStyleClass().add("long-description");
extendedDescription.setAccessibleText("Help");
AppFont.header(extendedDescription);
extendedDescription.setOnAction(e -> popover.show(extendedDescription));
@@ -1,5 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
@@ -41,68 +42,52 @@ public class StoreCategoryComp extends SimpleComp {
@Override
protected Region createSimple() {
var i = Bindings.createStringBinding(
() -> {
if (!DataStorage.get().supportsSharing()) {
return "mdal-keyboard_arrow_right";
}
var i = Bindings.createStringBinding(() -> {
if (!DataStorage.get().supportsSharing()) {
return "mdal-keyboard_arrow_right";
}
return category.getShare().getValue() ?
"mdi2a-account-convert" : "mdi2a-account-cancel";
},
category.getShare());
return category.getShare().getValue() ? "mdi2a-account-convert" : "mdi2a-account-cancel";
}, category.getShare());
var icon = new IconButtonComp(i).apply(struc -> AppFont.small(struc.get())).apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
struc.get().setPadding(new Insets(0, 0, 6, 0));
struc.get().setFocusTraversable(false);
});
var name = new LazyTextFieldComp(category.nameProperty())
.apply(struc -> {
struc.get().prefWidthProperty().unbind();
struc.get().setPrefWidth(150);
struc.getTextField().minWidthProperty().bind(struc.get().widthProperty());
})
.styleClass("name")
.createRegion();
var name = new LazyTextFieldComp(category.nameProperty()).apply(struc -> {
struc.get().prefWidthProperty().unbind();
struc.get().setPrefWidth(150);
struc.getTextField().minWidthProperty().bind(struc.get().widthProperty());
}).styleClass("name").createRegion();
var showing = new SimpleBooleanProperty();
var settings = new IconButtonComp("mdomz-settings")
.styleClass("settings")
.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> {
var settings = new IconButtonComp("mdomz-settings").styleClass("settings").apply(
new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> {
var cm = createContextMenu(name);
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = BindingsHelper.filteredContentBinding(
category.getContainedEntries(),
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString());
var shownList = BindingsHelper.filteredContentBinding(category.getContainedEntries(), storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(StoreViewState.get().getFilterString().getValue());
}, StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
var hover = new SimpleBooleanProperty();
var h = new HorizontalComp(List.of(
icon,
Comp.hspacer(4),
Comp.of(() -> name),
Comp.hspacer(),
count.hide(BindingsHelper.persist(hover.or(showing))),
settings.hide(BindingsHelper.persist(hover.not().and(showing.not())))));
h.apply(struc -> hover.bind(struc.get().hoverProperty()));
h.apply(struc -> struc.get().setOnMouseClicked(event -> {
category.select();
event.consume();
}));
h.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
var focus = new SimpleBooleanProperty();
var h = new HorizontalComp(
List.of(icon, Comp.hspacer(4), Comp.of(() -> name), Comp.hspacer(), count.hide(BindingsHelper.persist(hover.or(showing).or(focus))),
settings.hide(BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not())))));
h.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
h.styleClass("category-button");
var l = category.getChildren()
.sorted(Comparator.comparing(
storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
var categoryButton = new ButtonComp(null, h.createRegion(), category::select).styleClass("category-button").apply(
struc -> hover.bind(struc.get().hoverProperty())).apply(struc -> focus.bind(struc.get().focusedProperty())).accessibleText(
category.nameProperty()).grow(true, false);
var l = category.getChildren().sorted(Comparator.comparing(storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
var emptyBinding = Bindings.isEmpty(category.getChildren());
var v = new VerticalComp(List.of(h, Comp.separator().hide(emptyBinding), Comp.vspacer(5).hide(emptyBinding), children.hide(emptyBinding)));
var v = new VerticalComp(
List.of(categoryButton, children.hide(emptyBinding)));
v.styleClass("category");
v.apply(struc -> {
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
@@ -119,9 +104,7 @@ public class StoreCategoryComp extends SimpleComp {
var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick"));
newCategory.setOnAction(event -> {
DataStorage.get()
.addStoreCategory(
DataStoreCategory.createNew(category.getCategory().getUuid(), "New category"));
DataStorage.get().addStoreCategory(DataStoreCategory.createNew(category.getCategory().getUuid(), "New category"));
});
contextMenu.getItems().add(newCategory);
@@ -132,14 +115,14 @@ public class StoreCategoryComp extends SimpleComp {
} else {
return AppI18n.get("share");
}
},category.getShare()));
}, category.getShare()));
share.graphicProperty().bind(Bindings.createObjectBinding(() -> {
if (category.getShare().getValue()) {
return new FontIcon("mdi2b-block-helper");
} else {
return new FontIcon("mdi2s-share");
}
},category.getShare()));
}, category.getShare()));
share.setOnAction(event -> {
category.getShare().setValue(!category.getShare().getValue());
});
@@ -63,8 +63,8 @@ public class AboutComp extends Comp<CompStructure<?>> {
.grow(true, false),
null)
.addComp(
new TileButtonComp("termsOfService", "termsOfServiceDescription", "mdi2c-card-text-outline", e -> {
Hyperlinks.open(Hyperlinks.TOS);
new TileButtonComp("eula", "eulaDescription", "mdi2c-card-text-outline", e -> {
Hyperlinks.open(Hyperlinks.EULA);
e.consume();
})
.grow(true, false),
@@ -112,8 +112,7 @@ public class AppPrefs {
return connectionTimeOut;
}
private final BooleanProperty saveWindowLocationInternal = typed(new SimpleBooleanProperty(false), Boolean.class);
public final ReadOnlyBooleanProperty saveWindowLocation = saveWindowLocationInternal;
private final BooleanProperty saveWindowLocation = typed(new SimpleBooleanProperty(true), Boolean.class);
// External terminal
// =================
@@ -336,6 +335,10 @@ public class AppPrefs {
return windowOpacity;
}
public ObservableBooleanValue saveWindowLocation() {
return saveWindowLocation;
}
public ObservableBooleanValue developerDisableUpdateVersionCheck() {
return developerDisableUpdateVersionCheckEffective;
}
@@ -591,7 +594,7 @@ public class AppPrefs {
Setting.of("useSystemFont", BooleanField.ofBooleanType(useSystemFontInternal).render(() -> new CustomToggleControl()), useSystemFontInternal),
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
Setting.of("language", languageControl, languageInternal)),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocationInternal).render(() -> new CustomToggleControl()), saveWindowLocationInternal))),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocation).render(() -> new CustomToggleControl()), saveWindowLocation))),
Category.of(
"connections",
Group.of(
@@ -33,7 +33,7 @@ public class JsonStorageHandler implements StorageHandler {
private JsonNode getContent(String key) {
if (content == null) {
content = (ObjectNode) JsonConfigHelper.readConfig(file);
content = JsonConfigHelper.readConfigObject(file);
}
return content.get(key);
}
@@ -9,7 +9,6 @@ import com.dlsc.preferencesfx.model.Group;
import com.dlsc.preferencesfx.model.Setting;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LockChangeAlert;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings;
@@ -58,7 +57,7 @@ public class VaultCategory extends AppPrefsCategory {
@SneakyThrows
public Category create() {
var pro = LicenseProvider.get().getFeature("gitVault").isSupported();
var pro = true;
BooleanField enable = BooleanField.ofBooleanType(prefs.enableGitStorage)
.editable(pro)
.render(() -> {
@@ -33,7 +33,6 @@ public abstract class DataStorage {
public static final UUID LOCAL_ID = UUID.fromString("f0ec68aa-63f5-405c-b178-9a4454556d6b");
private static final String PERSIST_PROP = "io.xpipe.storage.persist";
private static final String IMMUTABLE_PROP = "io.xpipe.storage.immutable";
private static DataStorage INSTANCE;
protected final Path dir;
@@ -41,8 +40,10 @@ public abstract class DataStorage {
@Getter
protected final List<DataStoreCategory> storeCategories;
protected final Map<DataStoreEntry, DataStoreEntry> storeEntries;
@Getter
protected final Set<DataStoreEntry> storeEntries;
protected final Set<DataStoreEntry> storeEntriesSet;
@Getter
@Setter
@@ -53,7 +54,8 @@ public abstract class DataStorage {
public DataStorage() {
this.dir = AppPrefs.get().storageDirectory().getValue();
this.storeEntries = Collections.newSetFromMap(new ConcurrentHashMap<>());
this.storeEntries = new ConcurrentHashMap<>();
this.storeEntriesSet = storeEntries.keySet();
this.storeCategories = new CopyOnWriteArrayList<>();
}
@@ -138,7 +140,7 @@ public abstract class DataStorage {
var changed = new AtomicBoolean(false);
do {
changed.set(false);
storeEntries.forEach(dataStoreEntry -> {
storeEntries.keySet().forEach(dataStoreEntry -> {
if (makeValid ? dataStoreEntry.tryMakeValid() : dataStoreEntry.tryMakeInvalid()) {
changed.set(true);
}
@@ -241,7 +243,7 @@ public abstract class DataStorage {
public void deleteChildren(DataStoreEntry e) {
var c = getDeepStoreChildren(e);
c.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(c);
this.storeEntriesSet.removeAll(c);
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
refreshValidities(false);
saveAsync();
@@ -257,7 +259,7 @@ public abstract class DataStorage {
.toList();
toDelete.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(toDelete);
this.storeEntriesSet.removeAll(toDelete);
this.listeners.forEach(l -> l.onStoreRemove(toDelete.toArray(DataStoreEntry[]::new)));
refreshValidities(false);
saveAsync();
@@ -286,8 +288,9 @@ public abstract class DataStorage {
}
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) {
if (storeEntries.contains(e)) {
return e;
var found = storeEntries.get(e);
if (found != null) {
return found;
}
var byId = getStoreEntryIfPresent(e.getUuid()).orElse(null);
@@ -295,6 +298,10 @@ public abstract class DataStorage {
return byId;
}
if (getStoreCategoryIfPresent(e.getCategoryUuid()).isEmpty()) {
e.setCategoryUuid(DEFAULT_CATEGORY_UUID);
}
var syntheticParent = getSyntheticParent(e);
if (syntheticParent.isPresent()) {
addStoreEntryIfNotPresent(syntheticParent.get());
@@ -307,7 +314,7 @@ public abstract class DataStorage {
}
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
this.storeEntries.put(e, e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
@@ -321,7 +328,7 @@ public abstract class DataStorage {
public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) {
for (DataStoreEntry e : es) {
if (storeEntries.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) {
if (storeEntriesSet.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) {
return;
}
@@ -337,7 +344,7 @@ public abstract class DataStorage {
}
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
this.storeEntries.put(e, e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
@@ -379,7 +386,7 @@ public abstract class DataStorage {
return;
}
storeEntries.forEach(entry -> {
storeEntriesSet.forEach(entry -> {
if (entry.getCategoryUuid().equals(cat.getUuid())) {
entry.setCategoryUuid(DEFAULT_CATEGORY_UUID);
}
@@ -459,7 +466,7 @@ public abstract class DataStorage {
try {
var provider = entry.getProvider();
return Optional.ofNullable(provider.getDisplayParent(entry))
.filter(dataStoreEntry -> storeEntries.contains(dataStoreEntry));
.filter(dataStoreEntry -> storeEntriesSet.contains(dataStoreEntry));
} catch (Exception ex) {
return Optional.empty();
}
@@ -559,7 +566,7 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntries.stream()
return storeEntriesSet.stream()
.filter(n -> n.getStore() == store
|| (n.getStore() != null
&& Objects.equals(store.getClass(), n.getStore().getClass())
@@ -592,7 +599,7 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull String name) {
return storeEntries.stream()
return storeEntriesSet.stream()
.filter(n -> n.getName().equalsIgnoreCase(name))
.findFirst();
}
@@ -614,7 +621,11 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(UUID id) {
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
return storeEntriesSet.stream().filter(e -> e.getUuid().equals(id)).findAny();
}
public Set<DataStoreEntry> getStoreEntries() {
return storeEntriesSet;
}
public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) {
@@ -99,6 +99,27 @@ public class DataStoreEntry extends StorageElement {
this.storePersistentStateNode = storePersistentState;
}
private DataStoreEntry(
Path directory,
UUID uuid,
UUID categoryUuid,
String name,
Instant lastUsed,
Instant lastModified,
DataStore store
) {
super(directory, uuid, name, lastUsed, lastModified, false);
this.categoryUuid = categoryUuid;
this.store = store;
this.storeNode = null;
this.validity = Validity.COMPLETE;
this.configuration = Configuration.defaultConfiguration();
this.expanded = false;
this.color = null;
this.provider = null;
this.storePersistentStateNode = null;
}
@Override
public boolean equals(Object o) {
return o == this || (o instanceof DataStoreEntry e && e.getUuid().equals(getUuid()));
@@ -114,6 +135,18 @@ public class DataStoreEntry extends StorageElement {
return getName();
}
public static DataStoreEntry createTempWrapper(@NonNull DataStore store) {
return new DataStoreEntry(
null,
UUID.randomUUID(),
DataStorage.get().getSelectedCategory().getUuid(),
UUID.randomUUID().toString(),
Instant.now(),
Instant.now(),
store
);
}
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
return createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, store);
}
@@ -121,6 +154,8 @@ public class DataStoreEntry extends StorageElement {
@SneakyThrows
public static DataStoreEntry createNew(
@NonNull UUID uuid, @NonNull UUID categoryUuid, @NonNull String name, @NonNull DataStore store) {
var node = DataStorageWriter.storeToNode(store);
var validity = DataStorageParser.storeFromNode(node) == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE;
var entry = new DataStoreEntry(
null,
uuid,
@@ -128,9 +163,9 @@ public class DataStoreEntry extends StorageElement {
name,
Instant.now(),
Instant.now(),
DataStorageWriter.storeToNode(store),
node,
true,
store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE,
validity,
Configuration.defaultConfiguration(),
null,
false, null
@@ -26,6 +26,8 @@ public class StandardStorage extends DataStorage {
@Getter
private final GitStorageHandler gitStorageHandler;
private boolean loaded;
StandardStorage() {
this.gitStorageHandler = LicenseProvider.get().createStorageHandler();
this.gitStorageHandler.init(dir);
@@ -109,7 +111,6 @@ public class StandardStorage extends DataStorage {
public synchronized void load() {
this.gitStorageHandler.prepareForLoad();
var newSession = isNewSession();
var storesDir = getStoresDir();
var categoriesDir = getCategoriesDir();
@@ -173,6 +174,12 @@ public class StandardStorage extends DataStorage {
storeCategories.add(cat);
}
if (getStoreCategoryIfPresent(CUSTOM_SCRIPTS_CATEGORY_UUID).isEmpty()) {
var cat = DataStoreCategory.createNew(ALL_SCRIPTS_CATEGORY_UUID, CUSTOM_SCRIPTS_CATEGORY_UUID, "Custom");
cat.setDirectory(categoriesDir.resolve(CUSTOM_SCRIPTS_CATEGORY_UUID.toString()));
storeCategories.add(cat);
}
if (getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).isEmpty()) {
storeCategories.add(new DataStoreCategory(
categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()),
@@ -218,7 +225,7 @@ public class StandardStorage extends DataStorage {
entry.setCategoryUuid(null);
}
storeEntries.add(entry);
storeEntries.put(entry, entry);
} catch (IOException ex) {
// IO exceptions are not expected
exception.set(ex);
@@ -241,7 +248,7 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(exception.get()).handle();
}
storeEntries.forEach(dataStoreCategory -> {
storeEntriesSet.forEach(dataStoreCategory -> {
if (dataStoreCategory.getCategoryUuid() == null
|| getStoreCategoryIfPresent(dataStoreCategory.getCategoryUuid())
.isEmpty()) {
@@ -253,24 +260,24 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
}
var hasFixedLocal = storeEntries.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID));
var hasFixedLocal = storeEntriesSet.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID));
if (!hasFixedLocal) {
var e = DataStoreEntry.createNew(
LOCAL_ID, DataStorage.DEFAULT_CATEGORY_UUID, "Local Machine", new LocalStore());
e.setDirectory(getStoresDir().resolve(LOCAL_ID.toString()));
e.setConfiguration(
StorageElement.Configuration.builder().deletable(false).build());
storeEntries.add(e);
storeEntries.put(e, e);
e.validate();
}
var local = DataStorage.get().getStoreEntry(LOCAL_ID);
if (storeEntries.stream().noneMatch(entry -> entry.getColor() != null)) {
if (storeEntriesSet.stream().noneMatch(entry -> entry.getColor() != null)) {
local.setColor(DataStoreColor.BLUE);
}
refreshValidities(true);
storeEntries.forEach(entry -> {
storeEntriesSet.forEach(entry -> {
var syntheticParent = getSyntheticParent(entry);
syntheticParent.ifPresent(entry1 -> {
addStoreEntryIfNotPresent(entry1);
@@ -280,8 +287,8 @@ public class StandardStorage extends DataStorage {
// Save to apply changes
if (!hasFixedLocal) {
storeEntries.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore);
storeEntries.stream().filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).forEach(entry -> {
storeEntriesSet.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore);
storeEntriesSet.stream().filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).forEach(entry -> {
entry.dirty = true;
entry.setStoreNode(DataStorageWriter.storeToNode(entry.getStore()));
});
@@ -289,9 +296,15 @@ public class StandardStorage extends DataStorage {
}
deleteLeftovers();
loaded = true;
}
public synchronized void save() {
if (!loaded) {
return;
}
this.gitStorageHandler.prepareForSave();
try {
@@ -322,7 +335,7 @@ public class StandardStorage extends DataStorage {
}
});
storeEntries.stream()
storeEntriesSet.stream()
.filter(dataStoreEntry -> dataStoreEntry.shouldSave())
.forEach(e -> {
try {
@@ -1,43 +0,0 @@
package io.xpipe.app.update;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.*;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.stage.Modality;
import java.nio.file.Files;
import java.util.function.UnaryOperator;
public class CommercializationAlert {
public static void showIfNeeded() {
if (AppState.get().isInitialLaunch()) {
AppCache.update("commercializationSeen", true);
return;
}
boolean set = AppCache.get("commercializationSeen", Boolean.class, () -> false);
if (set) {
return;
}
AppWindowHelper.showBlockingAlert(
alert -> {
alert.setTitle(AppI18n.get("news"));
alert.setAlertType(Alert.AlertType.NONE);
alert.initModality(Modality.NONE);
AppResources.with(AppResources.XPIPE_MODULE, "misc/commercialization.md", file -> {
var md = Files.readString(file);
var markdown = new MarkdownComp(md, UnaryOperator.identity()).createRegion();
alert.getDialogPane().setContent(markdown);
});
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
});
AppCache.update("commercializationSeen", true);
}
}
@@ -4,17 +4,13 @@ import io.xpipe.app.issue.ErrorEvent;
public class Hyperlinks {
public static final String WEBSITE = "https://xpipe.io";
public static final String DOCUMENTATION = "https://docs.xpipe.io";
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
public static final String ROADMAP = "https://xpipe.kampsite.co/";
public static final String PRIVACY = "https://github.com/xpipe-io/xpipe/blob/master/PRIVACY.md";
public static final String TOS = "https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/misc/tos.md";
public static final String SECURITY = "https://github.com/xpipe-io/xpipe/blob/master/SECURITY.md";
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
public static final String SECURITY = "https://docs.xpipe.io/security";
public static final String DISCORD = "https://discord.gg/8y89vS8cRb";
public static final String SLACK =
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
public static final String DOCS_PRIVACY = "https://github.com/xpipe-io/xpipe/blob/master/PRIVACY.md";
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"};
@@ -1,10 +1,10 @@
package io.xpipe.app.util;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
@@ -14,10 +14,6 @@ import org.kordamp.ikonli.javafx.FontIcon;
public class JfxHelper {
public static void fontSize(Node node, double relativeSize) {
node.setStyle(node.getStyle() + "-fx-font-size: " + (relativeSize) + "em;");
}
public static Region createNamedEntry(String nameString, String descString) {
var header = new Label(nameString);
AppFont.header(header);
@@ -61,18 +57,18 @@ public class JfxHelper {
AppFont.header(header);
var desc = new Label(descString);
AppFont.small(desc);
var text = new VBox(header, desc);
text.setSpacing(2);
var text = new VBox(header, new Spacer(), desc);
if (image == null) {
return text;
}
var size = AppFont.getPixelSize(1) + AppFont.getPixelSize(-2) + 8;
var size = 40;
var graphic = PrettyImageHelper.ofFixedSquare(image, (int) size).createRegion();
var hbox = new HBox(graphic, text);
hbox.setAlignment(Pos.CENTER_LEFT);
hbox.setFillHeight(true);
hbox.setSpacing(10);
// graphic.fitWidthProperty().bind(Bindings.createDoubleBinding(() -> header.getHeight() +
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.JacksonMapper;
import org.apache.commons.io.FileUtils;
@@ -17,17 +18,27 @@ import java.nio.file.Path;
public class JsonConfigHelper {
public static JsonNode readConfig(Path in) {
JsonNode node = JsonNodeFactory.instance.objectNode();
public static JsonNode readRaw(Path in) {
try {
if (Files.exists(in)) {
ObjectMapper o = JacksonMapper.getDefault();
node = o.readTree(Files.readAllBytes(in));
var read = o.readTree(Files.readAllBytes(in));
return read;
}
} catch (IOException e) {
ErrorEvent.fromThrowable(e).build().handle();
}
return node;
return JsonNodeFactory.instance.missingNode();
}
public static ObjectNode readConfigObject(Path in) {
var read = readRaw(in);
// Check the results of loading fails
if (read.isObject()) {
return (ObjectNode) read;
}
return JsonNodeFactory.instance.objectNode();
}
public static void writeConfig(Path out, JsonNode node) {
@@ -48,9 +48,13 @@ public class ScanAlert {
var providers = ScanProvider.getAll();
var applicable = new ArrayList<ScanProvider.ScanOperation>();
for (ScanProvider scanProvider : providers) {
ScanProvider.ScanOperation operation = scanProvider.create(entry, sc);
if (operation != null) {
applicable.add(operation);
try {
ScanProvider.ScanOperation operation = scanProvider.create(entry, sc);
if (operation != null) {
applicable.add(operation);
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
return applicable;
@@ -62,125 +66,97 @@ public class ScanAlert {
}
private static void show(
DataStoreEntry initialStore, Function<DataStoreEntry, List<ScanProvider.ScanOperation>> applicable) {
DataStoreEntry initialStore, Function<DataStoreEntry, List<ScanProvider.ScanOperation>> applicable
) {
var entry = new SimpleObjectProperty<DataStoreEntryRef<ShellStore>>();
var selected = new SimpleListProperty<ScanProvider.ScanOperation>(FXCollections.observableArrayList());
var loading = new SimpleBooleanProperty();
Platform.runLater(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("scanAlertTitle"),
window -> {
return new MultiStepComp() {
var stage = AppWindowHelper.sideWindow(AppI18n.get("scanAlertTitle"), window -> {
return new MultiStepComp() {
private final StackPane stackPane = new StackPane();
private final StackPane stackPane = new StackPane();
{
stackPane.getStyleClass().add("scan-list");
}
{
stackPane.getStyleClass().add("scan-list");
}
@Override
protected List<Entry> setup() {
return List.of(new Entry(AppI18n.observable("a"), new Step<>() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(AppI18n.observable("a"), new Step<>() {
@Override
public CompStructure<?> createBase() {
var b = new OptionsBuilder()
.name("scanAlertChoiceHeader")
.description("scanAlertChoiceHeaderDescription")
.addComp(new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER,
null,
entry,
ShellStore.class,
store1 -> true,
StoreViewState.get().getAllConnectionsCategory()
)
.disable(new SimpleBooleanProperty(initialStore != null)))
.name("scanAlertHeader")
.description("scanAlertHeaderDescription")
.addComp(Comp.of(() -> stackPane).vgrow())
.buildComp()
.prefWidth(500)
.prefHeight(600)
.styleClass("window-content")
.apply(struc -> {
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
})
.createStructure()
.get();
public CompStructure<?> createBase() {
var b = new OptionsBuilder().name("scanAlertChoiceHeader").description("scanAlertChoiceHeaderDescription").addComp(
new DataStoreChoiceComp<>(DataStoreChoiceComp.Mode.OTHER, null, entry, ShellStore.class, store1 -> true,
StoreViewState.get().getAllConnectionsCategory()).disable(
new SimpleBooleanProperty(initialStore != null))).name("scanAlertHeader").description(
"scanAlertHeaderDescription").addComp(Comp.of(() -> stackPane).vgrow()).buildComp().prefWidth(500).prefHeight(
600).styleClass("window-content").apply(struc -> {
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
}).createStructure().get();
entry.addListener((observable, oldValue, newValue) -> {
selected.clear();
stackPane.getChildren().clear();
entry.addListener((observable, oldValue, newValue) -> {
selected.clear();
stackPane.getChildren().clear();
if (newValue == null) {
return;
}
ThreadHelper.runAsync(() -> {
BooleanScope.execute(loading, () -> {
var a = applicable.apply(entry.get().get());
Platform.runLater(() -> {
if (a == null) {
window.close();
return;
}
selected.setAll(a.stream()
.filter(
scanOperation ->
scanOperation.isDefaultSelected())
.toList());
var r = new ListSelectorComp<>(
a,
scanOperation ->
AppI18n.get(scanOperation.getNameKey()),
selected,
a.size() > 3
)
.createRegion();
stackPane.getChildren().add(r);
});
});
});
});
entry.set(initialStore != null ? initialStore.ref() : null);
return new SimpleCompStructure<>(b);
}
}));
}
@Override
protected void finish() {
ThreadHelper.runAsync(() -> {
if (entry.get() == null) {
if (newValue == null) {
return;
}
ThreadHelper.runAsync(() -> {
BooleanScope.execute(loading, () -> {
var a = applicable.apply(entry.get().get());
Platform.runLater(() -> {
window.close();
});
Platform.runLater(() -> {
if (a == null) {
window.close();
return;
}
BooleanScope.execute(loading, () -> {
entry.get().get().setExpanded(true);
for (var a : selected) {
try {
a.getScanner().run();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
selected.setAll(a.stream().filter(scanOperation -> scanOperation.isDefaultSelected()).toList());
var r = new ListSelectorComp<>(a, scanOperation -> AppI18n.get(scanOperation.getNameKey()), selected,
a.size() > 3).createRegion();
stackPane.getChildren().add(r);
});
});
});
});
entry.set(initialStore != null ? initialStore.ref() : null);
return new SimpleCompStructure<>(b);
}
};
},
false,
loading);
}));
}
@Override
protected void finish() {
ThreadHelper.runAsync(() -> {
if (entry.get() == null) {
return;
}
Platform.runLater(() -> {
window.close();
});
BooleanScope.execute(loading, () -> {
entry.get().get().setExpanded(true);
var copy = new ArrayList<>(selected);
for (var a : copy) {
try {
a.getScanner().run();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
});
});
}
};
}, false, loading);
stage.show();
});
}
@@ -34,7 +34,7 @@ public class SecretRetrievalStrategyHelper {
var content = new HorizontalComp(List.of(
new TextFieldComp(keyProperty).apply(struc -> struc.get().setPromptText("Password key")).hgrow(),
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
AppPrefs.get().selectCategory(4);
AppPrefs.get().selectCategory(5);
App.getApp().getStage().requestFocus();
})
.grow(false, true)))
@@ -63,38 +63,41 @@ public class SecretRetrievalStrategyHelper {
p);
}
public static OptionsBuilder comp(Property<SecretRetrievalStrategy> s) {
public static OptionsBuilder comp(Property<SecretRetrievalStrategy> s, boolean allowNone) {
SecretRetrievalStrategy strat = s.getValue();
var inPlace = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.InPlace i ? i : null);
var passwordManager =
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.PasswordManager i ? i : null);
var customCommand =
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand i ? i : null);
var command = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand c ? c : null);
var map = new LinkedHashMap<String, OptionsBuilder>();
map.put("none", new OptionsBuilder());
if (allowNone) {
map.put("none", new OptionsBuilder());
}
map.put("password", inPlace(inPlace));
map.put("passwordManager", passwordManager(passwordManager));
map.put("customCommand", customCommand(customCommand));
map.put("prompt", new OptionsBuilder());
int offset = allowNone ? 0 : -1;
var selected = new SimpleIntegerProperty(
strat instanceof SecretRetrievalStrategy.None
? 0
? offset
: strat instanceof SecretRetrievalStrategy.InPlace
? 1
? offset + 1
: strat instanceof SecretRetrievalStrategy.PasswordManager
? 2
? offset + 2
: strat instanceof SecretRetrievalStrategy.CustomCommand
? 3
? offset + 3
: strat instanceof SecretRetrievalStrategy.Prompt
? 4
? offset + 4
: strat == null ? -1 : 0);
return new OptionsBuilder()
.choice(selected, map)
.bindChoice(
() -> {
return switch (selected.get()) {
case 0 -> new SimpleObjectProperty<>(new SecretRetrievalStrategy.None());
return switch (selected.get() - offset) {
case 0 -> new SimpleObjectProperty<>(allowNone ? new SecretRetrievalStrategy.None() : null);
case 1 -> inPlace;
case 2 -> passwordManager;
case 3 -> customCommand;
@@ -3,6 +3,8 @@ package io.xpipe.app.util;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.WinReg;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl;
import java.util.Optional;
@@ -30,4 +32,30 @@ public class WindowsRegistry {
return Optional.empty();
}
}
public static Optional<String> readRemoteString(ShellControl shellControl, int hkey, String key, String valueName) throws Exception {
var command = CommandBuilder.of().add("reg", "query").addQuoted((hkey == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER") + "\\" + key).add("/v").addQuoted(valueName);
String output;
try (var c = shellControl.command(command).start()) {
output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return Optional.empty();
}
}
// Output has the following format:
// \n<Version information>\n\n<key>\t<registry type>\t<value>
if (output.contains("\t")) {
String[] parsed = output.split("\t");
return Optional.of(parsed[parsed.length - 1]);
}
if (output.contains(" ")) {
String[] parsed = output.split(" ");
return Optional.of(parsed[parsed.length - 1]);
}
return Optional.empty();
}
}