mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-10-09 16:41:27 +00:00
Merge branch 1.7.3-fixes into master
This commit is contained in:
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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user