mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-15 16:59:00 +12:00
File browser improvements
This commit is contained in:
parent
8ca064b0ff
commit
f8f1b90581
18 changed files with 228 additions and 35 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -> {
|
||||
var all = BrowserAction.ALL.stream()
|
||||
.filter(browserAction -> browserAction.getCategory() == cat)
|
||||
.filter(browserAction -> {
|
||||
if (!browserAction.isApplicable(model, selected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!browserAction.acceptsEmptySelection() && selected.isEmpty()) {
|
||||
if (!browserAction.acceptsEmptySelection() && empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
})
|
||||
.toList();
|
||||
if (all.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
// ~
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -16,6 +16,7 @@ public interface BrowserAction {
|
|||
static enum Category {
|
||||
CUSTOM,
|
||||
OPEN,
|
||||
NATIVE,
|
||||
COPY_PASTE,
|
||||
MUTATION
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -26,11 +26,6 @@ public class OpenTerminalAction implements LeafAction {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsEmptySelection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
|
|
|
@ -32,6 +32,8 @@ open module io.xpipe.ext.base {
|
|||
OpenDirectoryAction,
|
||||
OpenDirectoryInNewTabAction,
|
||||
OpenTerminalAction,
|
||||
OpenNativeFileDetailsAction,
|
||||
OpenInNativeManagerAction,
|
||||
EditFileAction,
|
||||
RunAction,
|
||||
CopyAction,
|
||||
|
|
Loading…
Reference in a new issue