Various fixes

This commit is contained in:
crschnick 2025-04-03 13:10:31 +00:00
parent f7ac25a073
commit b591fa2b5a
20 changed files with 115 additions and 84 deletions

View file

@ -2,7 +2,7 @@ package io.xpipe.app.browser.action;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.util.BindingsHelper;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.LicenseProvider;
@ -10,6 +10,7 @@ import io.xpipe.app.util.ThreadHelper;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
@ -38,7 +39,7 @@ public interface BrowserLeafAction extends BrowserAction {
event.consume();
});
var name = getName(model, selected);
new TooltipAugment<>(name, getShortcut()).augment(b);
Tooltip.install(b, TooltipHelper.create(name, getShortcut()));
var graphic = getIcon(model, selected);
if (graphic != null) {
b.setGraphic(graphic);

View file

@ -3,7 +3,8 @@ package io.xpipe.app.browser.file;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.base.TextFieldComp;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.InputHelper;
import javafx.beans.property.Property;
@ -11,6 +12,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
@ -45,8 +47,7 @@ public class BrowserFileListFilterComp extends Comp<BrowserFileListFilterComp.St
button.fire();
keyEvent.consume();
});
new TooltipAugment<>("app.search", new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN))
.augment(button);
Tooltip.install(button, TooltipHelper.create(AppI18n.observable("app.search"), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)));
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue && filterString.getValue() == null) {
if (button.isFocused()) {

View file

@ -8,6 +8,7 @@ import io.xpipe.app.comp.SimpleCompStructure;
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.util.InputHelper;
import io.xpipe.app.util.PlatformThread;
import io.xpipe.core.store.FilePath;
@ -16,6 +17,7 @@ import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
@ -51,8 +53,7 @@ public class BrowserFileSystemTabComp extends SimpleComp {
var root = new VBox();
var overview = new Button(null, new FontIcon("mdi2m-monitor"));
overview.setOnAction(e -> model.cdAsync((FilePath) null));
new TooltipAugment<>("overview", new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN))
.augment(overview);
Tooltip.install(overview, TooltipHelper.create(AppI18n.observable("overview"), new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN)));
overview.disableProperty().bind(model.getInOverview());
overview.setAccessibleText("System overview");
InputHelper.onKeyCombination(

View file

@ -351,6 +351,7 @@ public class BrowserFileTransferOperation {
outputStream = target.getFileSystem().openOutput(targetFile, fileSize);
transferFile(sourceFile, inputStream, outputStream, transferred, totalSize, start, fileSize);
outputStream.flush();
inputStream.transferTo(OutputStream.nullOutputStream());
} catch (Exception ex) {
// Mark progress as finished to reset any progress display

View file

@ -7,7 +7,8 @@ import io.xpipe.app.comp.SimpleCompStructure;
import io.xpipe.app.comp.augment.ContextMenuAugment;
import io.xpipe.app.comp.base.PrettyImageHelper;
import io.xpipe.app.comp.base.TextFieldComp;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ContextMenuHelper;
import io.xpipe.app.util.PlatformThread;
@ -65,8 +66,7 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, this::createContextMenu)
.augment(new SimpleCompStructure<>(historyButton));
new TooltipAugment<>("history", new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN))
.augment(historyButton);
Tooltip.install(historyButton, TooltipHelper.create(AppI18n.observable("history"), new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN)));
var breadcrumbs = new BrowserBreadcrumbBar(model);

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp;
import io.xpipe.app.comp.augment.Augment;
import io.xpipe.app.comp.augment.GrowAugment;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.BindingsHelper;
import io.xpipe.app.util.PlatformThread;
@ -12,6 +12,7 @@ import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Separator;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
@ -212,15 +213,24 @@ public abstract class Comp<S extends CompStructure<?>> {
}
public Comp<S> tooltip(ObservableValue<String> text) {
return apply(new TooltipAugment<>(text, null));
return apply(struc -> {
var tt = TooltipHelper.create(text, null);
Tooltip.install(struc.get(), tt);
});
}
public Comp<S> tooltipKey(String key) {
return apply(new TooltipAugment<>(key, null));
return apply(struc -> {
var tt = TooltipHelper.create(AppI18n.observable(key), null);
Tooltip.install(struc.get(), tt);
});
}
public Comp<S> tooltipKey(String key, KeyCombination shortcut) {
return apply(new TooltipAugment<>(key, shortcut));
return apply(struc -> {
var tt = TooltipHelper.create(AppI18n.observable(key), shortcut);
Tooltip.install(struc.get(), tt);
});
}
public Region createRegion() {

View file

@ -4,20 +4,24 @@ import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.SimpleCompStructure;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.PlatformThread;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import javafx.util.Duration;
import lombok.Getter;
import java.util.ArrayList;
@ -26,9 +30,9 @@ import java.util.List;
@Getter
public class OptionsComp extends Comp<CompStructure<Pane>> {
private final List<OptionsComp.Entry> entries;
private final List<Entry> entries;
public OptionsComp(List<OptionsComp.Entry> entries) {
public OptionsComp(List<Entry> entries) {
this.entries = entries;
}
@ -84,15 +88,19 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
description.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
}
if (entry.longDescriptionSource() != null) {
var markDown = new MarkdownComp(entry.longDescriptionSource(), s -> s, true)
.apply(struc -> struc.get().setMaxWidth(500))
.apply(struc -> struc.get().setMaxHeight(400));
var popover = new Popover(markDown.createRegion());
popover.setCloseButtonEnabled(false);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
AppFontSizes.xs(popover.getContentNode());
if (entry.longDescription() != null) {
Popover popover;
if (!entry.longDescription().startsWith("http")) {
var markDown = new MarkdownComp(entry.longDescription(), s -> s, true).apply(struc -> struc.get().setMaxWidth(500)).apply(
struc -> struc.get().setMaxHeight(400));
popover = new Popover(markDown.createRegion());
popover.setCloseButtonEnabled(false);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
AppFontSizes.xs(popover.getContentNode());
} else {
popover = null;
}
var extendedDescription = new Button("... ?");
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
@ -102,10 +110,20 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
extendedDescription.setAccessibleText("Help");
AppFontSizes.xl(extendedDescription);
extendedDescription.setOnAction(e -> {
popover.show(extendedDescription);
if (entry.longDescription().startsWith("http")) {
Hyperlinks.open(entry.longDescription());
} else if (popover != null) {
popover.show(extendedDescription);
}
e.consume();
});
if (entry.longDescription().startsWith("http")) {
var tt = TooltipHelper.create(new SimpleStringProperty(entry.longDescription()), null);
tt.setShowDelay(Duration.millis(1));
Tooltip.install(extendedDescription, tt);
}
var descriptionBox =
new HBox(description, new Spacer(Orientation.HORIZONTAL), extendedDescription);
descriptionBox.setSpacing(5);
@ -196,7 +214,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
public record Entry(
String key,
ObservableValue<String> description,
String longDescriptionSource,
String longDescription,
ObservableValue<String> name,
Comp<?> comp) {}
}

View file

@ -1,34 +1,23 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.augment.Augment;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCombination;
import javafx.stage.Window;
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
public class TooltipHelper {
private final ObservableValue<String> text;
private final KeyCombination shortcut;
public TooltipAugment(ObservableValue<String> text, KeyCombination shortcut) {
this.text = text;
this.shortcut = shortcut;
public static Tooltip create(String text) {
return create(new SimpleStringProperty(text), null);
}
public TooltipAugment(String key, KeyCombination shortcut) {
this.text = AppI18n.observable(key);
this.shortcut = shortcut;
}
@Override
public void augment(S struc) {
public static Tooltip create(ObservableValue<String> text, KeyCombination shortcut) {
var tt = new FixedTooltip();
if (shortcut != null) {
var s = AppI18n.observable("shortcut");
@ -46,7 +35,7 @@ public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
tt.setWrapText(true);
tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip");
Tooltip.install(struc.get(), tt);
return tt;
}
private static class FixedTooltip extends Tooltip {

View file

@ -1,11 +1,12 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.core.AppI18n;
import javafx.geometry.Pos;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.Tooltip;
import javafx.scene.input.*;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
@ -32,7 +33,7 @@ public class StoreActiveComp extends SimpleComp {
pane.setAlignment(Pos.CENTER);
pane.visibleProperty().bind(wrapper.getSessionActive());
pane.getStyleClass().add("store-active-comp");
new TooltipAugment<>("sessionActive", null).augment(pane);
Tooltip.install(pane, TooltipHelper.create(AppI18n.observable("sessionActive"), null));
return pane;
}
}

View file

@ -51,10 +51,9 @@ public class StoreCreationComp extends ModalOverlayContentComp {
var layout = new BorderPane();
layout.getStyleClass().add("store-creator");
var providerChoice = new StoreProviderChoiceComp(model.getFilter(), model.getProvider());
var showProviders = (!model.isStaticDisplay()
&& (providerChoice.getProviders().size() > 1
|| providerChoice.getProviders().getFirst().showProviderChoice()))
|| (model.isStaticDisplay() && model.getProvider().getValue().showProviderChoice());
var provider = model.getProvider().getValue() != null ? model.getProvider().getValue() : providerChoice.getProviders().getFirst();
var showProviders = (!model.isStaticDisplay() && provider.showProviderChoice())
|| (model.isStaticDisplay() && provider.showProviderChoice());
if (model.isStaticDisplay()) {
providerChoice.apply(struc -> struc.get().setDisable(true));
}

View file

@ -8,7 +8,6 @@ import io.xpipe.app.comp.augment.GrowAugment;
import io.xpipe.app.comp.base.IconButtonComp;
import io.xpipe.app.comp.base.LabelComp;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.core.*;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.prefs.AppPrefs;
@ -263,7 +262,7 @@ public abstract class StoreEntryComp extends SimpleComp {
}));
}
button.accessibleText(cs.getName(getWrapper().getEntry().ref()).getValue());
button.apply(new TooltipAugment<>(cs.getName(getWrapper().getEntry().ref()), null));
button.tooltip(cs.getName(getWrapper().getEntry().ref()));
return button;
}

View file

@ -2,13 +2,13 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.comp.base.PrettyImageHelper;
import io.xpipe.app.comp.base.TooltipAugment;
import io.xpipe.app.comp.base.TooltipHelper;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.Tooltip;
import javafx.scene.input.*;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@ -27,7 +27,7 @@ public class StoreIconComp extends SimpleComp {
var imageComp = PrettyImageHelper.ofFixedSize(wrapper.getIconFile(), w, h);
var storeIcon = imageComp.createRegion();
if (wrapper.getValidity().getValue().isUsable()) {
new TooltipAugment<>(wrapper.getEntry().getProvider().displayName(), null).augment(storeIcon);
Tooltip.install(storeIcon, TooltipHelper.create(wrapper.getEntry().getProvider().displayName(), null));
}
var background = new Region();

View file

@ -6,6 +6,7 @@ import io.xpipe.app.comp.base.*;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
@ -67,7 +68,7 @@ public class StoreListChoiceComp<T extends DataStore> extends SimpleComp {
selected.setValue(null);
}
});
var vbox = new VerticalComp(List.of(list, Comp.vspacer(5), add))
var vbox = new VerticalComp(List.of(list, Comp.vspacer(5).hide(Bindings.isEmpty(selectedList)), add))
.apply(struc -> struc.get().setFillWidth(true));
return vbox.styleClass("data-store-list-choice-comp").createRegion();
}

View file

@ -113,16 +113,10 @@ public class UserReportComp extends ModalOverlayContentComp {
attachments);
reportSection.setSpacing(5);
reportSection.getStyleClass().add("report");
reportSection.getChildren().addAll(new Spacer(8, Orientation.VERTICAL), emailHeader, email);
var layout = new BorderPane();
layout.setCenter(reportSection);
layout.getStyleClass().add("error-report");
layout.getStyleClass().add("background");
layout.setPrefWidth(600);
layout.setPrefHeight(550);
return layout;
reportSection.setPrefWidth(600);
reportSection.setPrefHeight(550);
return reportSection;
}
private void send() {

View file

@ -13,7 +13,22 @@ public enum DocumentationLink {
WEBTOP_UPDATE("guide/webtop#updating"),
SYNC("guide/sync"),
SCRIPTING("guide/scripting"),
SCRIPTING_COMPATIBILITY("guide/scripting#shell-compatibility"),
SCRIPTING_EDITING("guide/scripting#editing"),
SCRIPTING_TYPES("guide/scripting#init-scripts"),
SCRIPTING_DEPENDENCIES("guide/scripting#dependencies"),
SCRIPTING_GROUPS("guide/scripting#groups"),
KEEPASSXC("guide/keepassxc"),
KUBERNETES("guide/kubernetes"),
DOCKER("guide/docker"),
PROXMOX("guide/proxmox"),
TAILSCALE("guide/tailscale"),
TELEPORT("guide/teleport"),
LXC("guide/lxc"),
PODMAN("guide/podman"),
KVM("guide/kvm"),
VMWARE("guide/vmware"),
VNC("guide/vnc"),
SSH("guide/ssh");
private final String page;

View file

@ -377,9 +377,15 @@ public class OptionsBuilder {
return this;
}
public OptionsBuilder longDescription(DocumentationLink link) {
finishCurrent();
longDescription = link.getLink();
return this;
}
public OptionsBuilder longDescription(String descriptionKey) {
finishCurrent();
longDescription = AppI18n.get().getMarkdownDocumentation(descriptionKey);
longDescription = descriptionKey.startsWith("http") ? descriptionKey : AppI18n.get().getMarkdownDocumentation(descriptionKey);
return this;
}

View file

@ -1,12 +1,3 @@
.data-store-list-choice-comp {
-fx-border-width: 1px;
-fx-background-color: -color-bg-default;
-fx-border-color: -color-border-default;
-fx-padding: 0.6em;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
}
.data-store-list-choice-comp .entry {
-fx-border-width: 1px;
-fx-background-color: -color-bg-default;

View file

@ -58,6 +58,8 @@ public interface CommandControl extends ProcessControl {
void setExitTimeout(Duration duration);
void setStartTimeout(Duration duration);
boolean waitFor();
CommandControl withCustomCharset(Charset charset);

View file

@ -144,11 +144,11 @@ public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, Da
return new OptionsBuilder()
.name("minimumShellDialect")
.description("minimumShellDialectDescription")
.longDescription("base:scriptCompatibility")
.longDescription(DocumentationLink.SCRIPTING_COMPATIBILITY)
.addComp(choice, dialect)
.name("scriptContents")
.description("scriptContentsDescription")
.longDescription("base:script")
.longDescription(DocumentationLink.SCRIPTING_EDITING)
.addComp(
new IntegratedTextAreaComp(commandProp, false, "commands", Bindings.createStringBinding(() -> {
return dialect.getValue() != null
@ -157,13 +157,13 @@ public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, Da
})),
commandProp)
.nameAndDescription("executionType")
.longDescription("base:executionType")
.longDescription(DocumentationLink.SCRIPTING_TYPES)
.addComp(selectorComp, selectedExecTypes)
.check(validator ->
Validator.nonEmpty(validator, AppI18n.observable("executionType"), selectedExecTypes))
.name("snippets")
.description("snippetsDescription")
.longDescription("base:scriptDependencies")
.longDescription(DocumentationLink.SCRIPTING_DEPENDENCIES)
.addComp(
new StoreListChoiceComp<>(
others,
@ -173,6 +173,7 @@ public class SimpleScriptStoreProvider implements EnabledParentStoreProvider, Da
others)
.name("scriptGroup")
.description("scriptGroupDescription")
.longDescription(DocumentationLink.SCRIPTING_GROUPS)
.addComp(
new StoreChoiceComp<>(
StoreChoiceComp.Mode.OTHER,

View file

@ -633,7 +633,8 @@ executionTypeDescription=In what contexts to use this script
#context: computer shell program
minimumShellDialect=Shell type
#context: computer shell program
minimumShellDialectDescription=The required shell type for this script
#force
minimumShellDialectDescription=The shell type to run this script in
dumbOnly=Dumb
terminalOnly=Terminal
both=Both
@ -913,7 +914,7 @@ sshLocalTunnel.bindingDescription=What addresses to bind the tunnel to
sshLocalTunnel.localAddressDescription=The local address to bind
sshLocalTunnel.remoteAddressDescription=The remote address to bind
cmd.displayName=Terminal command
cmd.displayDescription=Run a custom command on a system in your terminal
cmd.displayDescription=Run a fixed command on a system in your terminal
k8sPod.displayName=Kubernetes Pod
k8sPod.displayDescription=Connect to a pod and its containers via kubectl
k8sContainer.displayName=Kubernetes Container