mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-18 21:50:01 -04:00
Rework connection notes
This commit is contained in:
@@ -4,6 +4,7 @@ import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.core.AppExtensionManager;
|
||||
import io.xpipe.app.core.AppNames;
|
||||
import io.xpipe.app.ext.*;
|
||||
import io.xpipe.app.hub.comp.StoreViewState;
|
||||
import io.xpipe.app.process.ScriptHelper;
|
||||
import io.xpipe.app.process.ShellControl;
|
||||
import io.xpipe.app.process.TerminalInitScriptConfig;
|
||||
@@ -82,6 +83,10 @@ public final class McpTools {
|
||||
|
||||
@NonNull
|
||||
String path;
|
||||
|
||||
String information;
|
||||
|
||||
String notes;
|
||||
}
|
||||
|
||||
public static McpServerFeatures.SyncToolSpecification listSystems() throws IOException {
|
||||
@@ -104,9 +109,14 @@ public final class McpTools {
|
||||
continue;
|
||||
}
|
||||
|
||||
var section = StoreViewState.get().getSectionForWrapper(StoreViewState.get().getEntryWrapper(e));
|
||||
var info = section.isPresent() ? e.getProvider().informationString(section.get()).getValue() : null;
|
||||
|
||||
var r = ConnectionResource.builder()
|
||||
.name(e.getName())
|
||||
.path(DataStorage.get().getStorePath(e).toString())
|
||||
.information(info)
|
||||
.notes(e.getNotes())
|
||||
.build();
|
||||
list.add(r);
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.RegionBuilder;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import org.int4.fx.builders.common.AbstractRegionBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DialogComp extends RegionBuilder<Region> {
|
||||
|
||||
protected Region createNavigation() {
|
||||
HBox buttons = new HBox();
|
||||
buttons.setFillHeight(true);
|
||||
var customButton = bottom();
|
||||
if (customButton != null) {
|
||||
var c = customButton.build();
|
||||
buttons.getChildren().add(c);
|
||||
HBox.setHgrow(c, Priority.ALWAYS);
|
||||
}
|
||||
var spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.SOMETIMES);
|
||||
buttons.getChildren().add(spacer);
|
||||
buttons.getStyleClass().add("buttons");
|
||||
buttons.setSpacing(5);
|
||||
buttons.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
buttons.getChildren()
|
||||
.addAll(customButtons().stream()
|
||||
.map(buttonComp -> buttonComp.build())
|
||||
.toList());
|
||||
var nextButton = finishButton();
|
||||
buttons.getChildren().add(nextButton.build());
|
||||
return buttons;
|
||||
}
|
||||
|
||||
protected AbstractRegionBuilder<?, ?> finishButton() {
|
||||
return new ButtonComp(AppI18n.observable(finishKey()), this::finish)
|
||||
.style(Styles.ACCENT)
|
||||
.style("next");
|
||||
}
|
||||
|
||||
protected String finishKey() {
|
||||
return "finishStep";
|
||||
}
|
||||
|
||||
protected List<AbstractRegionBuilder<?, ?>> customButtons() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region createSimple() {
|
||||
var sp = pane(content()).style("dialog-content").build();
|
||||
VBox vbox = new VBox();
|
||||
vbox.getChildren().addAll(sp, createNavigation());
|
||||
vbox.getStyleClass().add("dialog-comp");
|
||||
vbox.setFillWidth(true);
|
||||
VBox.setVgrow(sp, Priority.ALWAYS);
|
||||
return vbox;
|
||||
}
|
||||
|
||||
protected abstract void finish();
|
||||
|
||||
public abstract AbstractRegionBuilder<?, ?> content();
|
||||
|
||||
protected AbstractRegionBuilder<?, ?> pane(AbstractRegionBuilder<?, ?> content) {
|
||||
var entry = content;
|
||||
return RegionBuilder.of(() -> {
|
||||
var entryR = entry.build();
|
||||
var sp = new ScrollPane(entryR);
|
||||
sp.setFitToWidth(true);
|
||||
entryR.minHeightProperty().bind(sp.heightProperty());
|
||||
return sp;
|
||||
});
|
||||
}
|
||||
|
||||
public AbstractRegionBuilder<?, ?> bottom() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -459,10 +459,10 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
|
||||
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2c-comment-text-outline"));
|
||||
notes.setOnAction(event -> {
|
||||
getWrapper().getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
|
||||
StoreNotesComp.showDialog(getWrapper(), getDefaultNotes());
|
||||
event.consume();
|
||||
});
|
||||
notes.visibleProperty().bind(BindingsHelper.map(getWrapper().getNotes(), s -> s.getCommited() == null));
|
||||
notes.visibleProperty().bind(BindingsHelper.map(getWrapper().getNotes(), s -> s == null));
|
||||
items.add(items.size(), notes);
|
||||
|
||||
var freeze = new MenuItem();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class StoreEntryWrapper {
|
||||
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
|
||||
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
|
||||
private final Property<String> summary = new SimpleObjectProperty<>();
|
||||
private final Property<StoreNotes> notes;
|
||||
private final ObjectProperty<String> notes;
|
||||
private final Property<String> customIcon = new SimpleObjectProperty<>();
|
||||
private final Property<String> iconFile = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty sessionActive = new SimpleBooleanProperty();
|
||||
@@ -117,7 +117,7 @@ public class StoreEntryWrapper {
|
||||
shownSummary,
|
||||
AppI18n.activeLanguage());
|
||||
this.shownInformation = new SimpleObjectProperty<>();
|
||||
this.notes = new SimpleObjectProperty<>(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
||||
this.notes = new SimpleObjectProperty<>(entry.getNotes());
|
||||
|
||||
setupListeners();
|
||||
}
|
||||
@@ -158,12 +158,6 @@ public class StoreEntryWrapper {
|
||||
entry.addListener(() -> PlatformThread.runLaterIfNeeded(() -> {
|
||||
update();
|
||||
}));
|
||||
|
||||
notes.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue.isCommited()) {
|
||||
entry.setNotes(newValue.getCurrent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stopSession() {
|
||||
@@ -211,7 +205,7 @@ public class StoreEntryWrapper {
|
||||
}
|
||||
orderIndex.setValue(entry.getOrderIndex());
|
||||
color.setValue(entry.getColor());
|
||||
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
||||
notes.setValue(entry.getNotes());
|
||||
customIcon.setValue(entry.getIcon());
|
||||
readOnly.setValue(entry.isFreeze());
|
||||
iconFile.setValue(entry.getEffectiveIconFile());
|
||||
|
||||
@@ -1,30 +1,43 @@
|
||||
package io.xpipe.app.hub.comp;
|
||||
|
||||
import io.xpipe.app.comp.*;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.MarkdownEditorComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.platform.BindingsHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.input.MouseButton;
|
||||
|
||||
import atlantafx.base.controls.Popover;
|
||||
import org.int4.fx.builders.common.AbstractRegionBuilder;
|
||||
import java.util.UUID;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
public class StoreNotesComp extends RegionBuilder<Button> {
|
||||
|
||||
public class StoreNotesComp extends RegionStructureBuilder<Button, StoreNotesComp.Structure> {
|
||||
public static void showDialog(StoreEntryWrapper wrapper, String initial) {
|
||||
var prop = new SimpleStringProperty(initial);
|
||||
var md = new MarkdownEditorComp(prop, "notes-" + wrapper.getName().getValue())
|
||||
.prefWidth(600)
|
||||
.prefHeight(600);
|
||||
|
||||
var modal = ModalOverlay.of(new ReadOnlyStringWrapper(wrapper.getName().getValue()), md, null);
|
||||
if (wrapper.getNotes().getValue() != null) {
|
||||
modal.addButton(new ModalButton("delete", () -> {
|
||||
wrapper.getEntry().setNotes(null);
|
||||
DataStorage.get().saveAsync();
|
||||
}, true, false));
|
||||
}
|
||||
modal.addButton(new ModalButton("cancel", () -> {}, true, false));
|
||||
modal.addButton(new ModalButton("apply", () -> {
|
||||
wrapper.getEntry().setNotes(prop.getValue());
|
||||
DataStorage.get().saveAsync();
|
||||
}, true, true));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
private final StoreEntryWrapper wrapper;
|
||||
|
||||
@@ -33,133 +46,30 @@ public class StoreNotesComp extends RegionStructureBuilder<Button, StoreNotesCom
|
||||
}
|
||||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
protected Button createSimple() {
|
||||
var n = wrapper.getNotes();
|
||||
var button = new IconButtonComp("mdi2n-note-text-outline")
|
||||
.apply(struc -> AppFontSizes.xs(struc))
|
||||
.describe(d ->
|
||||
d.nameKey("notes").focusTraversal(RegionDescriptor.FocusTraversal.ENABLED_FOR_ACCESSIBILITY))
|
||||
.style("notes-button")
|
||||
.hide(BindingsHelper.map(n, s -> s.getCommited() == null && s.getCurrent() == null))
|
||||
.hide(n.isNull())
|
||||
.build();
|
||||
button.setOpacity(0.85);
|
||||
button.prefWidthProperty().bind(button.heightProperty());
|
||||
|
||||
var prop = new SimpleStringProperty(n.getValue().getCurrent());
|
||||
|
||||
var popover = new AtomicReference<Popover>();
|
||||
button.setOnAction(e -> {
|
||||
if (n.getValue().getCurrent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (popover.get() != null && popover.get().isShowing()) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
popover.set(createPopover(popover, prop));
|
||||
popover.get().show(button);
|
||||
showDialog(wrapper, wrapper.getNotes().getValue());
|
||||
e.consume();
|
||||
});
|
||||
prop.addListener((observable, oldValue, newValue) -> {
|
||||
n.setValue(new StoreNotes(n.getValue().getCommited(), newValue));
|
||||
});
|
||||
n.addListener((observable, oldValue, s) -> {
|
||||
prop.set(s.getCurrent());
|
||||
// Check for scene existence. If we exited the platform immediately after adding notes, this might throw an
|
||||
// exception
|
||||
if (s.getCurrent() != null
|
||||
&& oldValue.getCommited() == null
|
||||
&& oldValue.isCommited()
|
||||
&& button.getScene() != null) {
|
||||
Platform.runLater(() -> {
|
||||
popover.set(createPopover(popover, prop));
|
||||
popover.get().show(button);
|
||||
});
|
||||
|
||||
var editKey = UUID.randomUUID().toString();
|
||||
button.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.isShiftDown()) {
|
||||
FileOpener.openString("notes.md", editKey, wrapper.getNotes().getValue(), s -> wrapper.getEntry().setNotes(s));
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
return new Structure(popover.get(), button);
|
||||
}
|
||||
|
||||
private Popover createPopover(AtomicReference<Popover> ref, Property<String> prop) {
|
||||
var n = wrapper.getNotes();
|
||||
var md = new MarkdownEditorComp(prop, "notes-" + wrapper.getName().getValue())
|
||||
.prefWidth(600)
|
||||
.prefHeight(600)
|
||||
.buildStructure();
|
||||
var dialog = new DialogComp() {
|
||||
|
||||
@Override
|
||||
protected String finishKey() {
|
||||
return "apply";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractRegionBuilder<?, ?>> customButtons() {
|
||||
return List.of(new ButtonComp(AppI18n.observable("cancel"), () -> {
|
||||
ref.get().hide();
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish() {
|
||||
n.setValue(
|
||||
new StoreNotes(n.getValue().getCurrent(), n.getValue().getCurrent()));
|
||||
ref.get().hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseRegionBuilder<?, ?> content() {
|
||||
return RegionBuilder.of(() -> md.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseRegionBuilder<?, ?> bottom() {
|
||||
return new ButtonComp(AppI18n.observable("delete"), () -> {
|
||||
n.setValue(new StoreNotes(null, null));
|
||||
})
|
||||
.hide(BindingsHelper.map(n, v -> v.getCommited() == null));
|
||||
}
|
||||
}.build();
|
||||
|
||||
var popover = new Popover(dialog);
|
||||
popover.setAutoHide(!AppPrefs.get().limitedTouchscreenMode().get());
|
||||
popover.getScene().setFill(Color.TRANSPARENT);
|
||||
popover.setCloseButtonEnabled(true);
|
||||
popover.setHeaderAlwaysVisible(true);
|
||||
popover.setDetachable(true);
|
||||
popover.setTitle(wrapper.getName().getValue());
|
||||
popover.showingProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue) {
|
||||
n.setValue(
|
||||
new StoreNotes(n.getValue().getCommited(), n.getValue().getCommited()));
|
||||
DataStorage.get().saveAsync();
|
||||
ref.set(null);
|
||||
}
|
||||
});
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
|
||||
md.getEditButton().addEventFilter(ActionEvent.ANY, event -> {
|
||||
if (!popover.isDetached()) {
|
||||
popover.setDetached(true);
|
||||
event.consume();
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
md.getEditButton().fire();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return popover;
|
||||
}
|
||||
|
||||
public record Structure(Popover popover, Button button) implements RegionStructure<Button> {
|
||||
|
||||
@Override
|
||||
public Button get() {
|
||||
return button;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "The full path of the system"
|
||||
},
|
||||
"information": {
|
||||
"type": "string",
|
||||
"description": "Summary of the known system information"
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "User-supplied notes for the individual system"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
-fx-border-color: -color-neutral-emphasis;
|
||||
}
|
||||
|
||||
.edit-button.icon-button-comp:hover, .root:key-navigation .edit-button.icon-button-comp:focused {
|
||||
-fx-background-color: -color-accent-muted;
|
||||
}
|
||||
|
||||
.scan-list {
|
||||
-fx-background-radius: 4px;
|
||||
-fx-border-width: 1;
|
||||
|
||||
Reference in New Issue
Block a user