From 8d9b4c9d4e8450f8142605eb475e7b3cf2671fbf Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 25 May 2023 22:29:24 +0000 Subject: [PATCH] Browser improvements --- .../app/browser/BrowserBookmarkList.java | 8 ++-- .../xpipe/app/browser/BrowserContextMenu.java | 4 +- .../app/browser/BrowserFileListComp.java | 10 ++++ .../app/browser/BrowserFileListCompEntry.java | 4 +- .../app/browser/BrowserFileListModel.java | 7 --- .../io/xpipe/app/browser/BrowserModel.java | 16 +++++-- .../app/browser/BrowserOverviewComp.java | 35 ++++++++++++++ .../app/browser/BrowserStatusBarComp.java | 31 +++++++++++- .../xpipe/app/browser/OpenFileSystemComp.java | 47 ++++++++++++++----- .../io/xpipe/app/comp/base/BigIconButton.java | 7 ++- .../xpipe/app/comp/base/SideMenuBarComp.java | 9 ++++ .../app/comp/base/SimpleTitledPaneComp.java | 28 +++++++++++ .../fxcomps/augment/ContextMenuAugment.java | 2 +- .../java/io/xpipe/app/issue/ErrorAction.java | 22 +++++++++ .../io/xpipe/app/issue/ErrorHandlerComp.java | 45 ++++++++++++++---- .../io/xpipe/app/launcher/LauncherInput.java | 2 +- .../resources/lang/translations_en.properties | 2 + .../io/xpipe/app/resources/style/browser.css | 15 +++--- .../resources/style/error-handler-comp.css | 8 ++++ .../app/resources/style/sidebar-comp.css | 22 ++++----- .../java/io/xpipe/core/process/OsType.java | 21 +++++++++ .../browser/OpenDirectoryInNewTabAction.java | 2 +- 22 files changed, 276 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java create mode 100644 app/src/main/java/io/xpipe/app/comp/base/SimpleTitledPaneComp.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java index ce8585f9..16b5498d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java @@ -9,9 +9,7 @@ import io.xpipe.app.fxcomps.impl.PrettyImageComp; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.ShellStore; import javafx.application.Platform; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.*; import javafx.collections.SetChangeListener; import javafx.css.PseudoClass; import javafx.geometry.Point2D; @@ -93,8 +91,10 @@ final class BrowserBookmarkList extends SimpleComp { private final StringProperty img = new SimpleStringProperty(); private final Node imageView = new PrettyImageComp(img, 20, 20).createRegion(); + private final BooleanProperty busy = new SimpleBooleanProperty(false); private StoreCell() { + disableProperty().bind(busy); setGraphic(imageView); addEventHandler(DragEvent.DRAG_OVER, mouseEvent -> { if (getItem() == null) { @@ -114,7 +114,7 @@ final class BrowserBookmarkList extends SimpleComp { } var fileSystem = ((ShellStore) getItem().getEntry().getStore()); - model.openFileSystemAsync(null, fileSystem, null); + model.openFileSystemAsync(null, fileSystem, null, busy); event.consume(); }); var icon = new SimpleObjectProperty("mdal-keyboard_arrow_right"); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java index 756c70e5..2e5da48f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java @@ -26,11 +26,9 @@ final class BrowserContextMenu extends ContextMenu { AppFont.normal(this.getStyleableNode()); var empty = source == null; - var selected = new ArrayList<>(empty ? List.of() : model.getFileList().getSelection()); + var selected = new ArrayList<>(empty ? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false)) : model.getFileList().getSelection()); if (source != null && !selected.contains(source)) { selected.add(source); - } else if (source == null && model.getFileList().getSelection().isEmpty()) { - selected.add(new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false)); } for (BrowserAction.Category cat : BrowserAction.Category.values()) { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java index 63cd1709..43806ea9 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java @@ -204,6 +204,12 @@ final class BrowserFileListComp extends SimpleComp { private void prepareTableEntries(TableView table) { var emptyEntry = new BrowserFileListCompEntry(table, null, fileList); + table.setOnMouseClicked(e -> { + emptyEntry.onMouseClick(e); + }); + table.setOnMouseDragEntered(event -> { + emptyEntry.onMouseDragEntered(event); + }); table.setOnDragOver(event -> { emptyEntry.onDragOver(event); }); @@ -223,6 +229,10 @@ final class BrowserFileListComp extends SimpleComp { table.setRowFactory(param -> { TableRow row = new TableRow<>(); new ContextMenuAugment<>(event -> { + if (row.getItem() == null) { + return event.getButton() == MouseButton.SECONDARY; + } + if (row.getItem() != null && row.getItem().getRawFileEntry().isDirectory()) { return event.getButton() == MouseButton.SECONDARY; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java index 1319ed8e..dab1c01a 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java @@ -31,6 +31,8 @@ public class BrowserFileListCompEntry { @SuppressWarnings("unchecked") public void onMouseClick(MouseEvent t) { + t.consume(); + if (item == null) { model.getSelection().clear(); return; @@ -38,7 +40,6 @@ public class BrowserFileListCompEntry { if (t.getClickCount() == 2 && t.getButton() == MouseButton.PRIMARY) { model.onDoubleClick(item); - t.consume(); return; } @@ -54,7 +55,6 @@ public class BrowserFileListCompEntry { var end = tv.getSelectionModel().getFocusedIndex(); var start = end > min ? min : max; tv.getSelectionModel().selectRange(Math.min(start, end), Math.max(start, end) + 1); - t.consume(); return; } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java index 35f37a3e..aaaa7087 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java @@ -4,7 +4,6 @@ import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.impl.FileNames; import io.xpipe.core.store.FileSystem; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; @@ -33,8 +32,6 @@ public final class BrowserFileListModel { new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR); private final Property> all = new SimpleObjectProperty<>(new ArrayList<>()); private final Property> shown = new SimpleObjectProperty<>(new ArrayList<>()); - private final ObjectProperty> predicateProperty = - new SimpleObjectProperty<>(path -> true); private final ObservableList previousSelection = FXCollections.observableArrayList(); private final ObservableList selection = FXCollections.observableArrayList(); private final ObservableList selectedRaw = @@ -129,8 +126,4 @@ public final class BrowserFileListModel { // FileOpener.openInTextEditor(entry.getRawFileEntry()); } } - - public ObjectProperty> predicateProperty() { - return predicateProperty; - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java index 6ddaf8eb..e8f52147 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java @@ -1,10 +1,13 @@ package io.xpipe.app.browser; import io.xpipe.app.fxcomps.util.BindingsHelper; +import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.impl.FileStore; import io.xpipe.core.store.ShellStore; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -104,11 +107,11 @@ public class BrowserModel { if (found.isPresent()) { selected.setValue(found.get()); } else { - openFileSystemAsync(name, store, null); + openFileSystemAsync(name, store, null, null); } } - public void openFileSystemAsync(String name, ShellStore store, String path) { + public void openFileSystemAsync(String name, ShellStore store, String path, BooleanProperty busy) { // // Prevent multiple tabs in non browser modes // if (!mode.equals(Mode.BROWSER)) { // ThreadHelper.runFailableAsync(() -> { @@ -127,8 +130,13 @@ public class BrowserModel { // } ThreadHelper.runFailableAsync(() -> { - var model = new OpenFileSystemModel(name, this, store); - model.initFileSystem(); + OpenFileSystemModel model; + + try (var b = new BusyProperty(busy != null ? busy : new SimpleBooleanProperty())) { + model = new OpenFileSystemModel(name, this, store); + model.initFileSystem(); + } + openFileSystems.add(model); selected.setValue(model); if (path != null) { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java new file mode 100644 index 00000000..ef9ceaea --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java @@ -0,0 +1,35 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.comp.base.SimpleTitledPaneComp; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.impl.VerticalComp; +import io.xpipe.core.store.FileSystem; +import javafx.collections.FXCollections; +import javafx.scene.layout.Region; + +import java.time.Instant; +import java.util.List; + +public class BrowserOverviewComp extends SimpleComp { + + private final OpenFileSystemModel model; + + public BrowserOverviewComp(OpenFileSystemModel model) { + this.model = model; + } + + @Override + protected Region createSimple() { + var commonList = new BrowserSelectionListComp(FXCollections.observableArrayList( + new FileSystem.FileEntry(model.getFileSystem(), "C:\\", Instant.now(), true, false, false, 0, null))); + var common = new SimpleTitledPaneComp(AppI18n.observable("a"), commonList); + + var recentList = new BrowserSelectionListComp(FXCollections.observableArrayList( + new FileSystem.FileEntry(model.getFileSystem(), "C:\\", Instant.now(), true, false, false, 0, null))); + var recent = new SimpleTitledPaneComp(AppI18n.observable("Recent"), recentList); + + var vbox = new VerticalComp(List.of(common, recent)).styleClass("home"); + return vbox.createRegion(); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java index e84e99d7..eec14ac2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -59,9 +59,36 @@ public class BrowserStatusBarComp extends SimpleComp { }); AppFont.small(bar); - // Use status bar as an extension of file list - new ContextMenuAugment<>(() -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(bar)); + simulateEmptyCell(bar); return bar; } + + private void simulateEmptyCell(Region r) { + var emptyEntry = new BrowserFileListCompEntry(r, null, model.getFileList()); + r.setOnMouseClicked(e -> { + emptyEntry.onMouseClick(e); + }); + r.setOnMouseDragEntered(event -> { + emptyEntry.onMouseDragEntered(event); + }); + r.setOnDragOver(event -> { + emptyEntry.onDragOver(event); + }); + r.setOnDragEntered(event -> { + emptyEntry.onDragEntered(event); + }); + r.setOnDragDetected(event -> { + emptyEntry.startDrag(event); + }); + r.setOnDragExited(event -> { + emptyEntry.onDragExited(event); + }); + r.setOnDragDropped(event -> { + emptyEntry.onDragDrop(event); + }); + + // Use status bar as an extension of file list + new ContextMenuAugment<>(() -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(r)); + } } 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 adf33e45..7667ac49 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java @@ -2,10 +2,13 @@ package io.xpipe.app.browser; import atlantafx.base.controls.Spacer; import io.xpipe.app.comp.base.ModalOverlayComp; +import io.xpipe.app.comp.base.MultiContentComp; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.augment.ContextMenuAugment; +import io.xpipe.app.fxcomps.impl.VerticalComp; +import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.Shortcuts; import javafx.geometry.Insets; @@ -21,7 +24,8 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import org.kordamp.ikonli.javafx.FontIcon; -import static io.xpipe.app.browser.BrowserFileListModel.PREDICATE_NOT_HIDDEN; +import java.util.List; +import java.util.Map; public class OpenFileSystemComp extends SimpleComp { @@ -33,9 +37,7 @@ public class OpenFileSystemComp extends SimpleComp { @Override protected Region createSimple() { - var alertOverlay = new ModalOverlayComp( - Comp.of(() -> createContent()), - model.getOverlay()); + var alertOverlay = new ModalOverlayComp(Comp.of(() -> createContent()), model.getOverlay()); return alertOverlay.createRegion(); } @@ -58,22 +60,43 @@ public class OpenFileSystemComp extends SimpleComp { terminalBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory())); var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open")); - new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, () -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(menuButton)); + new ContextMenuAugment<>( + event -> event.getButton() == MouseButton.PRIMARY, () -> new BrowserContextMenu(model, null)) + .augment(new SimpleCompStructure<>(menuButton)); var filter = new BrowserFilterComp(model.getFilter()).createStructure(); Shortcuts.addShortcut(filter.toggleButton(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)); var topBar = new ToolBar(); topBar.getItems() - .setAll(backBtn, forthBtn, new Spacer(10), new BrowserNavBar(model).createRegion(), filter.get(), refreshBtn, terminalBtn, menuButton); + .setAll( + backBtn, + forthBtn, + new Spacer(10), + new BrowserNavBar(model).createRegion(), + filter.get(), + refreshBtn, + terminalBtn, + menuButton); - var directoryView = new BrowserFileListComp(model.getFileList()).createRegion(); - - var root = new VBox(topBar, directoryView); - root.getChildren().add(new BrowserStatusBarComp(model).createRegion()); - VBox.setVgrow(directoryView, Priority.ALWAYS); + var content = createFileListContent(); + var root = new VBox(topBar, content); + VBox.setVgrow(content, Priority.ALWAYS); root.setPadding(Insets.EMPTY); - model.getFileList().predicateProperty().set(PREDICATE_NOT_HIDDEN); return root; } + + private Region createFileListContent() { + var directoryView = new BrowserFileListComp(model.getFileList()).apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS)); + var statusBar = new BrowserStatusBarComp(model); + var fileList = new VerticalComp(List.of(directoryView, statusBar)); + + var home = new BrowserOverviewComp(model); + var stack = new MultiContentComp(Map.of( + home, + model.getCurrentPath().isNull(), + fileList, + BindingsHelper.persist(model.getCurrentPath().isNull().not()))); + return stack.createRegion(); + } } diff --git a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java b/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java index 77a826e6..bdc2e24e 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java +++ b/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java @@ -1,6 +1,5 @@ package io.xpipe.app.comp.base; -import com.jfoenix.controls.JFXButton; import io.xpipe.app.fxcomps.CompStructure; import javafx.beans.value.ObservableValue; import javafx.geometry.Pos; @@ -34,7 +33,7 @@ public class BigIconButton extends ButtonComp { label.getStyleClass().add("name"); vbox.getChildren().add(label); - var b = new JFXButton(null); + var b = new Button(null); b.setGraphic(vbox); b.setOnAction(e -> getListener().run()); b.getStyleClass().add("big-icon-button-comp"); @@ -50,14 +49,14 @@ public class BigIconButton extends ButtonComp { @Value @Builder public static class Structure implements CompStructure