mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-22 15:40:31 -04:00
Merge branch 'batch' into 16-release
This commit is contained in:
@@ -101,7 +101,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
||||
.grow(true, false)
|
||||
.accessibleTextKey("restoreAllSessions");
|
||||
|
||||
var layout = new VerticalComp(List.of(vbox, Comp.vspacer(5), listBox, Comp.separator(), tile));
|
||||
var layout = new VerticalComp(List.of(vbox, Comp.vspacer(5), listBox, Comp.hseparator(), tile));
|
||||
layout.styleClass("welcome");
|
||||
layout.spacing(14);
|
||||
layout.maxWidth(1000);
|
||||
|
||||
@@ -58,10 +58,14 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||
};
|
||||
}
|
||||
|
||||
public static Comp<CompStructure<Separator>> separator() {
|
||||
public static Comp<CompStructure<Separator>> hseparator() {
|
||||
return of(() -> new Separator(Orientation.HORIZONTAL));
|
||||
}
|
||||
|
||||
public static Comp<CompStructure<Separator>> vseparator() {
|
||||
return of(() -> new Separator(Orientation.VERTICAL));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <IR extends Region, SIN extends CompStructure<IR>, OR extends Region> Comp<CompStructure<OR>> derive(
|
||||
Comp<SIN> comp, Function<IR, OR> r) {
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S
|
||||
if (!hide.get()) {
|
||||
var cm = contextMenu.get();
|
||||
if (cm != null) {
|
||||
cm.show(r, Side.BOTTOM, 0, 0);
|
||||
cm.show(r, Side.TOP, 0, 0);
|
||||
currentContextMenu.set(cm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,26 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HorizontalComp extends Comp<CompStructure<HBox>> {
|
||||
|
||||
private final List<Comp<?>> entries;
|
||||
private final ObservableList<Comp<?>> entries;
|
||||
|
||||
public HorizontalComp(List<Comp<?>> comps) {
|
||||
entries = List.copyOf(comps);
|
||||
entries = FXCollections.observableArrayList(List.copyOf(comps));
|
||||
}
|
||||
|
||||
public HorizontalComp(ObservableList<Comp<?>> entries) {
|
||||
this.entries = PlatformThread.sync(entries);
|
||||
}
|
||||
|
||||
public Comp<CompStructure<HBox>> spacing(double spacing) {
|
||||
@@ -23,8 +32,11 @@ public class HorizontalComp extends Comp<CompStructure<HBox>> {
|
||||
|
||||
@Override
|
||||
public CompStructure<HBox> createBase() {
|
||||
HBox b = new HBox();
|
||||
var b = new HBox();
|
||||
b.getStyleClass().add("horizontal-comp");
|
||||
entries.addListener((ListChangeListener<? super Comp<?>>) c -> {
|
||||
b.getChildren().setAll(c.getList().stream().map(Comp::createRegion).toList());
|
||||
});
|
||||
for (var entry : entries) {
|
||||
b.getChildren().add(entry.createRegion());
|
||||
}
|
||||
|
||||
40
app/src/main/java/io/xpipe/app/comp/base/ToolbarComp.java
Normal file
40
app/src/main/java/io/xpipe/app/comp/base/ToolbarComp.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ToolbarComp extends Comp<CompStructure<ToolBar>> {
|
||||
|
||||
private final ObservableList<Comp<?>> entries;
|
||||
|
||||
public ToolbarComp(List<Comp<?>> comps) {
|
||||
entries = FXCollections.observableArrayList(List.copyOf(comps));
|
||||
}
|
||||
|
||||
public ToolbarComp(ObservableList<Comp<?>> entries) {
|
||||
this.entries = PlatformThread.sync(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<ToolBar> createBase() {
|
||||
var b = new ToolBar();
|
||||
b.getStyleClass().add("horizontal-comp");
|
||||
entries.addListener((ListChangeListener<? super Comp<?>>) c -> {
|
||||
b.getItems().setAll(c.getList().stream().map(Comp::createRegion).toList());
|
||||
});
|
||||
for (var entry : entries) {
|
||||
b.getItems().add(entry.createRegion());
|
||||
}
|
||||
return new SimpleCompStructure<>(b);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
@@ -71,9 +72,20 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
||||
var userIcon = createUserIcon().createRegion();
|
||||
|
||||
var selection = createBatchSelection().createRegion();
|
||||
grid.add(selection, 0, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(25));
|
||||
StoreViewState.get().getBatchMode().subscribe(batch -> {
|
||||
if (batch) {
|
||||
grid.getColumnConstraints().set(0, new ColumnConstraints(25));
|
||||
} else {
|
||||
grid.getColumnConstraints().set(0, new ColumnConstraints(-8));
|
||||
}
|
||||
});
|
||||
|
||||
var storeIcon = createIcon(28, 24);
|
||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
grid.add(storeIcon, 0, 0);
|
||||
grid.add(storeIcon, 1, 0);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(34));
|
||||
|
||||
var customSize = content != null ? 100 : 0;
|
||||
|
||||
@@ -38,15 +38,26 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
grid.setHgap(6);
|
||||
grid.setVgap(OsType.getLocal() == OsType.MACOS ? 2 : 0);
|
||||
|
||||
var selection = createBatchSelection();
|
||||
grid.add(selection.createRegion(), 0, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(25));
|
||||
StoreViewState.get().getBatchMode().subscribe(batch -> {
|
||||
if (batch) {
|
||||
grid.getColumnConstraints().set(0, new ColumnConstraints(25));
|
||||
} else {
|
||||
grid.getColumnConstraints().set(0, new ColumnConstraints(-6));
|
||||
}
|
||||
});
|
||||
|
||||
var storeIcon = createIcon(46, 40);
|
||||
grid.add(storeIcon, 0, 0, 1, 2);
|
||||
grid.add(storeIcon, 1, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(52));
|
||||
|
||||
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||
var nameBox = new HBox(name, userIcon, notes);
|
||||
nameBox.setSpacing(6);
|
||||
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||
grid.add(nameBox, 1, 0);
|
||||
grid.add(nameBox, 2, 0);
|
||||
GridPane.setVgrow(nameBox, Priority.ALWAYS);
|
||||
getWrapper().getSessionActive().subscribe(aBoolean -> {
|
||||
if (!aBoolean) {
|
||||
@@ -59,7 +70,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
var summaryBox = new HBox(createSummary());
|
||||
summaryBox.setAlignment(Pos.TOP_LEFT);
|
||||
GridPane.setVgrow(summaryBox, Priority.ALWAYS);
|
||||
grid.add(summaryBox, 1, 1);
|
||||
grid.add(summaryBox, 2, 1);
|
||||
|
||||
var nameCC = new ColumnConstraints();
|
||||
nameCC.setMinWidth(100);
|
||||
@@ -67,7 +78,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
nameCC.setPrefWidth(100);
|
||||
grid.getColumnConstraints().addAll(nameCC);
|
||||
|
||||
grid.add(createInformation(), 2, 0, 1, 2);
|
||||
grid.add(createInformation(), 3, 0, 1, 2);
|
||||
var info = new ColumnConstraints();
|
||||
info.prefWidthProperty().bind(content != null ? INFO_WITH_CONTENT_WIDTH : INFO_NO_CONTENT_WIDTH);
|
||||
info.setHalignment(HPos.LEFT);
|
||||
@@ -84,7 +95,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
controls.setAlignment(Pos.CENTER_RIGHT);
|
||||
controls.setSpacing(10);
|
||||
controls.setPadding(new Insets(0, 0, 0, 10));
|
||||
grid.add(controls, 3, 0, 1, 2);
|
||||
grid.add(controls, 4, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(custom);
|
||||
|
||||
grid.getStyleClass().add("store-entry-grid");
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
public class StoreEntryBatchSelectComp extends SimpleComp {
|
||||
|
||||
private final StoreSection section;
|
||||
|
||||
public StoreEntryBatchSelectComp(StoreSection section) {this.section = section;}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var cb = new CheckBox();
|
||||
cb.setAllowIndeterminate(true);
|
||||
cb.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
StoreViewState.get().selectBatchMode(section);
|
||||
} else {
|
||||
StoreViewState.get().unselectBatchMode(section);
|
||||
}
|
||||
});
|
||||
|
||||
StoreViewState.get().getBatchModeSelection().getList().addListener((ListChangeListener<? super StoreEntryWrapper>) c -> {
|
||||
Platform.runLater(() -> {
|
||||
update(cb);
|
||||
});
|
||||
});
|
||||
section.getShownChildren().getList().addListener((ListChangeListener<? super StoreSection>) c -> {
|
||||
if (cb.isSelected()) {
|
||||
StoreViewState.get().selectBatchMode(section);
|
||||
} else {
|
||||
StoreViewState.get().unselectBatchMode(section);
|
||||
}
|
||||
});
|
||||
|
||||
cb.getStyleClass().add("batch-mode-selector");
|
||||
cb.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY) {
|
||||
cb.setSelected(!cb.isSelected());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
return cb;
|
||||
}
|
||||
|
||||
private void update(CheckBox checkBox) {
|
||||
var isSelected = StoreViewState.get().isSectionSelected(section);
|
||||
checkBox.setSelected(isSelected);
|
||||
if (section.getShownChildren().getList().size() == 0) {
|
||||
checkBox.setIndeterminate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var count = section.getShownChildren().getList().stream().filter(c -> StoreViewState.get().getBatchModeSelection().getList().contains(c.getWrapper())).count();
|
||||
checkBox.setIndeterminate(count > 0 && count != section.getShownChildren().getList().size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
var r = createContent();
|
||||
var buttonBar = r.lookup(".button-bar");
|
||||
var iconChooser = r.lookup(".icon");
|
||||
var batchMode = r.lookup(".batch-mode-selector");
|
||||
|
||||
var button = new Button();
|
||||
button.setGraphic(r);
|
||||
@@ -103,6 +104,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
});
|
||||
button.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
var notOnButton = NodeHelper.isParent(iconChooser, event.getTarget())
|
||||
|| NodeHelper.isParent(batchMode, event.getTarget())
|
||||
|| NodeHelper.isParent(buttonBar, event.getTarget());
|
||||
if (AppPrefs.get().requireDoubleClickForConnections().get() && !notOnButton) {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() != 2) {
|
||||
@@ -116,6 +118,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
});
|
||||
button.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
var notOnButton = NodeHelper.isParent(iconChooser, event.getTarget())
|
||||
|| NodeHelper.isParent(batchMode, event.getTarget())
|
||||
|| NodeHelper.isParent(buttonBar, event.getTarget());
|
||||
if (AppPrefs.get().requireDoubleClickForConnections().get() && !notOnButton) {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() != 2) {
|
||||
@@ -274,6 +277,12 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
return settingsButton;
|
||||
}
|
||||
|
||||
protected Comp<?> createBatchSelection() {
|
||||
var c = new StoreEntryBatchSelectComp(section);
|
||||
c.hide(StoreViewState.get().getBatchMode().not());
|
||||
return c;
|
||||
}
|
||||
|
||||
protected ContextMenu createContextMenu() {
|
||||
var contextMenu = ContextMenuHelper.create();
|
||||
|
||||
|
||||
@@ -2,17 +2,22 @@ package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class StoreEntryListComp extends SimpleComp {
|
||||
|
||||
@@ -48,7 +53,15 @@ public class StoreEntryListComp extends SimpleComp {
|
||||
struc.get().setVvalue(0);
|
||||
});
|
||||
});
|
||||
return content.styleClass("store-list-comp");
|
||||
content.styleClass("store-list-comp");
|
||||
content.vgrow();
|
||||
|
||||
var statusBar = new StoreEntryListStatusBarComp();
|
||||
statusBar.apply(struc -> {
|
||||
VBox.setMargin(struc.get(), new Insets(3, 6, 4, 2));
|
||||
});
|
||||
statusBar.hide(StoreViewState.get().getBatchMode().not());
|
||||
return new VerticalComp(List.of(content, statusBar));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.CountComp;
|
||||
@@ -91,24 +92,30 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
StoreViewState.get().getFilterString().setValue(newValue);
|
||||
});
|
||||
});
|
||||
var filter = new FilterComp(StoreViewState.get().getFilterString());
|
||||
var f = filter.createRegion();
|
||||
var button = createAddButton();
|
||||
var hbox = new HBox(button, f);
|
||||
f.minHeightProperty().bind(button.heightProperty());
|
||||
f.prefHeightProperty().bind(button.heightProperty());
|
||||
f.maxHeightProperty().bind(button.heightProperty());
|
||||
var filter = new FilterComp(StoreViewState.get().getFilterString()).createRegion();
|
||||
var add = createAddButton();
|
||||
var batchMode = createBatchModeButton().createRegion();
|
||||
var hbox = new HBox(add, filter, batchMode);
|
||||
filter.minHeightProperty().bind(add.heightProperty());
|
||||
filter.prefHeightProperty().bind(add.heightProperty());
|
||||
filter.maxHeightProperty().bind(add.heightProperty());
|
||||
batchMode.minHeightProperty().bind(add.heightProperty());
|
||||
batchMode.prefHeightProperty().bind(add.heightProperty());
|
||||
batchMode.maxHeightProperty().bind(add.heightProperty());
|
||||
batchMode.minWidthProperty().bind(add.heightProperty());
|
||||
batchMode.prefWidthProperty().bind(add.heightProperty());
|
||||
batchMode.maxWidthProperty().bind(add.heightProperty());
|
||||
hbox.setSpacing(8);
|
||||
hbox.setAlignment(Pos.CENTER);
|
||||
HBox.setHgrow(f, Priority.ALWAYS);
|
||||
HBox.setHgrow(filter, Priority.ALWAYS);
|
||||
|
||||
f.getStyleClass().add("filter-bar");
|
||||
filter.getStyleClass().add("filter-bar");
|
||||
return hbox;
|
||||
}
|
||||
|
||||
private Region createAddButton() {
|
||||
var menu = new MenuButton(null, new FontIcon("mdi2p-plus-thick"));
|
||||
menu.textProperty().bind(AppI18n.observable("addConnections"));
|
||||
menu.textProperty().bind(AppI18n.observable("new"));
|
||||
menu.setAlignment(Pos.CENTER);
|
||||
menu.setTextAlignment(TextAlignment.CENTER);
|
||||
StoreCreationMenu.addButtons(menu);
|
||||
@@ -124,6 +131,29 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
private Comp<?> createBatchModeButton() {
|
||||
var batchMode = StoreViewState.get().getBatchMode();
|
||||
var b = new IconButtonComp("mdi2l-layers", () -> {
|
||||
batchMode.setValue(!batchMode.getValue());
|
||||
});
|
||||
b.apply(struc -> {
|
||||
struc
|
||||
.get()
|
||||
.opacityProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
if (batchMode.getValue()) {
|
||||
return 1.0;
|
||||
}
|
||||
return 0.4;
|
||||
},
|
||||
batchMode));
|
||||
struc.get().getStyleClass().remove(Styles.FLAT);
|
||||
});
|
||||
return b;
|
||||
}
|
||||
|
||||
private Comp<?> createAlphabeticalSortButton() {
|
||||
var sortMode = StoreViewState.get().getSortMode();
|
||||
var icon = Bindings.createObjectBinding(
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StoreEntryListStatusBarComp extends SimpleComp {
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var checkbox = new StoreEntryBatchSelectComp(StoreViewState.get().getCurrentTopLevelSection());
|
||||
var l = new LabelComp(Bindings.createStringBinding(() -> {
|
||||
return AppI18n.get("connectionsSelected", StoreViewState.get().getEffectiveBatchModeSelection().getList().size());
|
||||
}, StoreViewState.get().getEffectiveBatchModeSelection().getList(), AppI18n.activeLanguage()));
|
||||
l.minWidth(Region.USE_PREF_SIZE);
|
||||
l.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER);
|
||||
});
|
||||
var actions = new ToolbarComp(createActions());
|
||||
var close = new IconButtonComp("mdi2c-close", () -> {
|
||||
StoreViewState.get().getBatchMode().setValue(false);
|
||||
});
|
||||
close.apply(struc -> {
|
||||
struc.get().getStyleClass().remove(Styles.FLAT);
|
||||
struc.get().minWidthProperty().bind(struc.get().heightProperty());
|
||||
struc.get().prefWidthProperty().bind(struc.get().heightProperty());
|
||||
struc.get().maxWidthProperty().bind(struc.get().heightProperty());
|
||||
});
|
||||
var bar = new HorizontalComp(List.of(checkbox, Comp.hspacer(12), l, Comp.hspacer(20), actions, Comp.hspacer(), Comp.hspacer(20), close));
|
||||
bar.apply(struc -> {
|
||||
struc.get().setFillHeight(true);
|
||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||
});
|
||||
bar.minHeight(40);
|
||||
bar.prefHeight(40);
|
||||
bar.styleClass("bar");
|
||||
bar.styleClass("store-entry-list-status-bar");
|
||||
return bar.createRegion();
|
||||
}
|
||||
|
||||
private ObservableList<Comp<?>> createActions() {
|
||||
var l = new DerivedObservableList<ActionProvider>(FXCollections.observableArrayList(), true);
|
||||
StoreViewState.get().getEffectiveBatchModeSelection().getList().addListener((ListChangeListener<? super StoreEntryWrapper>) c -> {
|
||||
l.setContent(getCompatibleActionProviders());
|
||||
});
|
||||
return l.<Comp<?>>mapped(actionProvider -> {
|
||||
return buildButton(actionProvider);
|
||||
}).getList();
|
||||
}
|
||||
|
||||
private List<ActionProvider> getCompatibleActionProviders() {
|
||||
var l = StoreViewState.get().getEffectiveBatchModeSelection().getList();
|
||||
if (l.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var all = new ArrayList<>(ActionProvider.ALL);
|
||||
for (StoreEntryWrapper w : l) {
|
||||
var actions = ActionProvider.ALL.stream().filter(actionProvider -> {
|
||||
var s = actionProvider.getBatchDataStoreCallSite();
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!s.getApplicableClass().isAssignableFrom(w.getStore().getValue().getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!s.isApplicable(w.getEntry().ref())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
all.removeIf(actionProvider -> !actions.contains(actionProvider));
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends DataStore> Comp<?> buildButton(ActionProvider p) {
|
||||
ActionProvider.BatchDataStoreCallSite<T> s = (ActionProvider.BatchDataStoreCallSite<T>) p.getBatchDataStoreCallSite();
|
||||
if (s == null) {
|
||||
return Comp.empty();
|
||||
}
|
||||
|
||||
List<DataStoreEntryRef<T>> childrenRefs = StoreViewState.get().getEffectiveBatchModeSelection().getList().stream().map(
|
||||
storeEntryWrapper -> storeEntryWrapper.getEntry().<T>ref()).toList();
|
||||
var batchActions = s.getChildren(childrenRefs);
|
||||
var button = new ButtonComp(s.getName(), new SimpleObjectProperty<>(new LabelGraphic.IconGraphic(s.getIcon())), () -> {
|
||||
if (batchActions.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
runActions(s);
|
||||
});
|
||||
if (batchActions.size() > 0) {
|
||||
button.apply(new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
|
||||
var cm = ContextMenuHelper.create();
|
||||
s.getChildren(childrenRefs)
|
||||
.forEach(childProvider -> {
|
||||
var menu = buildMenuItemForAction(childrenRefs, childProvider);
|
||||
cm.getItems().add(menu);
|
||||
});
|
||||
return cm;
|
||||
}));
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends DataStore> MenuItem buildMenuItemForAction(List<DataStoreEntryRef<T>> batch, ActionProvider a) {
|
||||
ActionProvider.BatchDataStoreCallSite<T> s = (ActionProvider.BatchDataStoreCallSite<T>) a.getBatchDataStoreCallSite();
|
||||
var name = s.getName();
|
||||
var icon = s.getIcon();
|
||||
var children = s.getChildren(batch);
|
||||
if (children.size() > 0) {
|
||||
var menu = new Menu();
|
||||
menu.textProperty().bind(name);
|
||||
menu.setGraphic(new LabelGraphic.IconGraphic(icon).createGraphicNode());
|
||||
var items = children.stream()
|
||||
.filter(actionProvider -> actionProvider.getBatchDataStoreCallSite() != null)
|
||||
.map(c -> buildMenuItemForAction(batch, c))
|
||||
.toList();
|
||||
menu.getItems().addAll(items);
|
||||
return menu;
|
||||
} else {
|
||||
var item = new MenuItem();
|
||||
item.textProperty().bind(name);
|
||||
item.setGraphic(new LabelGraphic.IconGraphic(icon).createGraphicNode());
|
||||
item.setOnAction(event -> {
|
||||
runActions(s);
|
||||
event.consume();
|
||||
if (event.getTarget() instanceof Menu m) {
|
||||
m.getParentPopup().hide();
|
||||
}
|
||||
});
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends DataStore> void runActions(ActionProvider.BatchDataStoreCallSite<?> s) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var l = new ArrayList<>( StoreViewState.get().getEffectiveBatchModeSelection().getList());
|
||||
var mapped = l.stream().map(w -> w.getEntry().<T>ref()).toList();
|
||||
var action = ((ActionProvider.BatchDataStoreCallSite<T>) s).createAction(mapped);
|
||||
if (action != null) {
|
||||
action.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class StoreLayoutComp extends SimpleComp {
|
||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||
})
|
||||
.createStructure();
|
||||
struc.getLeft().setMinWidth(260);
|
||||
struc.getLeft().setMinWidth(270);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
struc.get().getStyleClass().add("store-layout");
|
||||
InputHelper.onKeyCombination(
|
||||
|
||||
@@ -156,7 +156,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||
section.getShownChildren().getList());
|
||||
var full = new VerticalComp(List.of(
|
||||
topEntryList,
|
||||
Comp.separator().hide(expanded.not()),
|
||||
Comp.hseparator().hide(expanded.not()),
|
||||
content.styleClass("children-content")
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(section.getWrapper().getExpanded()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.ext.DataStoreUsageCategory;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
@@ -14,6 +15,8 @@ import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
@@ -42,6 +45,23 @@ public class StoreViewState {
|
||||
@Getter
|
||||
private final Property<StoreSortMode> sortMode = new SimpleObjectProperty<>();
|
||||
|
||||
@Getter
|
||||
private final BooleanProperty batchMode = new SimpleBooleanProperty(true);
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> batchModeSelection = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> effectiveBatchModeSelection = batchModeSelection.filtered(storeEntryWrapper -> {
|
||||
if (!storeEntryWrapper.getValidity().getValue().isUsable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (storeEntryWrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@Getter
|
||||
private StoreSection currentTopLevelSection;
|
||||
|
||||
@@ -60,6 +80,7 @@ public class StoreViewState {
|
||||
INSTANCE.initSections();
|
||||
INSTANCE.updateContent();
|
||||
INSTANCE.initFilterListener();
|
||||
INSTANCE.initBatchListener();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
@@ -80,6 +101,36 @@ public class StoreViewState {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void selectBatchMode(StoreSection section) {
|
||||
var wrapper = section.getWrapper();
|
||||
if (wrapper != null && !batchModeSelection.getList().contains(wrapper)) {
|
||||
batchModeSelection.getList().add(wrapper);
|
||||
}
|
||||
if (wrapper == null || (wrapper.getValidity().getValue().isUsable() && wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP)) {
|
||||
section.getShownChildren().getList().forEach(c -> selectBatchMode(c));
|
||||
}
|
||||
}
|
||||
|
||||
public void unselectBatchMode(StoreSection section) {
|
||||
var wrapper = section.getWrapper();
|
||||
if (wrapper != null) {
|
||||
batchModeSelection.getList().remove(wrapper);
|
||||
}
|
||||
if (wrapper == null || (wrapper.getValidity().getValue().isUsable() && wrapper.getEntry().getProvider().getUsageCategory() == DataStoreUsageCategory.GROUP)) {
|
||||
section.getShownChildren().getList().forEach(c -> unselectBatchMode(c));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSectionSelected(StoreSection section) {
|
||||
if (section.getWrapper() == null) {
|
||||
var batchSet = new HashSet<>(batchModeSelection.getList());
|
||||
var childSet = section.getShownChildren().getList().stream().map(s -> s.getWrapper()).toList();
|
||||
return batchSet.containsAll(childSet);
|
||||
}
|
||||
|
||||
return getBatchModeSelection().getList().contains(section.getWrapper());
|
||||
}
|
||||
|
||||
private void updateContent() {
|
||||
categories.getList().forEach(c -> c.update());
|
||||
allEntries.getList().forEach(e -> e.update());
|
||||
@@ -115,6 +166,14 @@ public class StoreViewState {
|
||||
});
|
||||
}
|
||||
|
||||
private void initBatchListener() {
|
||||
allEntries.getList().addListener((ListChangeListener<? super StoreEntryWrapper>) c -> {
|
||||
batchModeSelection.getList().removeIf(storeEntryWrapper -> {
|
||||
return allEntries.getList().contains(storeEntryWrapper);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void initContent() {
|
||||
allEntries
|
||||
.getList()
|
||||
|
||||
@@ -46,7 +46,7 @@ public class AppLayoutModel {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
var state = AppCache.getNonNull("layoutState", SavedState.class, () -> new SavedState(260, 300));
|
||||
var state = AppCache.getNonNull("layoutState", SavedState.class, () -> new SavedState(270, 300));
|
||||
INSTANCE = new AppLayoutModel(state);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ public interface ActionProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
default BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default DefaultDataStoreCallSite<?> getDefaultDataStoreCallSite() {
|
||||
return null;
|
||||
}
|
||||
@@ -191,6 +195,41 @@ public interface ActionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
interface BatchDataStoreCallSite<T extends DataStore> {
|
||||
|
||||
ObservableValue<String> getName();
|
||||
|
||||
String getIcon();
|
||||
|
||||
Class<?> getApplicableClass();
|
||||
|
||||
default boolean isApplicable(DataStoreEntryRef<T> o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
default Action createAction(List<DataStoreEntryRef<T>> stores) {
|
||||
var individual = stores.stream().map(ref -> {
|
||||
return createAction(ref);
|
||||
}).filter(action -> action != null).toList();
|
||||
return new Action() {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
for (Action action : individual) {
|
||||
action.execute();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
default Action createAction(DataStoreEntryRef<T> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default List<? extends ActionProvider> getChildren(List<DataStoreEntryRef<T>> batch) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
class Loader implements ModuleLayerLoader {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -80,7 +80,7 @@ public class AboutCategory extends AppPrefsCategory {
|
||||
protected Comp<?> create() {
|
||||
var props = createProperties().padding(new Insets(0, 0, 0, 5));
|
||||
var update = new UpdateCheckComp().grow(true, false);
|
||||
return new VerticalComp(List.of(props, Comp.separator(), update, Comp.separator(), createLinks()))
|
||||
return new VerticalComp(List.of(props, Comp.hseparator(), update, Comp.hseparator(), createLinks()))
|
||||
.apply(s -> s.get().setFillWidth(true))
|
||||
.apply(struc -> struc.get().setSpacing(15))
|
||||
.styleClass("information")
|
||||
|
||||
@@ -131,9 +131,9 @@ public class IconsCategory extends AppPrefsCategory {
|
||||
var vbox = new VerticalComp(List.of(
|
||||
Comp.vspacer(10),
|
||||
box,
|
||||
Comp.separator(),
|
||||
Comp.hseparator(),
|
||||
refreshButton,
|
||||
Comp.separator(),
|
||||
Comp.hseparator(),
|
||||
addDirectoryButton,
|
||||
addGitButton));
|
||||
vbox.spacing(10);
|
||||
|
||||
@@ -322,7 +322,7 @@ public class OptionsBuilder {
|
||||
}
|
||||
|
||||
public OptionsBuilder separator() {
|
||||
return addComp(Comp.separator());
|
||||
return addComp(Comp.hseparator());
|
||||
}
|
||||
|
||||
public OptionsBuilder name(String nameKey) {
|
||||
|
||||
@@ -2,76 +2,44 @@ package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.ModalButton;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import java.util.List;
|
||||
|
||||
public class ScanDialog {
|
||||
|
||||
public static void showAsync(DataStoreEntry entry) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var showForCon = entry == null
|
||||
|| (entry.getStore() instanceof ShellStore
|
||||
&& (!(entry.getStorePersistentState() instanceof SystemState systemState)
|
||||
|| systemState.getTtyState() == null
|
||||
|| systemState.getTtyState() == ShellTtyState.NONE));
|
||||
if (showForCon) {
|
||||
showForShellStore(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void showForShellStore(DataStoreEntry initial) {
|
||||
var action = new ScanDialogAction() {
|
||||
|
||||
@Override
|
||||
public boolean scan(
|
||||
ObservableList<ScanProvider.ScanOpportunity> all,
|
||||
ObservableList<ScanProvider.ScanOpportunity> selected,
|
||||
DataStoreEntry entry,
|
||||
ShellControl sc) {
|
||||
if (!sc.canHaveSubshells()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sc.getTtyState() != ShellTtyState.NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var providers = ScanProvider.getAll();
|
||||
for (ScanProvider scanProvider : providers) {
|
||||
try {
|
||||
// Previous scan operation could have exited the shell
|
||||
sc.start();
|
||||
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
|
||||
if (operation != null) {
|
||||
if (!operation.isDisabled() && operation.isDefaultSelected()) {
|
||||
selected.add(operation);
|
||||
}
|
||||
all.add(operation);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
show(initial, action);
|
||||
var showForCon = entry == null
|
||||
|| (entry.getStore() instanceof ShellStore
|
||||
&& (!(entry.getStorePersistentState() instanceof SystemState systemState)
|
||||
|| systemState.getTtyState() == null
|
||||
|| systemState.getTtyState() == ShellTtyState.NONE));
|
||||
if (showForCon) {
|
||||
show(entry, ScanDialogAction.shellScanAction());
|
||||
}
|
||||
}
|
||||
|
||||
private static void show(DataStoreEntry initialStore, ScanDialogAction action) {
|
||||
var comp = new ScanDialogComp(initialStore != null ? initialStore.ref() : null, action);
|
||||
var comp = new ScanSingleDialogComp(initialStore != null ? initialStore.ref() : null, action);
|
||||
var modal = ModalOverlay.of("scanAlertTitle", comp);
|
||||
var button = new ModalButton(
|
||||
"ok",
|
||||
() -> {
|
||||
comp.finish();
|
||||
},
|
||||
false,
|
||||
true);
|
||||
button.augment(r -> r.disableProperty().bind(PlatformThread.sync(comp.getBusy())));
|
||||
modal.addButton(button);
|
||||
modal.show();
|
||||
}
|
||||
|
||||
public static void showMulti(List<DataStoreEntryRef<ShellStore>> entries, ScanDialogAction action) {
|
||||
var comp = new ScanMultiDialogComp(entries, action);
|
||||
var modal = ModalOverlay.of("scanAlertTitle", comp);
|
||||
var button = new ModalButton(
|
||||
"ok",
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public interface ScanDialogAction {
|
||||
|
||||
static ScanDialogAction shellScanAction() {
|
||||
var action = new ScanDialogAction() {
|
||||
|
||||
@Override
|
||||
public boolean scan(
|
||||
ObservableList<ScanProvider.ScanOpportunity> all,
|
||||
ObservableList<ScanProvider.ScanOpportunity> selected,
|
||||
DataStoreEntry entry,
|
||||
ShellControl sc) {
|
||||
if (!sc.canHaveSubshells()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sc.getTtyState() != ShellTtyState.NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var providers = ScanProvider.getAll();
|
||||
for (ScanProvider scanProvider : providers) {
|
||||
try {
|
||||
// Previous scan operation could have exited the shell
|
||||
sc.start();
|
||||
ScanProvider.ScanOpportunity operation = scanProvider.create(entry, sc);
|
||||
if (operation != null) {
|
||||
if (!operation.isDisabled() && operation.isDefaultSelected()) {
|
||||
selected.add(operation);
|
||||
}
|
||||
all.add(operation);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
boolean scan(
|
||||
ObservableList<ScanProvider.ScanOpportunity> all,
|
||||
ObservableList<ScanProvider.ScanOpportunity> selected,
|
||||
|
||||
139
app/src/main/java/io/xpipe/app/util/ScanDialogBase.java
Normal file
139
app/src/main/java/io/xpipe/app/util/ScanDialogBase.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
public class ScanDialogBase {
|
||||
|
||||
private final boolean expand;
|
||||
private final Runnable closeAction;
|
||||
private final ScanDialogAction action;
|
||||
private final ObservableList<DataStoreEntryRef<ShellStore>> entries;
|
||||
private final ObservableList<ScanProvider.ScanOpportunity> available =
|
||||
FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
|
||||
private final ListProperty<ScanProvider.ScanOpportunity> selected =
|
||||
new SimpleListProperty<>(FXCollections.synchronizedObservableList(FXCollections.observableArrayList()));
|
||||
|
||||
@Getter
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
|
||||
public ScanDialogBase(boolean expand, Runnable closeAction, ScanDialogAction action, ObservableList<DataStoreEntryRef<ShellStore>> entries) {
|
||||
this.expand = expand;
|
||||
this.closeAction = closeAction;
|
||||
this.action = action;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public void finish() throws Exception {
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
for (var entry : entries) {
|
||||
if (expand) {
|
||||
entry.get().setExpanded(true);
|
||||
}
|
||||
var copy = new ArrayList<>(selected);
|
||||
for (var a : copy) {
|
||||
// If the user decided to remove the selected entry
|
||||
// while the scan is running, just return instantly
|
||||
if (!DataStorage.get().getStoreEntriesSet().contains(entry.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Previous scan operation could have exited the shell
|
||||
var sc = entry.getStore().getOrStartSession();
|
||||
|
||||
try {
|
||||
a.getProvider().scan(entry.get(), sc);
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onUpdate() {
|
||||
available.clear();
|
||||
selected.clear();
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
for (var entry : entries) {
|
||||
boolean r;
|
||||
try {
|
||||
var sc = entry.getStore().getOrStartSession();
|
||||
r = action.scan(available, selected, entry.get(), sc);
|
||||
} catch (Throwable t) {
|
||||
closeAction.run();
|
||||
throw t;
|
||||
}
|
||||
if (!r) {
|
||||
closeAction.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Comp<?> createContent() {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getStyleClass().add("scan-list");
|
||||
VBox.setVgrow(stackPane, ALWAYS);
|
||||
|
||||
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||
var n = AppI18n.get(s.getNameKey());
|
||||
if (s.getLicensedFeatureId() == null) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var suffix = LicenseProvider.get().getFeature(s.getLicensedFeatureId());
|
||||
return n + suffix.getDescriptionSuffix().map(d -> " (" + d + ")").orElse("");
|
||||
};
|
||||
var r = new ListSelectorComp<>(
|
||||
available,
|
||||
nameFunc,
|
||||
selected,
|
||||
scanOperation -> scanOperation.isDisabled(),
|
||||
() -> available.size() > 3)
|
||||
.createRegion();
|
||||
stackPane.getChildren().add(r);
|
||||
|
||||
onUpdate();
|
||||
entries.addListener((ListChangeListener<? super DataStoreEntryRef<ShellStore>>) c -> onUpdate());
|
||||
|
||||
var comp = LoadingOverlayComp.noProgress(Comp.of(() -> stackPane), busy)
|
||||
.vgrow();
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.comp.base.ModalOverlayContentComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
class ScanDialogComp extends ModalOverlayContentComp {
|
||||
|
||||
private final DataStoreEntryRef<ShellStore> initialStore;
|
||||
private final ScanDialogAction action;
|
||||
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
||||
private final ObservableList<ScanProvider.ScanOpportunity> available =
|
||||
FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
|
||||
private final ListProperty<ScanProvider.ScanOpportunity> selected =
|
||||
new SimpleListProperty<>(FXCollections.synchronizedObservableList(FXCollections.observableArrayList()));
|
||||
|
||||
@Getter
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
|
||||
ScanDialogComp(DataStoreEntryRef<ShellStore> entry, ScanDialogAction action) {
|
||||
this.initialStore = entry;
|
||||
this.entry = new SimpleObjectProperty<>(entry);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (entry.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var modal = getModalOverlay();
|
||||
if (modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
});
|
||||
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
entry.get().get().setExpanded(true);
|
||||
var copy = new ArrayList<>(selected);
|
||||
for (var a : copy) {
|
||||
// If the user decided to remove the selected entry
|
||||
// while the scan is running, just return instantly
|
||||
if (!DataStorage.get()
|
||||
.getStoreEntriesSet()
|
||||
.contains(entry.get().get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Previous scan operation could have exited the shell
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
|
||||
try {
|
||||
a.getProvider().scan(entry.get().get(), sc);
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void onUpdate(DataStoreEntryRef<ShellStore> newValue) {
|
||||
available.clear();
|
||||
selected.clear();
|
||||
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
boolean r;
|
||||
try {
|
||||
var sc = entry.get().getStore().getOrStartSession();
|
||||
r = action.scan(available, selected, newValue.get(), sc);
|
||||
} catch (Throwable t) {
|
||||
var modal = getModalOverlay();
|
||||
if (initialStore != null && modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
if (!r) {
|
||||
var modal = getModalOverlay();
|
||||
if (initialStore != null && modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getStyleClass().add("scan-list");
|
||||
|
||||
var b = new OptionsBuilder()
|
||||
.name("scanAlertChoiceHeader")
|
||||
.description("scanAlertChoiceHeaderDescription")
|
||||
.addComp(new StoreChoiceComp<>(
|
||||
StoreChoiceComp.Mode.OTHER,
|
||||
null,
|
||||
entry,
|
||||
ShellStore.class,
|
||||
store1 -> true,
|
||||
StoreViewState.get().getAllConnectionsCategory())
|
||||
.disable(busy.or(new SimpleBooleanProperty(initialStore != null))))
|
||||
.name("scanAlertHeader")
|
||||
.description("scanAlertHeaderDescription")
|
||||
.addComp(LoadingOverlayComp.noProgress(Comp.of(() -> stackPane), busy)
|
||||
.vgrow())
|
||||
.buildComp()
|
||||
.prefWidth(500)
|
||||
.prefHeight(680)
|
||||
.apply(struc -> {
|
||||
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
|
||||
});
|
||||
|
||||
Function<ScanProvider.ScanOpportunity, String> nameFunc = (ScanProvider.ScanOpportunity s) -> {
|
||||
var n = AppI18n.get(s.getNameKey());
|
||||
if (s.getLicensedFeatureId() == null) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var suffix = LicenseProvider.get().getFeature(s.getLicensedFeatureId());
|
||||
return n + suffix.getDescriptionSuffix().map(d -> " (" + d + ")").orElse("");
|
||||
};
|
||||
var r = new ListSelectorComp<>(
|
||||
available,
|
||||
nameFunc,
|
||||
selected,
|
||||
scanOperation -> scanOperation.isDisabled(),
|
||||
() -> available.size() > 3)
|
||||
.createRegion();
|
||||
stackPane.getChildren().add(r);
|
||||
|
||||
entry.subscribe(newValue -> {
|
||||
onUpdate(newValue);
|
||||
});
|
||||
|
||||
return b.createRegion();
|
||||
}
|
||||
}
|
||||
61
app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java
Normal file
61
app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.ModalOverlayContentComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
class ScanMultiDialogComp extends ModalOverlayContentComp {
|
||||
|
||||
private final ScanDialogBase base;
|
||||
|
||||
ScanMultiDialogComp(List<DataStoreEntryRef<ShellStore>> entries, ScanDialogAction action) {
|
||||
ObservableList<DataStoreEntryRef<ShellStore>> list = FXCollections.observableArrayList(entries);
|
||||
this.base = new ScanDialogBase(true, () -> {
|
||||
var modal = getModalOverlay();
|
||||
if (modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
}, action, list);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
base.finish();
|
||||
});
|
||||
}
|
||||
|
||||
BooleanProperty getBusy() {
|
||||
return base.getBusy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var list = base.createContent();
|
||||
var b = new OptionsBuilder()
|
||||
.name("scanAlertHeader")
|
||||
.description("scanAlertHeaderDescription")
|
||||
.addComp(list.vgrow())
|
||||
.buildComp()
|
||||
.prefWidth(500)
|
||||
.prefHeight(680)
|
||||
.apply(struc -> {
|
||||
VBox.setVgrow(struc.get().getChildren().getFirst(), ALWAYS);
|
||||
});;
|
||||
return b.createRegion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.ModalOverlayContentComp;
|
||||
import io.xpipe.app.comp.store.StoreChoiceComp;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
class ScanSingleDialogComp extends ModalOverlayContentComp {
|
||||
|
||||
private final DataStoreEntryRef<ShellStore> initialStore;
|
||||
private final ObjectProperty<DataStoreEntryRef<ShellStore>> entry;
|
||||
private final ScanDialogBase base;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ScanSingleDialogComp(DataStoreEntryRef<ShellStore> entry, ScanDialogAction action) {
|
||||
this.initialStore = entry;
|
||||
this.entry = new SimpleObjectProperty<>(entry);
|
||||
|
||||
ObservableList<DataStoreEntryRef<ShellStore>> list = FXCollections.observableArrayList();
|
||||
this.entry.subscribe(v -> {
|
||||
if (v != null) {
|
||||
list.setAll(v);
|
||||
} else {
|
||||
list.clear();
|
||||
}
|
||||
});
|
||||
this.base = new ScanDialogBase(true, () -> {
|
||||
var modal = getModalOverlay();
|
||||
if (initialStore != null && modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
}, action, list);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
base.finish();
|
||||
});
|
||||
}
|
||||
|
||||
BooleanProperty getBusy() {
|
||||
return base.getBusy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var list = base.createContent();
|
||||
var b = new OptionsBuilder()
|
||||
.name("scanAlertChoiceHeader")
|
||||
.description("scanAlertChoiceHeaderDescription")
|
||||
.addComp(new StoreChoiceComp<>(
|
||||
StoreChoiceComp.Mode.OTHER,
|
||||
null,
|
||||
entry,
|
||||
ShellStore.class,
|
||||
store1 -> true,
|
||||
StoreViewState.get().getAllConnectionsCategory())
|
||||
.disable(base.getBusy().or(new SimpleBooleanProperty(initialStore != null))))
|
||||
.name("scanAlertHeader")
|
||||
.description("scanAlertHeaderDescription")
|
||||
.addComp(list.vgrow())
|
||||
.buildComp()
|
||||
.prefWidth(500)
|
||||
.prefHeight(680)
|
||||
.apply(struc -> {
|
||||
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
|
||||
});
|
||||
return b.createRegion();
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@ open module io.xpipe.app {
|
||||
requires io.xpipe.modulefs;
|
||||
requires io.xpipe.core;
|
||||
requires static lombok;
|
||||
requires java.desktop;
|
||||
requires org.apache.commons.io;
|
||||
requires org.apache.commons.lang3;
|
||||
requires javafx.base;
|
||||
@@ -91,6 +90,7 @@ open module io.xpipe.app {
|
||||
requires jdk.jdwp.agent;
|
||||
requires java.net.http;
|
||||
requires org.bouncycastle.provider;
|
||||
requires org.jetbrains.annotations;
|
||||
|
||||
uses TerminalLauncher;
|
||||
uses io.xpipe.app.ext.ActionProvider;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
.store-entry-list-status-bar {
|
||||
-fx-padding: 0 8 0 8;
|
||||
-fx-border-radius: 0 0 4 4;
|
||||
-fx-background-radius: 0 0 4 4;
|
||||
}
|
||||
|
||||
.store-entry-list-status-bar .button {
|
||||
-fx-padding: 4 8;
|
||||
}
|
||||
|
||||
.store-list-comp.scroll-pane * {
|
||||
-fx-icon-color: -color-fg-default;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.ext.base.script.ScriptHierarchy;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
@@ -71,6 +72,40 @@ public class RunScriptActionMenu implements ActionProvider {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<ShellStore> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
var t = AppPrefs.get().terminalType().getValue();
|
||||
return AppI18n.observable(
|
||||
"executeInTerminal",
|
||||
t != null ? t.toTranslatedString().getValue() : "?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2d-desktop-mac";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
return List.of();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@@ -116,6 +151,37 @@ public class RunScriptActionMenu implements ActionProvider {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public Class<ShellStore> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("executeInBackground");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2f-flip-to-back";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
return List.of();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@@ -189,6 +255,44 @@ public class RunScriptActionMenu implements ActionProvider {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return new SimpleStringProperty(hierarchy.getBase().get().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2p-play-box-multiple-outline";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
if (hierarchy.isLeaf()) {
|
||||
return List.of(
|
||||
new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
|
||||
}
|
||||
|
||||
return hierarchy.getChildren().stream()
|
||||
.map(c -> new ScriptActionProvider(c))
|
||||
.toList();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoScriptsActionProvider implements ActionProvider {
|
||||
@@ -225,6 +329,36 @@ public class RunScriptActionMenu implements ActionProvider {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("noScriptsAvailable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2i-image-filter-none";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
return List.of();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoStateActionProvider implements ActionProvider {
|
||||
@@ -342,4 +476,49 @@ public class RunScriptActionMenu implements ActionProvider {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public Class<ShellStore> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("runScript");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2p-play-box-multiple-outline";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
var hierarchy = ScriptHierarchy.buildEnabledHierarchy(ref -> {
|
||||
if (!ref.getStore().isRunnableScript()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
var list = hierarchy.getChildren().stream()
|
||||
.<ActionProvider>map(c -> new ScriptActionProvider(c))
|
||||
.toList();
|
||||
if (list.isEmpty()) {
|
||||
return List.of(new NoScriptsActionProvider());
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.ScanDialog;
|
||||
import io.xpipe.app.util.ScanDialogAction;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
|
||||
@@ -13,6 +14,8 @@ import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ScanStoreAction implements ActionProvider {
|
||||
|
||||
@Override
|
||||
@@ -61,6 +64,34 @@ public class ScanStoreAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("addConnections");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2l-layers-plus";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(List<DataStoreEntryRef<ShellStore>> stores) {
|
||||
return () -> {
|
||||
ScanDialog.showMulti(stores, ScanDialogAction.shellScanAction());
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
@@ -69,7 +100,7 @@ public class ScanStoreAction implements ActionProvider {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (entry == null || entry.getStore() instanceof ShellStore) {
|
||||
ScanDialog.showForShellStore(entry);
|
||||
ScanDialog.showAsync(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,37 @@ public class ServiceRefreshAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<FixedServiceCreatorStore>() {
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<FixedServiceCreatorStore> o) {
|
||||
return o.getStore().allowManualServicesRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("refreshServices");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2w-web";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return FixedServiceCreatorStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<FixedServiceCreatorStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
|
||||
@@ -41,6 +41,32 @@ public class StorePauseAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<PauseableStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("pause");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2p-pause";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return PauseableStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<PauseableStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
|
||||
@@ -47,6 +47,37 @@ public class StoreRestartAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<DataStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("restart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2r-restart";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return DataStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<DataStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<DataStore> o) {
|
||||
return o.getStore() instanceof StartableStore && o.getStore() instanceof StoppableStore;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
|
||||
@@ -41,6 +41,32 @@ public class StoreStartAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<StartableStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("start");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2p-play";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return StartableStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<StartableStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
|
||||
@@ -41,6 +41,32 @@ public class StoreStopAction implements ActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<?> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<StoppableStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("stop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2s-stop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return StoppableStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<StoppableStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Action implements ActionProvider.Action {
|
||||
|
||||
|
||||
23
lang/strings/translations_en.properties
generated
23
lang/strings/translations_en.properties
generated
@@ -33,7 +33,7 @@ addCommand=Command ...
|
||||
addAutomatically=Search Automatically ...
|
||||
addOther=Add Other ...
|
||||
addConnection=Add Connection
|
||||
addConnections=New
|
||||
new=New
|
||||
selectType=Select Type
|
||||
selectTypeDescription=Select connection type
|
||||
#context: computer shell program
|
||||
@@ -672,7 +672,6 @@ openFileWith=Open with ...
|
||||
openWithDefaultApplication=Open with default application
|
||||
rename=Rename
|
||||
run=Run
|
||||
new=New
|
||||
openInTerminal=Open in terminal
|
||||
file=File
|
||||
directory=Directory
|
||||
@@ -858,9 +857,11 @@ sshConfig=SSH config files
|
||||
autostart=Automatically connect on XPipe startup
|
||||
acceptHostKey=Accept host key
|
||||
modifyHostKeyPermissions=Modify host key permissions
|
||||
attachContainer=Attach to container
|
||||
#force
|
||||
attachContainer=Attach
|
||||
openInVsCode=Open in VSCode
|
||||
containerLogs=Show container logs
|
||||
#force
|
||||
containerLogs=Show logs
|
||||
openSftpClient=Open in external SFTP client
|
||||
openTermius=Open in Termius
|
||||
showInternalInstances=Show internal instances
|
||||
@@ -945,10 +946,10 @@ k8sCmd.displayName=kubectl client
|
||||
k8sCmd.displayDescription=Access Kubernetes clusters via kubectl
|
||||
k8sClusters=Kubernetes clusters
|
||||
shells=Available shells
|
||||
startContainer=Start container
|
||||
stopContainer=Stop container
|
||||
inspectContainer=Inspect container
|
||||
inspectContext=Inspect context
|
||||
#force
|
||||
inspectContainer=Inspect
|
||||
#force
|
||||
inspectContext=Inspect
|
||||
k8sClusterNameDescription=The name of the context the cluster is in.
|
||||
#context: kubernetes
|
||||
pod=Pod
|
||||
@@ -960,7 +961,8 @@ k8sClusterNamespaceDescription=The custom namespace or the default one if empty
|
||||
k8sConfigLocation=Config file
|
||||
k8sConfigLocationDescription=The custom kubeconfig file or the default one if left empty
|
||||
#context: kubernetes
|
||||
inspectPod=Inspect pod
|
||||
#force
|
||||
inspectPod=Inspect
|
||||
showAllContainers=Show non-running containers
|
||||
showAllPods=Show non-running pods
|
||||
k8sPodHostDescription=The host on which the pod is located
|
||||
@@ -1348,3 +1350,6 @@ noScriptStateAvailable=Refresh to determine script compatibility ...
|
||||
documentationDescription=Check out the documentation
|
||||
disableApiHttpsTlsCheck=Disable API HTTPS request certificate verification
|
||||
disableApiHttpsTlsCheckDescription=If your organization is decrypting your HTTPS traffic in firewalls using SSL interception, any update checks or license checks will fail due to the certificates not matching up. You can fix this by enabling this option and disabling TLS certificate validation.
|
||||
connectionsSelected=$NUMBER$ connections selected
|
||||
#force
|
||||
addConnections=Add connections
|
||||
|
||||
Reference in New Issue
Block a user