diff --git a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java index 700f99060..d8080897a 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java @@ -31,6 +31,7 @@ public CompStructure createBase() { var loadingBg = new StackPane(loading); loadingBg.getStyleClass().add("loading-comp"); + loadingBg.getStyleClass().add("modal-pane"); loadingBg.setVisible(showLoading.getValue()); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java index 917d91b1d..a1319e3ae 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java @@ -2,7 +2,6 @@ import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.MultiContentComp; -import io.xpipe.app.core.AppState; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.HorizontalComp; @@ -33,11 +32,10 @@ private Comp createList() { @Override protected Region createSimple() { - var initialCount = StoreViewState.get().getAllEntries().size(); + var initialCount = 1; var showIntro = Bindings.createBooleanBinding( () -> { - return initialCount == StoreViewState.get().getAllEntries().size() - && AppState.get().isInitialLaunch(); + return initialCount == StoreViewState.get().getAllEntries().size(); }, StoreViewState.get().getAllEntries()); var map = new LinkedHashMap, ObservableBooleanValue>(); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java index 2c8a2c04e..2262b8e53 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java @@ -206,7 +206,7 @@ public void toggleExpanded() { @Override public boolean shouldShow(String filter) { - return getName().toLowerCase().contains(filter.toLowerCase()) + return filter == null || getName().toLowerCase().contains(filter.toLowerCase()) || (summary.get() != null && summary.get().toLowerCase().contains(filter.toLowerCase())) || (information.get() != null && information.get().toLowerCase().contains(filter.toLowerCase())); } diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java index 13f1b8637..e5154e941 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java @@ -7,7 +7,6 @@ import io.xpipe.app.util.Hyperlinks; import io.xpipe.app.util.ScanAlert; import io.xpipe.core.impl.LocalStore; -import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -29,7 +28,7 @@ public Region createSimple() { var introDesc = new Label(AppI18n.get("storeIntroDescription")); - var mfi = new FontIcon("mdi2m-magnify"); + var mfi = new FontIcon("mdi2p-playlist-plus"); var machine = new Label(AppI18n.get("storeMachineDescription"), mfi); machine.heightProperty().addListener((c, o, n) -> { mfi.iconSizeProperty().set(n.intValue()); @@ -67,9 +66,8 @@ title, introDesc, new Separator(Orientation.HORIZONTAL), machine, scanPane v.getStyleClass().add("intro"); var sp = new StackPane(v); - sp.setAlignment(Pos.BOTTOM_CENTER); + sp.setAlignment(Pos.CENTER); sp.setPickOnBounds(false); - sp.setPadding(new Insets(0, 0, 40, 0)); return sp; } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java index 0c17c8b33..2101564d5 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java @@ -123,7 +123,7 @@ public static void showCreation(Predicate filter) { e -> { try { DataStorage.get().addStoreEntry(e); - if (e.getProvider().shouldHaveSubShells()) { + if (e.getProvider().shouldHaveChildren()) { ScanAlert.showAsync(e); } } catch (Exception ex) { diff --git a/app/src/main/java/io/xpipe/app/core/AppActionLinkDetector.java b/app/src/main/java/io/xpipe/app/core/AppActionLinkDetector.java index db7bf0fb5..e6dea0380 100644 --- a/app/src/main/java/io/xpipe/app/core/AppActionLinkDetector.java +++ b/app/src/main/java/io/xpipe/app/core/AppActionLinkDetector.java @@ -20,7 +20,7 @@ private static String getClipboardAction() { return content != null ? content.toString() : null; } - private static void handle(String content, boolean showAlert) { + public static void handle(String content, boolean showAlert) { var detected = LauncherInput.of(content); if (detected.size() == 0) { return; diff --git a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java index 3fc6bf554..e421f6afd 100644 --- a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java @@ -46,7 +46,7 @@ default boolean canHaveSubShells() { return true; } - default boolean shouldHaveSubShells() { + default boolean shouldHaveChildren() { return canHaveSubShells(); } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/FilterComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/FilterComp.java index 93d0bf6a6..8160c67d8 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/FilterComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/FilterComp.java @@ -1,9 +1,10 @@ package io.xpipe.app.fxcomps.impl; +import io.xpipe.app.core.AppActionLinkDetector; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.SimpleChangeListener; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.Property; import javafx.scene.Node; @@ -14,6 +15,8 @@ import lombok.Value; import org.kordamp.ikonli.javafx.FontIcon; +import java.util.Objects; + public class FilterComp extends Comp { private final Property filterText; @@ -31,9 +34,20 @@ public Structure createBase() { filter.setAccessibleText("Filter"); SimpleChangeListener.apply(filterText, val -> { - PlatformThread.runLaterIfNeeded(() -> filter.setText(val)); + Platform.runLater(() -> { + if (!Objects.equals(filter.getText(), val)) { + filter.setText(val); + } + }); }); filter.textProperty().addListener((observable, oldValue, newValue) -> { + // Handle pasted xpipe URLs + if (newValue != null && newValue.startsWith("xpipe://")) { + AppActionLinkDetector.handle(newValue, false); + filter.setText(null); + return; + } + filterText.setValue(newValue); }); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java index ee1c40309..48e6851fb 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java @@ -139,7 +139,7 @@ public static ObservableList filteredContentBinding( ObservableList l2, ObservableValue> predicate) { ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { - setContent(l1, l2.stream().filter(predicate.getValue()).toList()); + setContent(l1, predicate.getValue() != null ? l2.stream().filter(predicate.getValue()).toList() : l2); }; runnable.run(); l2.addListener((ListChangeListener) c -> { diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index fb904559a..62b9eed98 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -19,6 +19,7 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.LockChangeAlert; import io.xpipe.app.util.LockedSecretValue; +import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.SecretValue; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -35,6 +36,10 @@ public class AppPrefs { + public boolean isDevelopmentEnvironment() { + return developerMode().getValue() && !ModuleHelper.isImage(); + } + private static ObservableBooleanValue bindDeveloperTrue(ObservableBooleanValue o) { return Bindings.createBooleanBinding( () -> { @@ -588,7 +593,7 @@ private AppPreferencesFx createPreferences() { developerShowHiddenProviders)), Category.of("troubleshoot", Group.of(troubleshoot)))); - categories.get(categories.size() - 1).setVisibilityProperty(VisibilityProperty.of(developerMode())); + categories.get(categories.size() - 2).setVisibilityProperty(VisibilityProperty.of(developerMode())); var handler = new PrefsHandlerImpl(categories); PrefsProvider.getAll().forEach(prov -> prov.addPrefs(handler)); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 8d5e6cbab..1e3b175e6 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -182,6 +182,7 @@ public void setConfiguration(Configuration configuration) { public void setExpanded(boolean expanded) { this.dirty = true; this.expanded = expanded; + listeners.forEach(l -> l.onUpdate()); } public DataStore getStore() { diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index a2df27096..5603400b3 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -94,9 +94,8 @@ public synchronized void load() { storeEntries.add(entry); } catch (Exception e) { - // We only keep invalid entries in developer mode as there's no point in keeping them in - // production. - if (AppPrefs.get().developerMode().getValue()) { + // We only keep invalid entries in developer mode as there's no point in keeping them in production. + if (AppPrefs.get().isDevelopmentEnvironment()) { directoriesToKeep.add(path); } ErrorEvent.fromThrowable(e).omitted(true).build().handle(); @@ -107,7 +106,7 @@ public synchronized void load() { storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh()); // Remove even incomplete stores when in production - if (!AppPrefs.get().developerMode().getValue()) { + if (!AppPrefs.get().isDevelopmentEnvironment()) { storeEntries.removeIf(entry -> { return !entry.getState().isUsable(); }); diff --git a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java index e9ea1ff0f..af82f39b2 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java @@ -11,8 +11,7 @@ public class DesktopShortcuts { private static void createWindowsShortcut(String target, String name) throws Exception { var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); - var shortcutTarget = XPipeInstallation.getCurrentInstallationBasePath() - .resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.WINDOWS)); + var shortcutTarget = XPipeInstallation.getLocalDefaultCliExecutable(); var content = String.format( """ set "TARGET=%s" @@ -26,8 +25,7 @@ private static void createWindowsShortcut(String target, String name) throws Exc } private static void createLinuxShortcut(String target, String name) throws Exception { - var exec = XPipeInstallation.getCurrentInstallationBasePath() - .resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.LINUX)); + var exec = XPipeInstallation.getLocalDefaultCliExecutable(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var content = String.format( """ @@ -47,8 +45,7 @@ private static void createLinuxShortcut(String target, String name) throws Excep } private static void createMacOSShortcut(String target, String name) throws Exception { - var exec = XPipeInstallation.getCurrentInstallationBasePath() - .resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.MACOS)); + var exec = XPipeInstallation.getLocalDefaultCliExecutable(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var base = System.getProperty("user.home") + "/Desktop/" + name + ".app"; var content = String.format( diff --git a/app/src/main/java/io/xpipe/app/util/ScanAlert.java b/app/src/main/java/io/xpipe/app/util/ScanAlert.java index 5c2e154e2..0d7b5cd1b 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanAlert.java +++ b/app/src/main/java/io/xpipe/app/util/ScanAlert.java @@ -40,7 +40,7 @@ public static void showAsync(DataStoreEntry entry) { } private static void showForOtherStore(DataStoreEntry entry) { - showIfNeeded(() -> { + show(entry, () -> { var providers = ScanProvider.getAll(); var applicable = providers.stream() .map(scanProvider -> scanProvider.create(entry.getStore())) @@ -51,7 +51,7 @@ private static void showForOtherStore(DataStoreEntry entry) { } private static void showForShellStore(DataStoreEntry entry) { - showIfNeeded(() -> { + show(entry, () -> { try (var sc = ((ShellStore) entry.getStore()).control().start()) { var providers = ScanProvider.getAll(); var applicable = new ArrayList(); @@ -69,7 +69,7 @@ private static void showForShellStore(DataStoreEntry entry) { }); } - private static void showIfNeeded(Supplier> applicable) { + private static void show(DataStoreEntry entry, Supplier> applicable) { var busy = new SimpleBooleanProperty(); var selected = new SimpleListProperty(FXCollections.observableArrayList()); AppWindowHelper.showAlert( @@ -104,6 +104,8 @@ private static void showIfNeeded(Supplier> appl } } + entry.setExpanded(true); + Platform.runLater(() -> { alert.setResult(ButtonType.OK); alert.close(); diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties index 09fc42d5f..f2b2eac3d 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties @@ -12,10 +12,10 @@ dataSourceIntroStructure=Structure data sources contain some form of object stru dataSourceIntroText=Text data sources contain readable text that can\ncome in a variety of different encodings and simple formats. dataSourceIntroBinary=Binary data sources contain binary data. They\ncan be used when the data should be handled and preserved byte by byte. dataSourceIntroCollection=Collection data sources contain multiple sub data sources. \nExamples are zip files or file system directories. -storeIntroTitle=Adding Connections -storeIntroDescription=Connect to remote systems, databases, and more. +storeIntroTitle=Shell Connection Hub +storeIntroDescription=Connect to shells, remote systems, databases, and more. storeStreamDescription=Stream connections produce raw byte data\nthat can be used to construct data sources from. -storeMachineDescription=You can quickly search for available remote connections automatically.\nAlternatively, you can also of course add them manually. +storeMachineDescription=To start off, here you can quickly detect available\nconnections automatically and choose which ones to add. detectConnections=Search for connections storeDatabaseDescription=Database connections allow you to connect to\na database server and interact with its contained data. storeDocumentation=In case you prefer a more structured approach to\nfamiliarizing yourself with XPipe, check out the documentation: 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 5be7609fb..ba669190c 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 @@ -43,6 +43,6 @@ } .loading-comp { --fx-background-color: #0002; + -fx-background-color: -color-modal-pane-overlay; } diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index 56c317163..5418b7647 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -177,6 +177,11 @@ public static String getDataBasePath(ShellControl p) throws Exception { } } + public static String getLocalDefaultCliExecutable() { + Path path = ModuleHelper.isImage() ? getCurrentInstallationBasePath() : Path.of(getLocalDefaultInstallationBasePath(true)); + return path.resolve(getRelativeCliExecutablePath(OsType.getLocal())).toString(); + } + public static Path getLocalDefaultInstallationIcon() { Path path = getCurrentInstallationBasePath(); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java index 5eaa6da9e..046b27797 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java @@ -5,7 +5,7 @@ import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.DesktopShortcuts; -import io.xpipe.core.store.ShellStore; +import io.xpipe.core.store.LaunchableStore; import javafx.beans.value.ObservableValue; import lombok.Value; @@ -29,30 +29,30 @@ public void execute() throws Exception { @Override public DataStoreCallSite getDataStoreCallSite() { - return new DataStoreCallSite() { + return new DataStoreCallSite() { @Override - public Action createAction(ShellStore store) { + public Action createAction(LaunchableStore store) { return new Action(DataStorage.get().getStoreEntry(store)); } @Override - public Class getApplicableClass() { - return ShellStore.class; + public Class getApplicableClass() { + return LaunchableStore.class; } @Override - public ObservableValue getName(ShellStore store) { + public ObservableValue getName(LaunchableStore store) { return AppI18n.observable("createShortcut"); } @Override - public String getIcon(ShellStore store) { + public String getIcon(LaunchableStore store) { return "mdi2c-code-greater-than"; } @Override - public boolean isMajor(ShellStore o) { + public boolean isMajor(LaunchableStore o) { return false; } }; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java index 404ec4cc7..18cd8afcc 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java @@ -34,7 +34,7 @@ public DataStoreCallSite getDataStoreCallSite() { @Override public boolean isMajor(ShellStore o) { - return DataStoreProviders.byStore(o).shouldHaveSubShells(); + return DataStoreProviders.byStore(o).shouldHaveChildren(); } @Override