mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-04 03:40:32 +00:00
Rework
This commit is contained in:
@@ -30,12 +30,6 @@ public class ModalButton {
|
||||
@NonFinal
|
||||
Consumer<Button> augment;
|
||||
|
||||
public static ModalButton hide(ObservableValue<String> name, LabelGraphic icon, Runnable action) {
|
||||
return new ModalButton("hide", () -> {
|
||||
AppLayoutModel.get().getQueueEntries().add(new AppLayoutModel.QueueEntry(name, icon, action));
|
||||
}, true, false);
|
||||
}
|
||||
|
||||
public static ModalButton finish(Runnable action) {
|
||||
return new ModalButton("finish", action, true, true);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import lombok.*;
|
||||
import lombok.experimental.NonFinal;
|
||||
|
||||
@@ -24,7 +26,7 @@ public class ModalOverlay {
|
||||
}
|
||||
|
||||
public static ModalOverlay of(String titleKey, Comp<?> content, LabelGraphic graphic) {
|
||||
return new ModalOverlay(titleKey, content, graphic, new ArrayList<>(), false);
|
||||
return new ModalOverlay(titleKey, content, graphic, new ArrayList<>(), false, null);
|
||||
}
|
||||
|
||||
public ModalOverlay withDefaultButtons(Runnable action) {
|
||||
@@ -47,11 +49,21 @@ public class ModalOverlay {
|
||||
@NonFinal
|
||||
boolean persistent;
|
||||
|
||||
@NonFinal
|
||||
@Setter
|
||||
Runnable hideAction;
|
||||
|
||||
public ModalButton addButton(ModalButton button) {
|
||||
buttons.add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
public void hideable(ObservableValue<String> name, LabelGraphic icon, Runnable action) {
|
||||
setHideAction(() -> {
|
||||
AppLayoutModel.get().getQueueEntries().add(new AppLayoutModel.QueueEntry(name, icon, action));
|
||||
});
|
||||
}
|
||||
|
||||
public void addButtonBarComp(Comp<?> comp) {
|
||||
buttons.add(comp);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
@@ -21,6 +22,7 @@ import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -41,6 +43,22 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
this.overlayContent = overlayContent;
|
||||
}
|
||||
|
||||
private Animation showAnimation(Node node) {
|
||||
if (OsType.getLocal() == OsType.LINUX) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Timeline t = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(node.opacityProperty(), 0.01)),
|
||||
new KeyFrame(Duration.millis(100), new KeyValue(node.opacityProperty(), 0.01)),
|
||||
new KeyFrame(Duration.millis(200), new KeyValue(node.opacityProperty(), 1)));
|
||||
t.statusProperty().addListener((obs, old, val) -> {
|
||||
if (val == Animation.Status.STOPPED) {
|
||||
node.setOpacity(1.0F);
|
||||
}
|
||||
});
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var bgRegion = background.createRegion();
|
||||
@@ -150,6 +168,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
|
||||
private void showModalBox(ModalPane modal, ModalOverlay overlay) {
|
||||
var modalBox = toBox(modal, overlay);
|
||||
modalBox.setOpacity(0.01);
|
||||
modal.setPersistent(overlay.isPersistent());
|
||||
modal.show(modalBox);
|
||||
if (overlay.isPersistent() || overlay.getTitleKey() == null) {
|
||||
@@ -158,6 +177,19 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
closeButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
// This is ugly, but works better than animations
|
||||
// The content layout takes some time, resulting in shifting content
|
||||
// We don't want to show that, so wait after that is done
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
modalBox.setOpacity(1.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Region toBox(ModalPane pane, ModalOverlay newValue) {
|
||||
@@ -184,11 +216,12 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
}
|
||||
|
||||
if (newValue.getButtons().size() > 0) {
|
||||
var buttonBar = new ButtonBar();
|
||||
var buttonBar = new HBox();
|
||||
buttonBar.setSpacing(10);
|
||||
buttonBar.setAlignment(Pos.CENTER_RIGHT);
|
||||
for (var o : newValue.getButtons()) {
|
||||
var node = o instanceof ModalButton mb ? toButton(mb) : ((Comp<?>) o).createRegion();
|
||||
buttonBar.getButtons().add(node);
|
||||
ButtonBar.setButtonUniformSize(node, o instanceof ModalButton);
|
||||
buttonBar.getChildren().add(node);
|
||||
if (o instanceof ModalButton) {
|
||||
node.prefHeightProperty().bind(buttonBar.heightProperty());
|
||||
}
|
||||
@@ -197,7 +230,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
AppFontSizes.xs(buttonBar);
|
||||
}
|
||||
|
||||
var modalBox = new ModalBox(content) {
|
||||
var modalBox = new ModalBox(pane, content) {
|
||||
|
||||
@Override
|
||||
protected void setCloseButtonPosition() {
|
||||
@@ -205,6 +238,12 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
setRightAnchor(closeButton, 19d);
|
||||
}
|
||||
};
|
||||
if (newValue.getHideAction() != null) {
|
||||
modalBox.setOnMinimize(event -> {
|
||||
newValue.getHideAction().run();
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
modalBox.setOnClose(event -> {
|
||||
overlayContent.setValue(null);
|
||||
event.consume();
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.comp.base.ModalButton;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.ext.DataStoreProvider;
|
||||
@@ -15,6 +16,7 @@ import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
@@ -134,18 +136,12 @@ public class StoreCreationDialog {
|
||||
}
|
||||
|
||||
private static boolean showInvalidConfirmAlert() {
|
||||
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("confirmInvalidStoreTitle"));
|
||||
alert.setHeaderText(AppI18n.get("confirmInvalidStoreHeader"));
|
||||
alert.getDialogPane()
|
||||
.setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent")));
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
alert.getButtonTypes().clear();
|
||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("retry"), ButtonBar.ButtonData.CANCEL_CLOSE));
|
||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("skip"), ButtonBar.ButtonData.OK_DONE));
|
||||
})
|
||||
.map(b -> b.getButtonData().isDefaultButton())
|
||||
.orElse(false);
|
||||
var skipped = new SimpleBooleanProperty();
|
||||
var modal = ModalOverlay.of("confirmInvalidStoreTitle", AppDialog.dialogTextKey("confirmInvalidStoreContent"));
|
||||
modal.addButton(new ModalButton("retry", null, true, false));
|
||||
modal.addButton(new ModalButton("skip", () -> skipped.set(true), true, true));
|
||||
modal.showAndWait();
|
||||
return skipped.get();
|
||||
}
|
||||
|
||||
private static ModalOverlay createModalOverlay(StoreCreationModel model) {
|
||||
@@ -153,6 +149,13 @@ public class StoreCreationDialog {
|
||||
comp.prefWidth(650);
|
||||
var nameKey = model.storeTypeNameKey() + "Add";
|
||||
var modal = ModalOverlay.of(nameKey, comp);
|
||||
var provider = model.getProvider().getValue();
|
||||
var graphic = provider != null && provider.getDisplayIconFileName(model.getStore().get()) != null?
|
||||
new LabelGraphic.ImageGraphic(provider.getDisplayIconFileName(model.getStore().get()), 20) :
|
||||
new LabelGraphic.IconGraphic("mdi2b-beaker-plus-outline");
|
||||
modal.hideable(AppI18n.observable(model.storeTypeNameKey() + "Add"), graphic, () -> {
|
||||
modal.show();
|
||||
});
|
||||
modal.persist();
|
||||
modal.addButton(new ModalButton("docs", () -> {
|
||||
model.showDocs();
|
||||
@@ -160,27 +163,26 @@ public class StoreCreationDialog {
|
||||
button.visibleProperty().bind(Bindings.not(model.canShowDocs()));
|
||||
}));
|
||||
modal.addButton(ModalButton.cancel());
|
||||
var graphic = model.getProvider().getValue() != null ?
|
||||
new LabelGraphic.ImageGraphic(model.getProvider().getValue().getDisplayIconFileName(null), 20) :
|
||||
new LabelGraphic.IconGraphic("mdi2b-beaker-plus-outline");
|
||||
modal.addButton(ModalButton.hide(AppI18n.observable(model.storeTypeNameKey() + "Add"), graphic, () -> {
|
||||
modal.show();
|
||||
}));
|
||||
modal.addButton(new ModalButton("connect", () -> {
|
||||
model.connect();
|
||||
}, false, false).augment(button -> {
|
||||
button.visibleProperty().bind(Bindings.not(model.canConnect()));
|
||||
}));
|
||||
modal.addButton(new ModalButton("skipValidation", () -> {
|
||||
if (showInvalidConfirmAlert()) {
|
||||
modal.addButton(new ModalButton("skip", () -> {
|
||||
if (!showInvalidConfirmAlert()) {
|
||||
model.commit();
|
||||
} else {
|
||||
model.finish();
|
||||
}
|
||||
}, true, false));
|
||||
}, true, false)).augment(button -> {
|
||||
button.visibleProperty().bind(model.getSkippable());
|
||||
});
|
||||
modal.addButton(new ModalButton("finish", () -> {
|
||||
model.finish();
|
||||
}, true, true));
|
||||
}, false, true));
|
||||
model.getFinished().addListener((obs, oldValue, newValue) -> {
|
||||
modal.close();
|
||||
});
|
||||
return modal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class StoreIconChoiceDialog {
|
||||
|
||||
private ModalOverlay createOverlay() {
|
||||
var filterText = new SimpleStringProperty();
|
||||
var filter = new FilterComp(filterText).grow(true, false);
|
||||
var filter = new FilterComp(filterText).hgrow();
|
||||
filter.focusOnShow();
|
||||
var github = new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
|
||||
overlay.close();
|
||||
|
||||
@@ -60,7 +60,10 @@ public class ErrorHandlerDialog {
|
||||
}, false, false));
|
||||
}
|
||||
errorModal.addButton(new ModalButton("report", () -> {
|
||||
UserReportComp.show(event);
|
||||
if (UserReportComp.show(event)) {
|
||||
comp.getTakenAction().setValue(ErrorAction.ignore());
|
||||
errorModal.close();
|
||||
}
|
||||
}, false, false));
|
||||
errorModal.addButton(ModalButton.ok());
|
||||
modal.set(errorModal);
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
package io.xpipe.app.issue;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -28,31 +21,57 @@ import atlantafx.base.controls.Spacer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class UserReportComp extends SimpleComp {
|
||||
public class UserReportComp extends ModalOverlayContentComp {
|
||||
|
||||
private final StringProperty email = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
private final ListProperty<Path> includedDiagnostics;
|
||||
private final ErrorEvent event;
|
||||
private final Stage stage;
|
||||
|
||||
private boolean sent;
|
||||
|
||||
public UserReportComp(ErrorEvent event, Stage stage) {
|
||||
public UserReportComp(ErrorEvent event) {
|
||||
this.event = event;
|
||||
this.includedDiagnostics = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
this.stage = stage;
|
||||
stage.setOnHidden(event1 -> {
|
||||
if (!sent) {
|
||||
ErrorAction.ignore().handle(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void show(ErrorEvent event) {
|
||||
var window =
|
||||
AppWindowHelper.sideWindow(AppI18n.get("errorHandler"), w -> new UserReportComp(event, w), true, null);
|
||||
window.showAndWait();
|
||||
public static boolean show(ErrorEvent event) {
|
||||
var comp = new UserReportComp(event);
|
||||
var modal = ModalOverlay.of("errorHandler", comp);
|
||||
var sent = new SimpleBooleanProperty();
|
||||
modal.addButtonBarComp(privacyPolicy());
|
||||
modal.addButtonBarComp(Comp.hspacer());
|
||||
modal.addButton(new ModalButton("sendReport", () -> {
|
||||
comp.send();
|
||||
sent.set(true);
|
||||
}, true, true));
|
||||
modal.showAndWait();
|
||||
return sent.get();
|
||||
}
|
||||
|
||||
private static Comp<?> privacyPolicy() {
|
||||
return Comp.of(() -> {
|
||||
var dataPolicyButton = new Hyperlink(AppI18n.get("dataHandlingPolicies"));
|
||||
AppFontSizes.xs(dataPolicyButton);
|
||||
dataPolicyButton.setOnAction(event1 -> {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "misc/report_privacy_policy.md", file -> {
|
||||
var markDown = new MarkdownComp(Files.readString(file), s -> s, true)
|
||||
.apply(struc -> struc.get().setMaxWidth(500))
|
||||
.apply(struc -> struc.get().setMaxHeight(400));
|
||||
var popover = new Popover(markDown.createRegion());
|
||||
popover.setCloseButtonEnabled(true);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
popover.show(dataPolicyButton);
|
||||
});
|
||||
event1.consume();
|
||||
});
|
||||
|
||||
var agree = new Label("Note the issue reporter ");
|
||||
var buttons = new HBox(agree, dataPolicyButton);
|
||||
buttons.setAlignment(Pos.CENTER_LEFT);
|
||||
buttons.setMinWidth(Region.USE_PREF_SIZE);
|
||||
return buttons;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,13 +113,10 @@ public class UserReportComp extends SimpleComp {
|
||||
reportSection.setSpacing(5);
|
||||
reportSection.getStyleClass().add("report");
|
||||
|
||||
var buttons = createBottomBarNavigation();
|
||||
|
||||
reportSection.getChildren().addAll(new Spacer(8, Orientation.VERTICAL), emailHeader, email);
|
||||
|
||||
var layout = new BorderPane();
|
||||
layout.setCenter(reportSection);
|
||||
layout.setBottom(buttons);
|
||||
layout.getStyleClass().add("error-report");
|
||||
layout.getStyleClass().add("background");
|
||||
layout.setPrefWidth(600);
|
||||
@@ -108,42 +124,11 @@ public class UserReportComp extends SimpleComp {
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Region createBottomBarNavigation() {
|
||||
var dataPolicyButton = new Hyperlink(AppI18n.get("dataHandlingPolicies"));
|
||||
AppFontSizes.xs(dataPolicyButton);
|
||||
dataPolicyButton.setOnAction(event1 -> {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "misc/report_privacy_policy.md", file -> {
|
||||
var markDown = new MarkdownComp(Files.readString(file), s -> s, true)
|
||||
.apply(struc -> struc.get().setMaxWidth(500))
|
||||
.apply(struc -> struc.get().setMaxHeight(400));
|
||||
var popover = new Popover(markDown.createRegion());
|
||||
popover.setCloseButtonEnabled(true);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
popover.show(dataPolicyButton);
|
||||
});
|
||||
event1.consume();
|
||||
});
|
||||
var sendButton = new ButtonComp(AppI18n.observable("sendReport"), this::send)
|
||||
.apply(struc -> struc.get().setDefaultButton(true))
|
||||
.createRegion();
|
||||
var spacer = new Region();
|
||||
var agree = new Label("Note the issue reporter ");
|
||||
var buttons = new HBox(agree, dataPolicyButton, spacer, sendButton);
|
||||
buttons.setAlignment(Pos.CENTER);
|
||||
buttons.getStyleClass().add("buttons");
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private void send() {
|
||||
event.clearAttachments();
|
||||
event.setShouldSendDiagnostics(true);
|
||||
includedDiagnostics.forEach(event::addAttachment);
|
||||
event.attachUserReport(email.get(), text.get());
|
||||
SentryErrorHandler.getInstance().handle(event);
|
||||
sent = true;
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.*;
|
||||
@@ -16,7 +15,6 @@ import io.xpipe.core.process.ShellScript;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import lombok.Builder;
|
||||
@@ -106,7 +104,7 @@ public class PasswordManagerCommand implements PasswordManager {
|
||||
|
||||
@Override
|
||||
public String retrievePassword(String key) {
|
||||
var cmd = ExternalApplicationHelper.replaceFileArgument(script.getValue(), "KEY", key);
|
||||
var cmd = ExternalApplicationHelper.replaceVariableArgument(script.getValue(), "KEY", key);
|
||||
return retrieveWithCommand(cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public abstract class PasswordManagerFixedCommand implements PasswordManager {
|
||||
|
||||
@Override
|
||||
public synchronized String retrievePassword(String key) {
|
||||
var cmd = ExternalApplicationHelper.replaceFileArgument(getScript().getValue(), "KEY", key);
|
||||
var cmd = ExternalApplicationHelper.replaceVariableArgument(getScript().getValue(), "KEY", key);
|
||||
return PasswordManagerCommand.retrieveWithCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class ExternalApplicationHelper {
|
||||
|
||||
public static String replaceFileArgument(String format, String variable, String file) {
|
||||
public static String replaceVariableArgument(String format, String variable, String value) {
|
||||
// Support for legacy variables that were not upper case
|
||||
variable = variable.toUpperCase(Locale.ROOT);
|
||||
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
|
||||
|
||||
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
|
||||
var fileString = value.contains(" ") ? "\"" + value + "\"" : value;
|
||||
// Check if the variable is already quoted
|
||||
return format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
|
||||
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
var command = CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString()));
|
||||
var command = CommandBuilder.of().add(ExternalApplicationHelper.replaceVariableArgument(format, "FILE", file.toString()));
|
||||
if (AppPrefs.get().customEditorCommandInTerminal().get()) {
|
||||
TerminalLauncher.openDirect(file.toString(), sc -> command.buildFull(sc), AppPrefs.get().terminalType.get());
|
||||
} else {
|
||||
|
||||
@@ -293,7 +293,7 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
||||
var format =
|
||||
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add(ExternalApplicationHelper.replaceFileArgument(
|
||||
.add(ExternalApplicationHelper.replaceVariableArgument(
|
||||
format,
|
||||
"FILE",
|
||||
writeRdpConfigFile(configuration.getTitle(), configuration.getConfig())
|
||||
|
||||
@@ -39,7 +39,7 @@ public class CustomTerminalType extends ExternalApplicationType implements Exter
|
||||
|
||||
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
|
||||
try (var pc = LocalShell.getShell()) {
|
||||
var toExecute = ExternalApplicationHelper.replaceFileArgument(
|
||||
var toExecute = ExternalApplicationHelper.replaceVariableArgument(
|
||||
format, "CMD", configuration.getScriptFile().toString());
|
||||
// We can't be sure whether the command is blocking or not, so always make it not blocking
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
}
|
||||
|
||||
.error-report .report {
|
||||
-fx-padding: 1.0em 1.5em 1em 1.5em;
|
||||
-fx-spacing: 1.3em;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Add setting to disable HTTPs TLS verification for license activation API calls for cases where TLS traffic is decrypted in your organization
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix custom service commands not launching properly with PowerShell as the local shell
|
||||
- Fix update check being influenced by the local GitHub rate limiting
|
||||
@@ -126,7 +126,7 @@ public interface ServiceProtocolType {
|
||||
var format = commandTemplate.toLowerCase(Locale.ROOT).contains("$port")
|
||||
? commandTemplate
|
||||
: commandTemplate + " localhost:$PORT";
|
||||
var toExecute = ExternalApplicationHelper.replaceFileArgument(format, "PORT", port);
|
||||
var toExecute = ExternalApplicationHelper.replaceVariableArgument(format, "PORT", port);
|
||||
// We can't be sure whether the command is blocking or not, so always make it not blocking
|
||||
ExternalApplicationHelper.startAsync(toExecute);
|
||||
}
|
||||
|
||||
Binary file not shown.
Generated
+4
-3
@@ -50,9 +50,10 @@ dragAndDropFilesHere=Or just drag and drop a file here
|
||||
confirmDsCreationAbortTitle=Confirm abort
|
||||
confirmDsCreationAbortHeader=Do you want to abort the data source creation?
|
||||
confirmDsCreationAbortContent=Any data source creation progress will be lost.
|
||||
confirmInvalidStoreTitle=Failed connection
|
||||
confirmInvalidStoreHeader=Do you want to skip connection validation?
|
||||
confirmInvalidStoreContent=You can add this connection even if it could not be validated and fix the connection problems later on.
|
||||
#force
|
||||
confirmInvalidStoreTitle=Skip validation
|
||||
#force
|
||||
confirmInvalidStoreContent=Do you want to skip connection validation? You can add this connection even if it could not be validated and fix the connection problems later on.
|
||||
expand=Expand
|
||||
accessSubConnections=Access sub connections
|
||||
#context: noun, not rare
|
||||
|
||||
Reference in New Issue
Block a user