mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Preview executed commands in connection creation window
This commit is contained in:
parent
6d4e800333
commit
67654f3005
17 changed files with 168 additions and 70 deletions
|
@ -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());
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.store-creator .scroll-pane {
|
||||
-fx-padding: 0 5px 0 0;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue