Preview executed commands in connection creation window

This commit is contained in:
crschnick 2023-06-13 16:40:52 +00:00
parent 6d4e800333
commit 67654f3005
17 changed files with 168 additions and 70 deletions

View file

@ -8,8 +8,12 @@ import io.xpipe.app.core.AppResources;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.issue.ErrorEvent;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
@ -25,21 +29,26 @@ import java.util.function.UnaryOperator;
public class MarkdownComp extends Comp<CompStructure<StackPane>> {
private final String markdown;
private final UnaryOperator<String> transformation;
private final ObservableValue<String> markdown;
private final UnaryOperator<String> htmlTransformation;
public MarkdownComp(String markdown, UnaryOperator<String> transformation) {
public MarkdownComp(String markdown, UnaryOperator<String> htmlTransformation) {
this.markdown = new SimpleStringProperty(markdown);
this.htmlTransformation = htmlTransformation;
}
public MarkdownComp(ObservableValue<String> markdown, UnaryOperator<String> htmlTransformation) {
this.markdown = markdown;
this.transformation = transformation;
this.htmlTransformation = htmlTransformation;
}
private String getHtml() {
MutableDataSet options = new MutableDataSet();
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
Document document = parser.parse(markdown);
Document document = parser.parse(markdown.getValue());
var html = renderer.render(document);
var result = transformation.apply(html);
var result = htmlTransformation.apply(html);
return "<article class=\"markdown-body\">" + result + "</article>";
}
@ -51,15 +60,17 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
.orElseThrow();
wv.getEngine().setUserStyleSheetLocation(url.toString());
// Work around for https://bugs.openjdk.org/browse/JDK-8199014
try {
var file = Files.createTempFile(null, ".html");
Files.writeString(file, getHtml());
var contentUrl = file.toUri();
wv.getEngine().load(contentUrl.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> {
// Work around for https://bugs.openjdk.org/browse/JDK-8199014
try {
var file = Files.createTempFile(null, ".html");
Files.writeString(file, getHtml());
var contentUrl = file.toUri();
wv.getEngine().load(contentUrl.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
wv.getStyleClass().add("markdown-comp");
addLinkHandler(wv.getEngine());

View file

@ -1,11 +1,13 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.Spacer;
import com.jfoenix.controls.JFXTabPane;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyProperty;
@ -80,14 +82,6 @@ public abstract class MultiStepComp extends Comp<CompStructure<VBox>> {
});
}
protected void setStartingIndex(int start) {
if (this.entries != null) {
throw new IllegalStateException();
}
currentIndex = start;
}
public boolean isCompleted(Entry e) {
return entries.indexOf(e) < currentIndex;
}
@ -193,23 +187,15 @@ public abstract class MultiStepComp extends Comp<CompStructure<VBox>> {
MultiStepComp comp = this;
HBox buttons = new HBox();
buttons.setFillHeight(true);
buttons.getChildren().add(new Region());
buttons.getChildren().add(new Spacer());
buttons.getStyleClass().add("buttons");
buttons.setSpacing(5);
var helpButton = new ButtonComp(AppI18n.observable("help"), null, () -> {
getValue().help.run();
})
.styleClass("help")
.apply(struc -> struc.get()
.visibleProperty()
.bind(Bindings.createBooleanBinding(() -> getValue().help == null, currentStep)));
if (getValue().help != null) {
buttons.getChildren().add(helpButton.createRegion());
}
var spacer = new Region();
buttons.getChildren().add(spacer);
HBox.setHgrow(spacer, Priority.ALWAYS);
SimpleChangeListener.apply(currentStep, val -> {
buttons.getChildren().set(0, val.bottom() != null ? val.bottom().createRegion() : new Region());
});
buttons.setAlignment(Pos.CENTER_RIGHT);
var nextText = Bindings.createStringBinding(
@ -242,7 +228,7 @@ public abstract class MultiStepComp extends Comp<CompStructure<VBox>> {
var compContent = new JFXTabPane();
compContent.getStyleClass().add("content");
for (var entry : comp.getEntries()) {
for (var ignored : comp.getEntries()) {
compContent.getTabs().add(new Tab(null, null));
}
var entryR = comp.getValue().createRegion();
@ -282,10 +268,8 @@ public abstract class MultiStepComp extends Comp<CompStructure<VBox>> {
public abstract static class Step<S extends CompStructure<?>> extends Comp<S> {
private final Runnable help;
public Step(Runnable help) {
this.help = help;
public Comp<?> bottom() {
return null;
}
public void onInit() {}

View file

@ -0,0 +1,56 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.Popover;
import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button;
import javafx.scene.layout.Region;
public class PopupMenuButtonComp extends SimpleComp {
private final ObservableValue<String> name;
private final Comp<?> content;
private final boolean lazy;
public PopupMenuButtonComp(ObservableValue<String> name, Comp<?> content, boolean lazy) {
this.name = name;
this.content = content;
this.lazy = lazy;
}
@Override
protected Region createSimple() {
var popover = new Popover();
if (!lazy) {
popover.setContentNode(content.createRegion());
}
popover.setCloseButtonEnabled(false);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
AppFont.small(popover.getContentNode());
var extendedDescription = new Button();
extendedDescription.textProperty().bind(name);
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
extendedDescription.getStyleClass().add(Styles.BUTTON_OUTLINED);
extendedDescription.getStyleClass().add(Styles.ACCENT);
extendedDescription.getStyleClass().add("long-description");
extendedDescription.setOnAction(e -> {
if (popover.isShowing()) {
e.consume();
return;
}
if (lazy) {
popover.setContentNode(content.createRegion());
}
popover.show(extendedDescription);
e.consume();
});
return extendedDescription;
}
}

View file

@ -71,7 +71,7 @@ public class DsDataTransferComp extends SimpleComp {
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>(null) {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();

View file

@ -51,7 +51,6 @@ public class GuiDsConfigStep extends MultiStepComp.Step<CompStructure<?>> {
Property<? extends DataSource<?>> currentSource,
Property<DataSourceType> type,
BooleanProperty loading) {
super(null);
this.input = input;
this.baseSource = baseSource;
this.currentSource = currentSource;

View file

@ -26,7 +26,6 @@ public class GuiDsCreatorSaveStep extends MultiStepComp.Step<CompStructure<?>> {
public GuiDsCreatorSaveStep(
Property<DataSourceCollection> storageGroup, Property<DataSourceEntry> dataSourceEntry) {
super(null);
this.storageGroup = storageGroup;
this.dataSourceEntry = dataSourceEntry;
}

View file

@ -35,7 +35,6 @@ public class GuiDsCreatorTransferStep extends MultiStepComp.Step<CompStructure<?
DataSourceCollection targetGroup,
Property<? extends DataStore> store,
ObjectProperty<? extends DataSource<?>> source) {
super(null);
this.targetGroup = targetGroup;
this.store = store;
this.source = source;

View file

@ -41,7 +41,6 @@ public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? ext
DataSourceProvider.Category category,
ObjectProperty<? extends DataSource<?>> baseSource,
BooleanProperty loading) {
super(null);
this.parent = parent;
this.provider = provider;
this.input = input;

View file

@ -48,7 +48,7 @@ public class GuiDsTableMappingConfirmation extends SimpleComp {
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>(null) {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();

View file

@ -2,6 +2,7 @@ package io.xpipe.app.comp.source.store;
import io.xpipe.app.comp.base.ErrorOverlayComp;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.base.PopupMenuButtonComp;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
@ -22,9 +23,11 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
@ -60,7 +63,6 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
Predicate<DataStoreProvider> filter,
String initialName, boolean exists
) {
super(null);
this.parent = parent;
this.provider = provider;
this.store = store;
@ -158,6 +160,16 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
});
}
@Override
public Comp<?> bottom() {
var disable = Bindings.createBooleanBinding(() -> {
return provider.getValue() == null || store.getValue() == null || !store.getValue().isComplete();
}, provider, store);
return new PopupMenuButtonComp(new SimpleStringProperty("Insights >"), Comp.of(() -> {
return provider.getValue() != null ? provider.getValue().createInsightsComp(store).createRegion() : null;
}), true).disable(disable).styleClass("button-comp");
}
private static boolean showInvalidConfirmAlert() {
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmInvalidStoreTitle"));
@ -197,6 +209,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
private Region createLayout() {
var layout = new BorderPane();
layout.getStyleClass().add("store-creator");
layout.setPadding(new Insets(20));
var providerChoice = new DsStoreProviderChoiceComp(filter, provider);
if (provider.getValue() != null) {
@ -217,7 +230,10 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
var d = n.guiDialog(store);
var propVal = new SimpleValidator();
var propR = createStoreProperties(d == null || d.getComp() == null ? null : d.getComp(), propVal);
layout.setCenter(propR);
var sp = new ScrollPane(propR);
sp.setFitToWidth(true);
layout.setCenter(sp);
validator.setValue(new ChainedValidator(List.of(
d != null && d.getValidator() != null ? d.getValidator() : new SimpleValidator(), propVal)));

View file

@ -1,13 +1,17 @@
package io.xpipe.app.ext;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import java.util.List;
@ -27,6 +31,22 @@ public interface DataStoreProvider {
}
}
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
var content = Bindings.createStringBinding(() -> {
if (store.getValue() == null || !store.getValue().isComplete() || !getStoreClasses().contains(store.getValue().getClass())) {
return null;
}
return createInsightsMarkdown(store.getValue());
}, store);
var markdown = new MarkdownComp(content, s -> s).apply(struc -> struc.get().setPrefWidth(450)).apply(struc -> struc.get().setPrefHeight(200));
return markdown;
}
default String createInsightsMarkdown(DataStore store) {
return null;
}
default DataCategory getCategory() {
var c = getStoreClasses().get(0);
if (StreamDataStore.class.isAssignableFrom(c)) {

View file

@ -90,15 +90,17 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
popover.setDetachable(true);
AppFont.small(popover.getContentNode());
var descriptionHover = new Button("... ?");
descriptionHover.getStyleClass().add(Styles.BUTTON_OUTLINED);
descriptionHover.getStyleClass().add(Styles.ACCENT);
descriptionHover.setPadding(new Insets(0, 6, 0, 6));
descriptionHover.getStyleClass().add("long-description");
AppFont.header(descriptionHover);
descriptionHover.setOnAction(e -> popover.show(descriptionHover));
var extendedDescription = new Button("... ?");
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
extendedDescription.getStyleClass().add(Styles.BUTTON_OUTLINED);
extendedDescription.getStyleClass().add(Styles.ACCENT);
extendedDescription.setPadding(new Insets(0, 6, 0, 6));
extendedDescription.getStyleClass().add("long-description");
AppFont.header(extendedDescription);
extendedDescription.setOnAction(e -> popover.show(extendedDescription));
var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), descriptionHover);
var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), extendedDescription);
descriptionBox.setSpacing(5);
HBox.setHgrow(descriptionBox, Priority.ALWAYS);
descriptionBox.setAlignment(Pos.CENTER_LEFT);
line.getChildren().add(descriptionBox);

View file

@ -29,7 +29,7 @@
}
.bar .button-comp {
-fx-padding: 0.3em 0em 0.3em 0em;
-fx-padding: 0.2em 0em 0.2em 0em;
}
.collections-bar {

View file

@ -0,0 +1,3 @@
.store-creator .scroll-pane {
-fx-padding: 0 5px 0 0;
}

View file

@ -57,6 +57,14 @@ public abstract class ProcessControlProvider {
.orElseThrow();
}
public static String createSshPreview(Object sshStore) {
return INSTANCES.stream()
.map(localProcessControlProvider -> localProcessControlProvider.createSshControlPreview(sshStore))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow();
}
public abstract ShellControl sub(
ShellControl parent,
@NonNull FailableFunction<ShellControl, String, Exception> commandFunction,
@ -70,4 +78,6 @@ public abstract class ProcessControlProvider {
public abstract ShellControl createLocalProcessControl(boolean stoppable);
public abstract ShellControl createSshControl(Object sshStore);
public abstract String createSshControlPreview(Object sshStore);
}

View file

@ -197,7 +197,7 @@ public interface ShellControl extends ProcessControl {
default CommandControl command(List<String> command) {
return command(
shellProcessControl -> shellProcessControl.getShellDialect().flatten(command));
shellProcessControl -> ShellDialect.flatten(command));
}
void exitAndWait() throws IOException;

View file

@ -15,6 +15,16 @@ import java.util.stream.Stream;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface ShellDialect {
public static String flatten(List<String> command) {
return command.stream()
.map(s -> s.contains(" ")
&& !(s.startsWith("\"") && s.endsWith("\""))
&& !(s.startsWith("'") && s.endsWith("'"))
? "\"" + s + "\""
: s)
.collect(Collectors.joining(" "));
}
CommandControl prepareTempDirectory(ShellControl shellControl, String directory);
String initFileName(ShellControl sc) throws Exception;
@ -67,16 +77,6 @@ public interface ShellDialect {
String prepareScriptContent(String content);
default String flatten(List<String> command) {
return command.stream()
.map(s -> s.contains(" ")
&& !(s.startsWith("\"") && s.endsWith("\""))
&& !(s.startsWith("'") && s.endsWith("'"))
? "\"" + s + "\""
: s)
.collect(Collectors.joining(" "));
}
default String getExitCommand() {
return "exit";
}