File browser improvements

This commit is contained in:
crschnick 2023-05-17 04:42:32 +00:00
parent 8ca064b0ff
commit f8f1b90581
18 changed files with 228 additions and 35 deletions

View file

@ -3,6 +3,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
@ -28,13 +29,18 @@ public class BrowserNavBar extends SimpleComp {
var changed = model.cd(newValue);
changed.ifPresent(path::set);
});
var pathBar = new TextFieldComp(path, true).createRegion();
var pathBar = new TextFieldComp(path, true).createStructure().get();
pathBar.getStyleClass().add("path-text");
model.getCurrentPath().addListener((observable, oldValue, newValue) -> {
path.set(newValue);
});
SimpleChangeListener.apply(pathBar.focusedProperty(), val -> {
pathBar.pseudoClassStateChanged(INVISIBLE, !val);
if (val) {
Platform.runLater(() -> {
pathBar.selectAll();
});
}
});
var breadcrumbs = new FileBrowserBreadcrumbBar(model)

View file

@ -53,6 +53,10 @@ public class FileBrowserStatusBarComp extends SimpleComp {
selectedComp.createRegion()
);
bar.getStyleClass().add("status-bar");
bar.setOnDragDetected(event -> {
event.consume();
bar.startFullDrag();
});
AppFont.small(bar);
// Use status bar as an extension of file list

View file

