diff --git a/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java index 38b2160b8..7dc6b7165 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java @@ -95,7 +95,16 @@ public class DenseStoreEntryComp extends StoreEntryComp { nameCC.setMinWidth(100); nameCC.setHgrow(Priority.ALWAYS); grid.getColumnConstraints().addAll(nameCC); + + var active = new StoreActiveComp(getWrapper()).createRegion(); var nameBox = new HBox(name, notes); + getWrapper().getSessionActive().subscribe(aBoolean -> { + if (!aBoolean) { + nameBox.getChildren().remove(active); + } else { + nameBox.getChildren().add(1, active); + } + }); nameBox.setSpacing(6); nameBox.setAlignment(Pos.CENTER_LEFT); grid.addRow(0, nameBox); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java index 4d2914f23..6b67d4d40 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java @@ -41,11 +41,19 @@ public class StandardStoreEntryComp extends StoreEntryComp { grid.add(storeIcon, 0, 0, 1, 2); grid.getColumnConstraints().add(new ColumnConstraints(56)); - var nameAndNotes = new HBox(name, notes); - nameAndNotes.setSpacing(6); - nameAndNotes.setAlignment(Pos.CENTER_LEFT); - grid.add(nameAndNotes, 1, 0); - GridPane.setVgrow(nameAndNotes, Priority.ALWAYS); + var active = new StoreActiveComp(getWrapper()).createRegion(); + var nameBox = new HBox(name, notes); + nameBox.setSpacing(6); + nameBox.setAlignment(Pos.CENTER_LEFT); + grid.add(nameBox, 1, 0); + GridPane.setVgrow(nameBox, Priority.ALWAYS); + getWrapper().getSessionActive().subscribe(aBoolean -> { + if (!aBoolean) { + nameBox.getChildren().remove(active); + } else { + nameBox.getChildren().add(1, active); + } + }); var summaryBox = new HBox(createSummary()); summaryBox.setAlignment(Pos.TOP_LEFT); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreActiveComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreActiveComp.java new file mode 100644 index 000000000..bee5b36e6 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreActiveComp.java @@ -0,0 +1,36 @@ +package io.xpipe.app.comp.store; + +import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.impl.TooltipAugment; +import javafx.beans.value.ObservableBooleanValue; +import javafx.geometry.Pos; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; + +public class StoreActiveComp extends SimpleComp { + + private final StoreEntryWrapper wrapper; + + public StoreActiveComp(StoreEntryWrapper wrapper) {this.wrapper = wrapper;} + + @Override + protected Region createSimple() { + var c = new Circle(6); + c.getStyleClass().add("dot"); + c.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { + if (event.getButton() == MouseButton.PRIMARY) { + wrapper.stopSession(); + event.consume(); + } + }); + var pane = new StackPane(c); + pane.setAlignment(Pos.CENTER); + pane.visibleProperty().bind(wrapper.getSessionActive()); + pane.getStyleClass().add("store-active-comp"); + new TooltipAugment<>("sessionActive", null).augment(pane); + return pane; + } +} diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java index 4e43f2087..ff4e6ec93 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java @@ -10,6 +10,7 @@ import io.xpipe.app.storage.DataStoreCategory; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.store.SingletonSessionStore; import javafx.beans.property.*; import javafx.collections.FXCollections; @@ -44,6 +45,7 @@ public class StoreEntryWrapper { private final Property notes; private final Property customIcon = new SimpleObjectProperty<>(); private final Property iconFile = new SimpleObjectProperty<>(); + private final BooleanProperty sessionActive = new SimpleBooleanProperty(); public StoreEntryWrapper(DataStoreEntry entry) { this.entry = entry; @@ -118,7 +120,15 @@ public class StoreEntryWrapper { }); } - public void update() { + public void stopSession() { + ThreadHelper.runFailableAsync(() -> { + if (entry.getStore() instanceof SingletonSessionStore singletonSessionStore) { + singletonSessionStore.stopSessionIfNeeded(); + } + }); + } + + public synchronized void update() { // We are probably in shutdown then if (StoreViewState.get() == null) { return; @@ -147,6 +157,7 @@ public class StoreEntryWrapper { busy.setValue(entry.getBusyCounter().get() != 0); deletable.setValue(entry.getConfiguration().isDeletable() || AppPrefs.get().developerDisableGuiRestrictions().getValue()); + sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore ss && ss.isSessionRunning()); category.setValue(StoreViewState.get() .getCategoryWrapper(DataStorage.get() diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index ae8e1e256..ac65553af 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -148,3 +148,19 @@ -fx-padding: 15 0; -fx-cursor: hand; } + +.root:pretty:light .store-active-comp .dot { + -fx-fill: radial-gradient(radius 180%, rgb(30, 180, 30, 0.6), rgb(20, 120, 20, 0.65), rgb(37, 200, 37, 0.6)); +} + +.root:performance:light .store-active-comp .dot { + -fx-fill: rgb(30, 180, 30, 0.6); +} + +.root:pretty:dark .store-active-comp .dot { + -fx-fill: radial-gradient(radius 180%, rgb(30, 180, 30, 0.8), rgb(20, 120, 20, 0.85), rgb(37, 200, 37, 0.8)); +} + +.root:performance:dark .store-active-comp .dot { + -fx-fill: rgb(30, 180, 30, 0.7); +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java index 9fb01bf11..225067ae1 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java @@ -19,7 +19,10 @@ import io.xpipe.core.process.ShellStoreState; import io.xpipe.app.ext.ShellStore; import io.xpipe.ext.base.script.ScriptStore; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; public interface ShellStoreProvider extends DataStoreProvider { diff --git a/lang/app/strings/translations_en.properties b/lang/app/strings/translations_en.properties index 1bfdfa46a..2ec568a80 100644 --- a/lang/app/strings/translations_en.properties +++ b/lang/app/strings/translations_en.properties @@ -538,3 +538,4 @@ terminalLoggingDirectory=Terminal session logs terminalLoggingDirectoryDescription=All logs are stored in the XPipe data directory on your local system. openSessionLogs=Open session logs sessionLogging=Session logging +sessionActive=A background session is running for this connection.\n\nTo stop this session manually, click on the status indicator.