Improve automatic scan dialog

This commit is contained in:
crschnick 2023-08-14 17:17:34 +00:00
parent 63269bbbfb
commit ce6a56d234
8 changed files with 108 additions and 26 deletions

View file

@ -0,0 +1,38 @@
package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.util.ScanAlert;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class StoreScanBarComp extends SimpleComp {
@Override
protected Region createSimple() {
var newTunnelStore = new ButtonComp(AppI18n.observable("addAutomatically"), new FontIcon("mdi2e-eye-plus-outline"), () -> {
ScanAlert.showAsync(null);
})
.styleClass(Styles.FLAT)
.shortcut(new KeyCodeCombination(KeyCode.A, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addAutomatically"));
var box = new VerticalComp(List.of(newTunnelStore))
.apply(struc -> struc.get().setFillWidth(true));
box.apply(s -> AppFont.medium(s.get()));
var bar = box.createRegion();
bar.getStyleClass().add("bar");
bar.getStyleClass().add("store-creation-bar");
return bar;
}
}

View file

@ -15,10 +15,11 @@ public class StoreSidebarComp extends SimpleComp {
protected Region createSimple() {
var sideBar = new VerticalComp(List.of(
new StoreEntryListHeaderComp(),
new StoreScanBarComp(),
new StoreCreationBarComp(),
new StoreOrganizationComp(),
Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar")));
sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(3), Priority.ALWAYS));
sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(4), Priority.ALWAYS));
sideBar.styleClass("sidebar");
return sideBar.createRegion();
}

View file

@ -16,6 +16,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Predicate;
@ -91,7 +92,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
@SuppressWarnings("unchecked")
protected Region createSimple() {
var list = StoreEntryFlatMiniSectionComp.ALL;
var comboBox = new CustomComboBoxBuilder<>(
var comboBox = new CustomComboBoxBuilder<T>(
selected,
t -> list.stream()
.filter(e -> t.equals(e.getEntry().getStore()))
@ -100,6 +101,14 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
.createRegion(),
new Label(AppI18n.get("none")),
n -> true);
if (list.size() > 5) {
comboBox.addFilter((t, s) -> {
var entry = DataStorage.get().getStoreDisplayName(t).orElse("?");
return entry.toLowerCase(Locale.ROOT).contains(s.toLowerCase(Locale.ROOT));
});
}
comboBox.setAccessibleNames(t -> toName(t));
comboBox.setSelectedDisplay(t -> createGraphic(t));
comboBox.setUnknownNode(t -> createGraphic(t));

View file

@ -15,6 +15,7 @@ import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.Separator;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -223,6 +224,14 @@ public class CustomComboBoxBuilder<T> {
private class Cell extends ListCell<Node> {
public Cell() {
addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
if (!nodeMap.containsKey(getItem())) {
event.consume();
}
});
}
@Override
protected void updateItem(Node item, boolean empty) {
setGraphic(item);

View file

@ -6,16 +6,20 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.ScanProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.ShellStore;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
@ -25,32 +29,20 @@ import javafx.scene.layout.VBox;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.function.Function;
public class ScanAlert {
public static void showAsync(DataStoreEntry entry) {
ThreadHelper.runAsync(() -> {
if (entry.getStore() instanceof ShellStore) {
if (entry == null || entry.getStore() instanceof ShellStore) {
showForShellStore(entry);
} else {
showForOtherStore(entry);
}
});
}
private static void showForOtherStore(DataStoreEntry entry) {
show(entry, () -> {
var providers = ScanProvider.getAll();
return providers.stream()
.map(scanProvider -> scanProvider.create(entry.getStore()))
.filter(scanOperation -> scanOperation != null)
.toList();
});
}
private static void showForShellStore(DataStoreEntry entry) {
show(entry, () -> {
private static void showForShellStore(DataStoreEntry initial) {
show(initial != null ? initial.getStore().asNeeded() : null, (DataStoreEntry entry) -> {
try (var sc = ((ShellStore) entry.getStore()).control().start()) {
var providers = ScanProvider.getAll();
var applicable = new ArrayList<ScanProvider.ScanOperation>();
@ -68,8 +60,10 @@ public class ScanAlert {
});
}
private static void show(DataStoreEntry entry, Supplier<List<ScanProvider.ScanOperation>> applicable) {
private static void show(
ShellStore initialStore, Function<DataStoreEntry, List<ScanProvider.ScanOperation>> applicable) {
var busy = new SimpleBooleanProperty();
var store = new SimpleObjectProperty<ShellStore>();
var selected = new SimpleListProperty<ScanProvider.ScanOperation>(FXCollections.observableArrayList());
AppWindowHelper.showAlert(
alert -> {
@ -78,23 +72,40 @@ public class ScanAlert {
alert.getButtonTypes().add(ButtonType.OK);
var content = new LoadingOverlayComp(
new VerticalComp(List.<Comp<?>>of(
new LabelComp(AppI18n.get("scanAlertHeader"))
new LabelComp(AppI18n.get("scanAlertChoiceHeader"))
.apply(struc ->
struc.get().setWrapText(true)),
new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER,
null,
store,
ShellStore.class,
store1 -> true)
.disable(new SimpleBooleanProperty(initialStore != null)),
new LabelComp(AppI18n.get("scanAlertHeader"))
.apply(struc ->
struc.get().setWrapText(true))
.padding(new Insets(20, 0, 0, 0)),
Comp.of(() -> new Region())))
.apply(struc -> struc.get().setSpacing(15))
.styleClass("window-content"),
busy)
.createRegion();
content.setPrefWidth(500);
content.setPrefHeight(450);
content.setPrefHeight(550);
alert.getDialogPane().setContent(content);
// Custom behavior for ok button
var btOk = (Button) alert.getDialogPane().lookupButton(ButtonType.OK);
btOk.addEventFilter(ActionEvent.ACTION, event -> {
if (store.get() == null) {
event.consume();
return;
}
ThreadHelper.runAsync(() -> {
BusyProperty.execute(busy, () -> {
var entry = DataStorage.get().getStoreEntry(store.get());
entry.setExpanded(true);
for (var a : selected) {
@ -113,11 +124,21 @@ public class ScanAlert {
});
});
// Asynchronous loading of content
alert.setOnShown(event -> {
store.addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
selected.clear();
((VBox) ((StackPane) alert.getDialogPane().getContent())
.getChildren()
.get(0))
.getChildren()
.set(3, new Region());
return;
}
ThreadHelper.runAsync(() -> {
BusyProperty.execute(busy, () -> {
var a = applicable.get();
var entry = DataStorage.get().getStoreEntry(newValue);
var a = applicable.apply(entry);
Platform.runLater(() -> {
if (a == null) {
@ -139,11 +160,13 @@ public class ScanAlert {
.getChildren()
.get(0))
.getChildren()
.set(1, r);
.set(3, r);
});
});
});
});
store.set(initialStore);
},
busy,
null);

View file

@ -46,6 +46,7 @@ addTunnel=Add Tunnel ...
addHost=Add Remote Host ...
addShell=Add Environment ...
addCommand=Add Command ...
addAutomatically=Add Automatically ...
addOther=Add Other ...
addStreamTitle=Add Stream Store
addConnection=Add Connection

View file

@ -64,7 +64,7 @@ logLevel=Log level
appBehaviour=Application behaviour
logLevelDescription=The log level that should be used when writing log files.
developerMode=Developer mode
developerModeDescription=When enabled, you will have access to a variety of additional options that are useful for development.
developerModeDescription=When enabled, you will have access to a variety of additional options that are useful for development. Only active after a restart.
editor=Editor
custom=Custom
passwordManagerCommand=Password manager command

View file

@ -55,6 +55,7 @@ connectionNameDescription=Give this connection a custom name
openFileTitle=Open file
unknown=Unknown
scanAlertTitle=Add connections
scanAlertChoiceHeader=Choose from where to add connections:
scanAlertHeader=Select types of connections you want to automatically add for the host system:
namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active