@ -26,20 +26,26 @@ final class FileContextMenu extends ContextMenu {
private void createMenu() {
AppFont.normal(this.getStyleableNode());
var selected = empty ? FXCollections.<FileBrowserEntry>observableArrayList() : model.getFileList().getSelected();
var selected = empty || model.getFileList().getSelected().isEmpty()
? FXCollections.observableArrayList(
new FileBrowserEntry(model.getCurrentDirectory(), model.getFileList(), false))
: model.getFileList().getSelected();
for (BrowserAction.Category cat : BrowserAction.Category.values()) {
var all = BrowserAction.ALL.stream().filter(browserAction -> browserAction.getCategory() == cat).filter(browserAction -> {
if (!browserAction.isApplicable(model, selected)) {
return false;
}
var all = BrowserAction.ALL.stream()
.filter(browserAction -> browserAction.getCategory() == cat)
.filter(browserAction -> {
if (!browserAction.isApplicable(model, selected)) {
return false;
}
if (!browserAction.acceptsEmptySelection() && selected.isEmpty()) {
return false;
}
if (!browserAction.acceptsEmptySelection() && empty) {
return false;
}
return true;
}).toList();
return true;
})
.toList();
if (all.size() == 0) {
continue;
}

View file

@ -3,7 +3,6 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import atlantafx.base.theme.Tweaks;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.comp.base.LazyTextFieldComp;
@ -90,7 +89,6 @@ final class FileListComp extends AnchorPane {
mtimeCol.setCellValueFactory(param ->
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().getDate()));
mtimeCol.setCellFactory(col -> new FileTimeCell());
mtimeCol.getStyleClass().add(Tweaks.ALIGN_RIGHT);
var modeCol = new TableColumn<FileBrowserEntry, String>("Attributes");
modeCol.setCellValueFactory(param ->
@ -116,7 +114,6 @@ final class FileListComp extends AnchorPane {
FXCollections.sort(param.getItems(), us);
return true;
});
table.getSortOrder().add(filenameCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
filenameCol.minWidthProperty().bind(table.widthProperty().multiply(0.5));
@ -253,7 +250,9 @@ final class FileListComp extends AnchorPane {
row.setOnMouseClicked(e -> {
listEntry.get().onMouseClick(e);
});
row.setOnMouseDragEntered(event -> {
listEntry.get().onMouseDragEntered(event);
});
row.setOnDragEntered(event -> {
listEntry.get().onDragEntered(event);
});

View file

@ -131,6 +131,7 @@ public class FileListCompEntry {
public void startDrag(MouseEvent event) {
if (item == null) {
row.startFullDrag();
return;
}
@ -191,6 +192,22 @@ public class FileListCompEntry {
acceptDrag(event);
}
@SuppressWarnings("unchecked")
public void onMouseDragEntered(MouseDragEvent event) {
event.consume();
if (model.getFileSystemModel().getCurrentDirectory() == null) {
return;
}
if (item == null || item.isSynthetic()) {
return;
}
var tv = ((TableView<FileBrowserEntry>) row.getParent().getParent().getParent().getParent());
tv.getSelectionModel().select(item);
}
public void onDragOver(DragEvent event) {
event.consume();
if (!acceptsDrop(event)) {

View file

@ -1,11 +1,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.ModalOverlayComp;
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.util.PlatformThread;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.geometry.Insets;
@ -21,8 +21,6 @@ import javafx.scene.layout.VBox;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.function.UnaryOperator;
import static io.xpipe.app.browser.FileListModel.PREDICATE_NOT_HIDDEN;
import static io.xpipe.app.util.Controls.iconButton;
@ -60,19 +58,15 @@ public class OpenFileSystemComp extends SimpleComp {
e -> model.openTerminalAsync(model.getCurrentPath().get()));
terminalBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory()));
var addBtn = new MenuButton(null, new FontIcon("mdmz-plus"));
var s = model.getFileList().getSelected();
var action = (BranchAction) BrowserAction.ALL.stream().filter(browserAction -> browserAction.getName(model, s).equals("New")).findFirst().orElseThrow();
action.getBranchingActions().forEach(action1 -> {
addBtn.getItems().add(action1.toItem(model, s, UnaryOperator.identity()));
});
var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open"));
new ContextMenuAugment<>(true, () -> new FileContextMenu(model, true)).augment(new SimpleCompStructure<>(menuButton));
var filter = new FileFilterComp(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, addBtn);
.setAll(backBtn, forthBtn, new Spacer(10), new BrowserNavBar(model).createRegion(), filter.get(), refreshBtn, terminalBtn, menuButton);
// ~

View file

@ -12,6 +12,7 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.FileSystemStore;
@ -124,8 +125,34 @@ public final class OpenFileSystemModel {
}
// Handle commands typed into navigation bar
if (!FileNames.isAbsolute(path)) {
if (!FileNames.isAbsolute(path) && fileSystem.getShell().isPresent()) {
var directory = currentPath.get();
var name = path + " - "
+ XPipeDaemon.getInstance().getStoreName(store.getValue()).orElse("?");
ThreadHelper.runFailableAsync(() -> {
if (ShellDialects.ALL.stream().anyMatch(dialect -> path.startsWith(dialect.getOpenCommand()))) {
var cmd = fileSystem
.getShell()
.get()
.subShell(path)
.initWith(fileSystem
.getShell()
.get()
.getShellDialect()
.getCdCommand(currentPath.get()))
.prepareTerminalOpen(name);
TerminalHelper.open(path, cmd);
} else {
var cmd = fileSystem
.getShell()
.get()
.command(path)
.workingDirectory(directory)
.prepareTerminalOpen(name);
TerminalHelper.open(path, cmd);
}
});
return Optional.of(currentPath.get());
}
String newPath = null;
@ -306,7 +333,8 @@ public final class OpenFileSystemModel {
var fs = fileSystem.createFileSystem();
fs.open();
this.fileSystem = fs;
this.local.set(fs.getShell().map(shellControl -> shellControl.isLocal()).orElse(false));
this.local.set(
fs.getShell().map(shellControl -> shellControl.isLocal()).orElse(false));
var storageEntry = DataStorage.get()
.getStoreEntryIfPresent(fileSystem)

View file

@ -16,6 +16,7 @@ public interface BrowserAction {
static enum Category {
CUSTOM,
OPEN,
NATIVE,
COPY_PASTE,
MUTATION
}

View file

@ -17,6 +17,11 @@ import java.util.Random;
public class ScriptHelper {
public static String createDetachCommand(ShellControl pc, String command) {
if (pc.getShellDialect().equals(ShellDialects.POWERSHELL)) {
var script = ScriptHelper.createExecScript(pc, command);
return String.format("Start-Process -WindowStyle Minimized -FilePath powershell.exe -ArgumentList \"-NoProfile\", \"-File\", %s", ShellDialects.POWERSHELL.fileArgument(script));
}
if (pc.getOsType().equals(OsType.WINDOWS)) {
return "start \"\" /MIN " + command;
} else {

View file

@ -135,6 +135,15 @@ public interface ShellControl extends ProcessControl {
});
}
default ShellControl enforcedDialect(ShellDialect type) throws Exception {
start();
if (getShellDialect().equals(type)) {
return this;
} else {
return subShell(type).start();
}
}
default <T> T enforceDialect(@NonNull ShellDialect type, Function<ShellControl, T> sc) throws Exception {
if (isRunning() && getShellDialect().equals(type)) {
return sc.apply(this);

View file

@ -20,6 +20,11 @@ public class CopyAction implements LeafAction {
model.getCurrentDirectory(), entries.stream().map(entry -> entry.getRawFileEntry()).toList());
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return new FontIcon("mdi2c-content-copy");

View file

@ -25,6 +25,11 @@ public class CopyPathAction implements BrowserAction, BranchAction {
return Category.COPY_PASTE;
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public List<LeafAction> getBranchingActions() {
return List.of(

View file

@ -35,7 +35,7 @@ public class NewItemAction implements BrowserAction, BranchAction {
@Override
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return entries.size() == 0;
return entries.size() == 1 && entries.get(0).getRawFileEntry().getPath().equals(model.getCurrentPath().get());
}
@Override

View file

@ -34,6 +34,11 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
return entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory()) && model.getBrowserModel().getMode() == FileBrowserModel.Mode.BROWSER;
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.ENTER);

View file

@ -0,0 +1,57 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.FileBrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.process.OsType;
import java.util.List;
public class OpenInNativeManagerAction implements LeafAction {
@Override
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
for (FileBrowserEntry entry : entries) {
var e = entry.getRawFileEntry().getPath();
switch (OsType.getLocal()) {
case OsType.Windows windows -> {
model.getFileSystem()
.getShell()
.get()
.executeSimpleCommand("explorer "
+ model.getFileSystem()
.getShell()
.get()
.getShellDialect()
.fileArgument(e));
}
case OsType.Linux linux -> {}
case OsType.MacOs macOs -> {}
}
}
}
@Override
public Category getCategory() {
return Category.NATIVE;
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory());
}
@Override
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return switch (OsType.getLocal()) {
case OsType.Windows windows -> "Open in Windows Explorer";
case OsType.Linux linux -> "Open in Windows Explorer";
case OsType.MacOs macOs -> "Open in Windows Explorer";
};
}
}

View file

@ -0,0 +1,55 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.FileBrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialects;
import java.util.List;
public class OpenNativeFileDetailsAction implements LeafAction {
@Override
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
for (FileBrowserEntry entry : entries) {
var e = entry.getRawFileEntry().getPath();
switch (OsType.getLocal()) {
case OsType.Windows windows -> {
var content = String.format(
"""
$shell = New-Object -ComObject Shell.Application; $shell.NameSpace('%s').ParseName('%s').InvokeVerb('Properties')
""",
FileNames.getParent(e),
FileNames.getFileName(e));
try (var sub = model.getFileSystem().getShell().get().enforcedDialect(ShellDialects.POWERSHELL).start()) {
sub.command(content).notComplex().execute();
}
}
case OsType.Linux linux -> {}
case OsType.MacOs macOs -> {}
}
}
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public Category getCategory() {
return Category.NATIVE;
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return true;
}
@Override
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
return "Details";
}
}

View file

@ -26,11 +26,6 @@ public class OpenTerminalAction implements LeafAction {
}
}
@Override
public boolean acceptsEmptySelection() {
return true;
}
@Override
public Category getCategory() {
return Category.OPEN;

View file

@ -32,6 +32,8 @@ open module io.xpipe.ext.base {
OpenDirectoryAction,
OpenDirectoryInNewTabAction,
OpenTerminalAction,
OpenNativeFileDetailsAction,
OpenInNativeManagerAction,
EditFileAction,
RunAction,
CopyAction,