From a199b114c7b844c6d338ca3c651e7e9d50e312b2 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 16 May 2023 06:29:18 +0000 Subject: [PATCH] More file browser improvements --- .../io/xpipe/app/browser/BrowserNavBar.java | 1 + .../app/browser/FileBrowserBreadcrumbBar.java | 23 ++++++++-- .../io/xpipe/app/browser/FileBrowserComp.java | 13 +++++- .../io/xpipe/app/browser/FileFilterComp.java | 7 ++- .../xpipe/app/browser/OpenFileSystemComp.java | 7 +-- .../app/browser/OpenFileSystemModel.java | 11 ++++- ...OverlayComp.java => ModalOverlayComp.java} | 12 ++--- .../io/xpipe/app/core/mode/OperationMode.java | 2 + .../io/xpipe/app/resources/style/browser.css | 4 ++ .../java/io/xpipe/core/impl/FileNames.java | 2 +- .../java/io/xpipe/core/process/OsType.java | 10 +++++ .../io/xpipe/core/process/ShellControl.java | 10 ++++- .../io/xpipe/core/util/XPipeSystemId.java | 44 +++++++++++++++++++ .../xpipe/ext/base/browser/NewItemAction.java | 6 +-- 14 files changed, 126 insertions(+), 26 deletions(-) rename app/src/main/java/io/xpipe/app/comp/base/{AlertOverlayComp.java => ModalOverlayComp.java} (91%) create mode 100644 core/src/main/java/io/xpipe/core/util/XPipeSystemId.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index da210dd4..3fb86d9e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -42,6 +42,7 @@ public class BrowserNavBar extends SimpleComp { .createRegion(); var stack = new StackPane(pathBar, breadcrumbs); + breadcrumbs.prefHeightProperty().bind(pathBar.heightProperty()); HBox.setHgrow(stack, Priority.ALWAYS); stack.setAlignment(Pos.CENTER_LEFT); diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserBreadcrumbBar.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserBreadcrumbBar.java index 83e9cba4..45292f1d 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserBreadcrumbBar.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserBreadcrumbBar.java @@ -12,6 +12,8 @@ import javafx.scene.control.Label; import javafx.scene.layout.Region; import javafx.util.Callback; +import java.util.ArrayList; + public class FileBrowserBreadcrumbBar extends SimpleComp { private final OpenFileSystemModel model; @@ -23,7 +25,8 @@ public class FileBrowserBreadcrumbBar extends SimpleComp { @Override protected Region createSimple() { Callback, ButtonBase> crumbFactory = crumb -> { - var btn = new Button(FileNames.getFileName(crumb.getValue()), null); + var btn = new Button(crumb.getValue().equals("/") ? "/" : FileNames.getFileName(crumb.getValue()), null); + btn.setMnemonicParsing(false); btn.setFocusTraversable(false); return btn; }; @@ -45,11 +48,25 @@ public class FileBrowserBreadcrumbBar extends SimpleComp { if (sc.isEmpty()) { breadcrumbs.setDividerFactory(item -> item != null && !item.isLast() ? new Label("/") : null); } else { - breadcrumbs.setDividerFactory(item -> item != null && !item.isLast() ? new Label(sc.get().getOsType().getFileSystemSeparator()) : null); + breadcrumbs.setDividerFactory(item -> { + if (item == null) { + return null; + } + + if (item.isFirst() && item.getValue().equals("/")) { + return new Label(""); + } + + return !item.isLast() ? new Label(sc.get().getOsType().getFileSystemSeparator()) : null; + }); } var elements = FileNames.splitHierarchy(val); - Breadcrumbs.BreadCrumbItem items = Breadcrumbs.buildTreeModel(elements.toArray(String[]::new)); + var modifiedElements = new ArrayList<>(elements); + if (val.startsWith("/")) { + modifiedElements.add(0, "/"); + } + Breadcrumbs.BreadCrumbItem items = Breadcrumbs.buildTreeModel(modifiedElements.toArray(String[]::new)); breadcrumbs.setSelectedCrumb(items); }); diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java index cf88573f..2fce55cd 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java @@ -11,6 +11,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.impl.PrettyImageComp; import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.fxcomps.util.SimpleChangeListener; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.ThreadHelper; @@ -58,6 +59,14 @@ public class FileBrowserComp extends SimpleComp { return !model.getMode().equals(FileBrowserModel.Mode.BROWSER); }, PlatformThread.sync(model.getOpenFileSystems()))).createRegion(); + SimpleChangeListener.apply(model.getSelected(), val -> { + localDownloadStage.visibleProperty().unbind(); + if (val == null) { + return; + } + + localDownloadStage.visibleProperty().bind(PlatformThread.sync(val.getLocal().not())); + }); var vertical = new VBox(bookmarksList, localDownloadStage); vertical.setFillWidth(true); @@ -68,7 +77,9 @@ public class FileBrowserComp extends SimpleComp { // set sidebar width in pixels depending on split pane width (obs, old, val) -> splitPane.setDividerPosition(0, 230 / splitPane.getWidth())); - return addBottomBar(splitPane); + var r = addBottomBar(splitPane); + // AppFont.small(r); + return r; } private Region addBottomBar(Region r) { diff --git a/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java index c7e6b8d7..8a488c0c 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java @@ -3,12 +3,11 @@ package io.xpipe.app.browser; import atlantafx.base.theme.Styles; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; -import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.impl.TextFieldComp; import io.xpipe.app.fxcomps.util.SimpleChangeListener; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.layout.HBox; @@ -47,7 +46,6 @@ public class FileFilterComp extends Comp { }); var fi = new FontIcon("mdi2m-magnify"); - GrowAugment.create(false, true).augment(new SimpleCompStructure<>(button)); button.setGraphic(fi); button.setOnAction(event -> { if (expanded.get()) { @@ -75,9 +73,10 @@ public class FileFilterComp extends Comp { button.getStyleClass().add(Styles.FLAT); } }); + button.prefHeightProperty().bind(text.heightProperty()); var box = new HBox(text, button); - box.setFillHeight(true); + box.setAlignment(Pos.CENTER); return new Structure(box, (TextField) text, button); } diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java index b6261186..749af2a2 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java @@ -3,13 +3,11 @@ package io.xpipe.app.browser; import atlantafx.base.controls.Spacer; import io.xpipe.app.browser.action.BranchAction; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.comp.base.AlertOverlayComp; +import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.TextFieldComp; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.Shortcuts; -import javafx.beans.property.SimpleStringProperty; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.MenuButton; @@ -17,7 +15,6 @@ import javafx.scene.control.ToolBar; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; @@ -39,7 +36,7 @@ public class OpenFileSystemComp extends SimpleComp { @Override protected Region createSimple() { - var alertOverlay = new AlertOverlayComp( + var alertOverlay = new ModalOverlayComp( Comp.of(() -> createContent()), model.getOverlay()); return alertOverlay.createRegion(); diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index 60560347..5f66744d 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -2,7 +2,7 @@ package io.xpipe.app.browser; -import io.xpipe.app.comp.base.AlertOverlayComp; +import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.core.AppCache; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.storage.DataStorage; @@ -44,7 +44,8 @@ public final class OpenFileSystemModel { private final BooleanProperty noDirectory = new SimpleBooleanProperty(); private final Property savedState = new SimpleObjectProperty<>(); private final OpenFileSystemCache cache = new OpenFileSystemCache(this); - private final Property overlay = new SimpleObjectProperty<>(); + private final Property overlay = new SimpleObjectProperty<>(); + private final BooleanProperty local = new SimpleBooleanProperty(); public OpenFileSystemModel(FileBrowserModel browserModel) { this.browserModel = browserModel; @@ -122,6 +123,11 @@ public final class OpenFileSystemModel { return Optional.empty(); } + // Handle commands typed into navigation bar + if (!FileNames.isAbsolute(path)) { + + } + String newPath = null; try { newPath = FileSystemHelper.resolveDirectoryPath(this, path); @@ -300,6 +306,7 @@ public final class OpenFileSystemModel { var fs = fileSystem.createFileSystem(); fs.open(); this.fileSystem = fs; + this.local.set(fs.getShell().map(shellControl -> shellControl.isLocal()).orElse(false)); var storageEntry = DataStorage.get() .getStoreEntryIfPresent(fileSystem) diff --git a/app/src/main/java/io/xpipe/app/comp/base/AlertOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java similarity index 91% rename from app/src/main/java/io/xpipe/app/comp/base/AlertOverlayComp.java rename to app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java index 65217559..6ccb3838 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/AlertOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java @@ -1,5 +1,6 @@ package io.xpipe.app.comp.base; +import atlantafx.base.controls.ModalPane; import atlantafx.base.theme.Styles; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; @@ -21,10 +22,10 @@ import javafx.scene.layout.VBox; import lombok.Value; import org.kordamp.ikonli.javafx.FontIcon; -public class AlertOverlayComp extends SimpleComp { +public class ModalOverlayComp extends SimpleComp { - public AlertOverlayComp(Comp background, Property overlayContent) { + public ModalOverlayComp(Comp background, Property overlayContent) { this.background = background; this.overlayContent = overlayContent; } @@ -43,11 +44,12 @@ public class AlertOverlayComp extends SimpleComp { @Override protected Region createSimple() { var bgRegion = background.createRegion(); - var pane = new StackPane(bgRegion); + var modal = new ModalPane(); + var pane = new StackPane(bgRegion, modal); pane.setPickOnBounds(false); PlatformThread.sync(overlayContent).addListener((observable, oldValue, newValue) -> { if (oldValue != null) { - pane.getChildren().remove(1); + modal.hide(true); } if (newValue != null) { @@ -91,7 +93,7 @@ public class AlertOverlayComp extends SimpleComp { close.maxWidthProperty().bind(tp.widthProperty()); close.maxHeightProperty().bind(tp.heightProperty()); - pane.getChildren().add(stack); + modal.show(stack); } }); return pane; diff --git a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java index bf101947..4e54bf84 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java @@ -8,6 +8,7 @@ import io.xpipe.app.launcher.LauncherCommand; import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.XPipeSession; import io.xpipe.core.util.XPipeDaemonMode; +import io.xpipe.core.util.XPipeSystemId; import org.apache.commons.lang3.function.FailableRunnable; import java.util.ArrayList; @@ -91,6 +92,7 @@ public abstract class OperationMode { AppProperties.logArguments(args); AppProperties.logSystemProperties(); AppProperties.logPassedProperties(); + XPipeSystemId.init(); TrackEvent.info("mode", "Finished initial setup"); } catch (Throwable ex) { ErrorEvent.fromThrowable(ex).term().handle(); diff --git a/app/src/main/resources/io/xpipe/app/resources/style/browser.css b/app/src/main/resources/io/xpipe/app/resources/style/browser.css index a363b3eb..b382eab8 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/browser.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/browser.css @@ -76,6 +76,10 @@ -fx-padding: 0; } +.browser .breadcrumbs { +-fx-padding: 2px 10px 2px 10px; +} + .browser .context-menu .separator .line { -fx-padding: 0; -fx-border-insets: 0px; diff --git a/core/src/main/java/io/xpipe/core/impl/FileNames.java b/core/src/main/java/io/xpipe/core/impl/FileNames.java index b28b8f1c..6d020d3e 100644 --- a/core/src/main/java/io/xpipe/core/impl/FileNames.java +++ b/core/src/main/java/io/xpipe/core/impl/FileNames.java @@ -106,7 +106,7 @@ public class FileNames { return false; } - if (!file.startsWith("/") && !file.startsWith("~") && !file.matches("^\\w:.*")) { + if (!file.startsWith("\\") && !file.startsWith("/") && !file.startsWith("~") && !file.matches("^\\w:.*")) { return false; } diff --git a/core/src/main/java/io/xpipe/core/process/OsType.java b/core/src/main/java/io/xpipe/core/process/OsType.java index a911e897..206f519f 100644 --- a/core/src/main/java/io/xpipe/core/process/OsType.java +++ b/core/src/main/java/io/xpipe/core/process/OsType.java @@ -1,5 +1,7 @@ package io.xpipe.core.process; +import io.xpipe.core.impl.FileNames; + import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -23,6 +25,14 @@ public sealed interface OsType permits OsType.Windows, OsType.Linux, OsType.MacO } } + default String getXPipeHomeDirectory(ShellControl pc) throws Exception { + return FileNames.join(getHomeDirectory(pc), ".xpipe"); + } + + default String getSystemIdFile(ShellControl pc) throws Exception { + return FileNames.join(getXPipeHomeDirectory(pc), "system_id"); + } + String getHomeDirectory(ShellControl pc) throws Exception; String getFileSystemSeparator(); diff --git a/core/src/main/java/io/xpipe/core/process/ShellControl.java b/core/src/main/java/io/xpipe/core/process/ShellControl.java index ffa0f91b..ea6f108d 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellControl.java @@ -2,17 +2,25 @@ package io.xpipe.core.process; import io.xpipe.core.util.FailableFunction; import io.xpipe.core.util.SecretValue; +import io.xpipe.core.util.XPipeSystemId; import lombok.NonNull; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.UUID; import java.util.concurrent.Semaphore; import java.util.function.Consumer; import java.util.function.Function; public interface ShellControl extends ProcessControl { + default boolean isLocal() { + return getSystemId().equals(XPipeSystemId.getLocal()); + } + + UUID getSystemId(); + Semaphore getCommandLock(); ShellControl onInit(Consumer pc); @@ -63,8 +71,6 @@ public interface ShellControl extends ProcessControl { void restart() throws Exception; - boolean isLocal(); - OsType getOsType(); ShellControl elevated(FailableFunction elevationFunction); diff --git a/core/src/main/java/io/xpipe/core/util/XPipeSystemId.java b/core/src/main/java/io/xpipe/core/util/XPipeSystemId.java new file mode 100644 index 00000000..56ce5d54 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/XPipeSystemId.java @@ -0,0 +1,44 @@ +package io.xpipe.core.util; + +import io.xpipe.core.impl.FileNames; +import io.xpipe.core.process.ShellControl; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class XPipeSystemId { + + private static UUID localId; + + public static void init() { + try { + var file = Path.of(System.getProperty("user.home")).resolve(".xpipe").resolve("system_id"); + if (!Files.exists(file)) { + Files.writeString(file, UUID.randomUUID().toString()); + } + localId = UUID.fromString(Files.readString(file).trim()); + } catch (Exception ex) { + localId = UUID.randomUUID(); + } + } + + public static UUID getLocal() { + return localId; + } + + public static UUID getSystemId(ShellControl proc) throws Exception { + var file = proc.getOsType().getSystemIdFile(proc); + + if (!proc.getShellDialect().createFileExistsCommand(proc, file).executeAndCheck()) { + proc.executeSimpleCommand( + proc.getShellDialect().getMkdirsCommand(FileNames.getParent(file)), + "Unable to access or create directory " + file); + var id = UUID.randomUUID(); + proc.getShellDialect().createTextFileWriteCommand(proc, id.toString(), file).execute(); + return id; + } + + return UUID.fromString(proc.executeSimpleStringCommand(proc.getShellDialect().getFileReadCommand(file)).trim()); + } +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java index 31f10e56..08403240 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java @@ -6,7 +6,7 @@ import io.xpipe.app.browser.action.BranchAction; import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.browser.action.LeafAction; import io.xpipe.app.browser.icon.FileBrowserIcons; -import io.xpipe.app.comp.base.AlertOverlayComp; +import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.Comp; import javafx.beans.property.SimpleStringProperty; @@ -55,7 +55,7 @@ public class NewItemAction implements BrowserAction, BranchAction { @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { var name = new SimpleStringProperty(); - model.getOverlay().setValue(new AlertOverlayComp.OverlayContent(AppI18n.observable("newFile"), Comp.of(() -> { + model.getOverlay().setValue(new ModalOverlayComp.OverlayContent(AppI18n.observable("newFile"), Comp.of(() -> { var creationName = new TextField(); creationName.textProperty().bindBidirectional(name); return creationName; @@ -78,7 +78,7 @@ public class NewItemAction implements BrowserAction, BranchAction { @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { var name = new SimpleStringProperty(); - model.getOverlay().setValue(new AlertOverlayComp.OverlayContent(AppI18n.observable("newDirectory"), Comp.of(() -> { + model.getOverlay().setValue(new ModalOverlayComp.OverlayContent(AppI18n.observable("newDirectory"), Comp.of(() -> { var creationName = new TextField(); creationName.textProperty().bindBidirectional(name); return creationName;