From e0b34ff4803d9ff82447e83d5c9651abac887383 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 24 Feb 2023 22:16:27 +0000 Subject: [PATCH] More browser improvements --- .../io/xpipe/app/browser/FileFilterComp.java | 89 +++++++++++++++++++ .../io/xpipe/app/browser/FileListComp.java | 6 +- .../io/xpipe/app/browser/FileListModel.java | 13 ++- .../xpipe/app/browser/FileSystemHelper.java | 4 +- .../xpipe/app/browser/OpenFileSystemComp.java | 3 + .../app/browser/OpenFileSystemModel.java | 2 + .../io/xpipe/app/update/AppInstaller.java | 2 +- .../java/io/xpipe/core/impl/LocalStore.java | 48 +++++++--- .../io/xpipe/core/process/ShellDialect.java | 2 +- .../core/store/ConnectionFileSystem.java | 4 +- .../java/io/xpipe/core/store/FileSystem.java | 2 +- 11 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/FileFilterComp.java diff --git a/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java new file mode 100644 index 00000000..b386d9a3 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java @@ -0,0 +1,89 @@ +package io.xpipe.app.browser; + +import atlantafx.base.theme.Styles; +import io.xpipe.app.fxcomps.SimpleComp; +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.Shortcuts; +import io.xpipe.app.fxcomps.util.SimpleChangeListener; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.Button; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import org.kordamp.ikonli.javafx.FontIcon; + +public class FileFilterComp extends SimpleComp { + + private final Property filterString; + + public FileFilterComp(Property filterString) { + this.filterString = filterString; + } + + @Override + protected Region createSimple() { + var expanded = new SimpleBooleanProperty(); + var text = new TextFieldComp(filterString, false).createRegion(); + text.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue && filterString.getValue() == null) { + expanded.set(false); + return; + } + + if (newValue) { + expanded.set(true); + } + }); + filterString.addListener((observable, oldValue, newValue) -> { + if (newValue == null && !text.isFocused()) { + expanded.set(false); + } + }); + text.setMinWidth(0); + Styles.toggleStyleClass(text, Styles.LEFT_PILL); + + SimpleChangeListener.apply(filterString, val -> { + if (val == null) { + text.getStyleClass().remove(Styles.SUCCESS); + } else { + text.getStyleClass().add(Styles.SUCCESS); + } + }); + + var fi = new FontIcon("mdi2m-magnify"); + var button = new Button(); + GrowAugment.create(false, true).augment(new SimpleCompStructure<>(button)); + Shortcuts.addShortcut(button, new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)); + button.setGraphic(fi); + button.setOnAction(event -> { + if (expanded.get() && filterString.getValue() == null) { + expanded.set(false); + return; + } + + text.requestFocus(); + event.consume(); + }); + + SimpleChangeListener.apply(expanded, val -> { + if (val) { + text.setPrefWidth(250); + button.getStyleClass().add(Styles.RIGHT_PILL); + button.getStyleClass().remove(Styles.FLAT); + } else { + text.setPrefWidth(0); + button.getStyleClass().remove(Styles.RIGHT_PILL); + button.getStyleClass().add(Styles.FLAT); + } + }); + + var box = new HBox(text, button); + box.setFillHeight(true); + return box; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/FileListComp.java b/app/src/main/java/io/xpipe/app/browser/FileListComp.java index 9b2375ad..fbe3cf68 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListComp.java @@ -22,10 +22,7 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.input.*; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import java.io.File; import java.time.Instant; @@ -83,6 +80,7 @@ final class FileListComp extends AnchorPane { // ~ var table = new TableView(); + table.setPlaceholder(new Region()); table.getStyleClass().add(Styles.STRIPED); table.getColumns().setAll(filenameCol, sizeCol, mtimeCol); table.getSortOrder().add(filenameCol); diff --git a/app/src/main/java/io/xpipe/app/browser/FileListModel.java b/app/src/main/java/io/xpipe/app/browser/FileListModel.java index 5dd8e489..2dd495a1 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListModel.java @@ -14,6 +14,7 @@ import lombok.Getter; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.function.Predicate; @Getter @@ -34,6 +35,10 @@ final class FileListModel { public FileListModel(OpenFileSystemModel model) { this.model = model; + + model.getFilter().addListener((observable, oldValue, newValue) -> { + refreshShown(); + }); } public void setAll(List newFiles) { @@ -47,11 +52,17 @@ final class FileListModel { } private void refreshShown() { + List filtered = model.getFilter().getValue() != null ? all.getValue().stream().filter(entry -> { + var name = FileNames.getFileName(entry.getPath()).toLowerCase(Locale.ROOT); + var filterString = model.getFilter().getValue().toLowerCase(Locale.ROOT); + return name.contains(filterString); + }).toList() : all.getValue(); + Comparator tableComparator = comparatorProperty.getValue(); var comparator = tableComparator != null ? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) : FILE_TYPE_COMPARATOR; - var listCopy = new ArrayList<>(all.getValue()); + var listCopy = new ArrayList<>(filtered); listCopy.sort(comparator); shown.setValue(listCopy); } diff --git a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java index e6bf7d46..69a4e9d0 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java @@ -8,7 +8,7 @@ import io.xpipe.core.store.ShellStore; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; public class FileSystemHelper { @@ -108,7 +108,7 @@ public class FileSystemHelper { private static void dropFileAcrossFileSystems(FileSystem.FileEntry target, FileSystem.FileEntry source) throws Exception { - var flatFiles = new HashMap(); + var flatFiles = new LinkedHashMap(); // Prevent dropping directory into itself if (source.getFileSystem().equals(target.getFileSystem()) 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 011a169a..da949f76 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java @@ -57,12 +57,15 @@ public class OpenFileSystemComp extends SimpleComp { creatingProperty.set(true); }); + var filter = new FileFilterComp(model.getFilter()).createRegion(); + var topBar = new ToolBar(); topBar.getItems().setAll( backBtn, forthBtn, new Spacer(10), pathBar, + filter, refreshBtn, terminalBtn, addBtn 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 ca772e5c..3be32062 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -25,6 +25,7 @@ final class OpenFileSystemModel { private Property store = new SimpleObjectProperty<>(); private FileSystem fileSystem; + private final Property filter = new SimpleStringProperty(); private final FileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); private final FileBrowserNavigationHistory history = new FileBrowserNavigationHistory(); @@ -63,6 +64,7 @@ final class OpenFileSystemModel { return false; } + filter.setValue(null); currentPath.set(path); history.cd(path); return true; diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index 6e915247..2817b61d 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -39,7 +39,7 @@ public class AppInstaller { } else { targetFile = FileNames.join( s.getTemporaryDirectory(), localFile.getFileName().toString()); - try (CommandProcessControl c = s.command(s.getShellDialect().getStreamFileWriteCommand(targetFile)) + try (CommandProcessControl c = s.getShellDialect().getStreamFileWriteCommand(s, targetFile) .start()) { c.discardOut(); c.discardErr(); diff --git a/core/src/main/java/io/xpipe/core/impl/LocalStore.java b/core/src/main/java/io/xpipe/core/impl/LocalStore.java index 66fed896..8e537e1d 100644 --- a/core/src/main/java/io/xpipe/core/impl/LocalStore.java +++ b/core/src/main/java/io/xpipe/core/impl/LocalStore.java @@ -3,17 +3,22 @@ package io.xpipe.core.impl; import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ShellProcessControl; -import io.xpipe.core.store.*; +import io.xpipe.core.store.FileSystem; +import io.xpipe.core.store.FileSystemStore; +import io.xpipe.core.store.MachineStore; import io.xpipe.core.util.JacksonizedValue; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Optional; +import java.nio.file.StandardCopyOption; +import java.util.*; import java.util.stream.Stream; +import java.util.stream.StreamSupport; @JsonTypeName("local") public class LocalStore extends JacksonizedValue implements FileSystemStore, MachineStore { @@ -25,7 +30,6 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac @Override public FileSystem createFileSystem() { - if (true) return new ConnectionFileSystem(ShellStore.local().create()); return new FileSystem() { @Override public Optional getShell() { @@ -34,7 +38,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac @Override public FileSystem open() throws Exception { - return this; + return this; } @Override @@ -44,17 +48,17 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac @Override public void delete(String file) throws Exception { - + Files.delete(Path.of(file)); } @Override public void copy(String file, String newFile) throws Exception { - + Files.copy(Path.of(file), Path.of(newFile), StandardCopyOption.REPLACE_EXISTING); } @Override public void move(String file, String newFile) throws Exception { - + Files.move(Path.of(file), Path.of(newFile), StandardCopyOption.REPLACE_EXISTING); } @Override @@ -69,6 +73,11 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac @Override public void touch(String file) throws Exception { + if (exists(file)) { + return; + } + + Files.createFile(Path.of(file)); } @Override @@ -78,12 +87,27 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac @Override public Stream listFiles(String file) throws Exception { - return null; + return Files.list(Path.of(file)).map(path -> { + try { + var date = Files.getLastModifiedTime(path); + var size = Files.isDirectory(path) ? 0 : Files.size(path); + return new FileEntry( + this, + path.toString(), + date.toInstant(), + Files.isDirectory(path), + Files.isHidden(path), + Files.isExecutable(path), + size); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } @Override public List listRoots() throws Exception { - return null; + return StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false).map(path -> path.toString()).toList(); } @Override @@ -99,9 +123,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac } @Override - public void close() throws IOException { - - } + public void close() throws IOException {} }; } diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java index 9c5b0127..5331377f 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java @@ -93,7 +93,7 @@ public interface ShellDialect { String getFileMoveCommand(String oldFile, String newFile); - String getStreamFileWriteCommand(String file); + CommandProcessControl getStreamFileWriteCommand(ShellProcessControl processControl, String file); String getTextFileWriteCommand(String content, String file); diff --git a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java index 463b4884..07b7f728 100644 --- a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java @@ -54,8 +54,8 @@ public class ConnectionFileSystem implements FileSystem { @Override public OutputStream openOutput(String file) throws Exception { - return shellProcessControl.command(proc -> proc.getShellDialect() - .getStreamFileWriteCommand(proc.getOsType().normalizeFileName(file))) + return shellProcessControl.getShellDialect() + .getStreamFileWriteCommand(shellProcessControl, shellProcessControl.getOsType().normalizeFileName(file)) .startExternalStdin(); } diff --git a/core/src/main/java/io/xpipe/core/store/FileSystem.java b/core/src/main/java/io/xpipe/core/store/FileSystem.java index 1420a563..31b61235 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -73,7 +73,7 @@ public interface FileSystem extends Closeable, AutoCloseable { } try { - return listFilesRecursively(fileEntry.getPath()); + return Stream.concat(Stream.of(fileEntry), listFilesRecursively(fileEntry.getPath())); } catch (Exception e) { throw new RuntimeException(e); }