mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-06-24 17:10:19 +12:00
More file browser improvements [release]
This commit is contained in:
parent
355de8e2fc
commit
d5e024c43e
|
@ -22,14 +22,13 @@ import javafx.geometry.Pos;
|
|||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
|
||||
import static javafx.scene.control.TableColumn.SortType.ASCENDING;
|
||||
|
@ -41,7 +40,6 @@ final class FileListComp extends AnchorPane {
|
|||
private static final PseudoClass DRAG = PseudoClass.getPseudoClass("drag");
|
||||
private static final PseudoClass DRAG_OVER = PseudoClass.getPseudoClass("drag-over");
|
||||
private static final PseudoClass DRAG_INTO_CURRENT = PseudoClass.getPseudoClass("drag-into-current");
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private final FileListModel fileList;
|
||||
|
||||
|
@ -59,7 +57,6 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TableView<FileSystem.FileEntry> createTable() {
|
||||
var editing = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||
var filenameCol = new TableColumn<FileSystem.FileEntry, String>("Name");
|
||||
filenameCol.setCellValueFactory(param -> new SimpleStringProperty(
|
||||
param.getValue() != null
|
||||
|
@ -67,7 +64,7 @@ final class FileListComp extends AnchorPane {
|
|||
: null));
|
||||
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
|
||||
filenameCol.setSortType(ASCENDING);
|
||||
filenameCol.setCellFactory(col -> new FilenameCell(editing));
|
||||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
||||
|
||||
var sizeCol = new TableColumn<FileSystem.FileEntry, Number>("Size");
|
||||
sizeCol.setCellValueFactory(
|
||||
|
@ -91,22 +88,29 @@ final class FileListComp extends AnchorPane {
|
|||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
filenameCol.minWidthProperty().bind(table.widthProperty().multiply(0.5));
|
||||
|
||||
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER) || fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
|
||||
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)
|
||||
|| fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
} else {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
}
|
||||
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
|
||||
fileList.getSelected().setAll(c.getList());
|
||||
fileList.getFileSystemModel().getBrowserModel().getSelectedFiles().setAll(c.getList());
|
||||
});
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>)
|
||||
c -> {
|
||||
fileList.getSelected().setAll(c.getList());
|
||||
fileList.getFileSystemModel()
|
||||
.getBrowserModel()
|
||||
.getSelectedFiles()
|
||||
.setAll(c.getList());
|
||||
});
|
||||
|
||||
table.setOnKeyPressed(event -> {
|
||||
if (event.isControlDown()
|
||||
&& event.getCode().equals(KeyCode.C)
|
||||
&& table.getSelectionModel().getSelectedItems().size() > 0) {
|
||||
FileBrowserClipboard.startCopy(fileList.getFileSystemModel().getCurrentDirectory(), table.getSelectionModel().getSelectedItems());
|
||||
FileBrowserClipboard.startCopy(
|
||||
fileList.getFileSystemModel().getCurrentDirectory(),
|
||||
table.getSelectionModel().getSelectedItems());
|
||||
event.consume();
|
||||
}
|
||||
|
||||
|
@ -140,31 +144,20 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
table.setRowFactory(param -> {
|
||||
TableRow<FileSystem.FileEntry> row = new TableRow<>();
|
||||
var listEntry = Bindings.createObjectBinding(() -> new FileListEntry(row, row.getItem(), fileList), row.itemProperty());
|
||||
var listEntry = Bindings.createObjectBinding(
|
||||
() -> new FileListEntry(row, row.getItem(), fileList), row.itemProperty());
|
||||
|
||||
row.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue && listEntry.get().isSynthetic()) {
|
||||
row.updateSelected(false);
|
||||
}
|
||||
});
|
||||
|
||||
row.itemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
row.pseudoClassStateChanged(DRAG, false);
|
||||
row.pseudoClassStateChanged(DRAG_OVER, false);
|
||||
});
|
||||
|
||||
row.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> {
|
||||
t.consume();
|
||||
if (row.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cm = new FileContextMenu(fileList.getFileSystemModel(), row.getItem(), editing);
|
||||
if (t.getButton() == MouseButton.SECONDARY) {
|
||||
cm.show(row, t.getScreenX(), t.getScreenY());
|
||||
}
|
||||
});
|
||||
|
||||
row.setOnMouseClicked(e -> {
|
||||
if (e.getClickCount() == 2 && !row.isEmpty()) {
|
||||
fileList.onDoubleClick(row.getItem());
|
||||
}
|
||||
});
|
||||
|
||||
fileList.getDraggedOverDirectory().addListener((observable, oldValue, newValue) -> {
|
||||
row.pseudoClassStateChanged(DRAG_OVER, newValue != null && newValue == row.getItem());
|
||||
});
|
||||
|
@ -173,6 +166,16 @@ final class FileListComp extends AnchorPane {
|
|||
table.pseudoClassStateChanged(DRAG_INTO_CURRENT, newValue);
|
||||
});
|
||||
|
||||
row.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> {
|
||||
listEntry.get().onMouseClick(t);
|
||||
});
|
||||
|
||||
row.setOnMouseClicked(e -> {
|
||||
if (e.getClickCount() == 2 && !row.isEmpty()) {
|
||||
fileList.onDoubleClick(row.getItem());
|
||||
}
|
||||
});
|
||||
|
||||
row.setOnDragEntered(event -> {
|
||||
listEntry.get().onDragEntered(event);
|
||||
});
|
||||
|
@ -194,7 +197,13 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
SimpleChangeListener.apply(fileList.getShown(), (newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
table.getItems().setAll(newValue);
|
||||
var newItems = new ArrayList<FileSystem.FileEntry>();
|
||||
var parentEntry = fileList.getFileSystemModel().getCurrentParentDirectory();
|
||||
if (parentEntry != null) {
|
||||
newItems.add(parentEntry);
|
||||
}
|
||||
newItems.addAll(newValue);
|
||||
table.getItems().setAll(newItems);
|
||||
if (newValue.size() > 0) {
|
||||
table.scrollTo(0);
|
||||
}
|
||||
|
@ -217,8 +226,7 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
public FilenameCell(Property<FileSystem.FileEntry> editing) {
|
||||
editing.addListener((observable, oldValue, newValue) -> {
|
||||
if (getTableRow().getItem() != null
|
||||
&& getTableRow().getItem().equals(newValue)) {
|
||||
if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) {
|
||||
textField.requestFocus();
|
||||
}
|
||||
});
|
||||
|
@ -257,7 +265,7 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
pseudoClassStateChanged(FOLDER, isDirectory);
|
||||
|
||||
var fileName = FileNames.getFileName(fullPath);
|
||||
var fileName = getTableRow().getItem().equals(fileList.getFileSystemModel().getCurrentParentDirectory()) ? ".." : FileNames.getFileName(fullPath);
|
||||
var hidden = getTableRow().getItem().isHidden() || fileName.startsWith(".");
|
||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||
text.set(fileName);
|
||||
|
@ -277,17 +285,7 @@ final class FileListComp extends AnchorPane {
|
|||
} else {
|
||||
var path = getTableRow().getItem();
|
||||
if (path.isDirectory()) {
|
||||
if (true) {
|
||||
setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<FileSystem.FileEntry> stream =
|
||||
path.getFileSystem().listFiles(path.getPath())) {
|
||||
setText(stream.count() + " items");
|
||||
} catch (Exception e) {
|
||||
setText(UNKNOWN);
|
||||
}
|
||||
setText("");
|
||||
} else {
|
||||
setText(byteCount(fileSize.longValue()));
|
||||
}
|
||||
|
@ -307,7 +305,7 @@ final class FileListComp extends AnchorPane {
|
|||
fileTime != null
|
||||
? HumanReadableFormat.date(
|
||||
fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime())
|
||||
: UNKNOWN);
|
||||
: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,7 @@ import io.xpipe.core.store.FileSystem;
|
|||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.input.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -33,6 +30,22 @@ public class FileListEntry {
|
|||
this.model = model;
|
||||
}
|
||||
|
||||
public void onMouseClick(MouseEvent t) {
|
||||
t.consume();
|
||||
if (item == null || isSynthetic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cm = new FileContextMenu(model.getFileSystemModel(), item, model.getEditing());
|
||||
if (t.getButton() == MouseButton.SECONDARY) {
|
||||
cm.show(row, t.getScreenX(), t.getScreenY());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return item != null && item.equals(model.getFileSystemModel().getCurrentParentDirectory());
|
||||
}
|
||||
|
||||
private boolean acceptsDrop(DragEvent event) {
|
||||
if (FileBrowserClipboard.currentDragClipboard == null) {
|
||||
return false;
|
||||
|
@ -95,6 +108,10 @@ public class FileListEntry {
|
|||
return;
|
||||
}
|
||||
|
||||
if (isSynthetic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "img/file_drag_icon.png")
|
||||
.orElseThrow();
|
||||
var image = new Image(url.toString(), 80, 80, true, false);
|
||||
|
@ -116,7 +133,6 @@ public class FileListEntry {
|
|||
}
|
||||
|
||||
private void handleHoverTimer(DragEvent event) {
|
||||
|
||||
if (item == null || !item.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ final class FileListModel {
|
|||
|
||||
private final Property<FileSystem.FileEntry> draggedOverDirectory = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||
private final Property<FileSystem.FileEntry> editing = new SimpleObjectProperty<>();
|
||||
|
||||
public FileListModel(OpenFileSystemModel fileSystemModel) {
|
||||
this.fileSystemModel = fileSystemModel;
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
|||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
@ -52,8 +53,26 @@ final class OpenFileSystemModel {
|
|||
cdSync(currentPath.get());
|
||||
}
|
||||
|
||||
public FileSystem.FileEntry getCurrentParentDirectory() {
|
||||
var current = getCurrentDirectory();
|
||||
if (current == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var parent = FileNames.getParent(currentPath.get());
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FileSystem.FileEntry(fileSystem, parent, null, true, false, false, 0);
|
||||
}
|
||||
|
||||
public FileSystem.FileEntry getCurrentDirectory() {
|
||||
return new FileSystem.FileEntry(fileSystem, currentPath.get(), Instant.now(), true, false, false, 0);
|
||||
if (currentPath.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FileSystem.FileEntry(fileSystem, currentPath.get(), null, true, false, false, 0);
|
||||
}
|
||||
|
||||
public Optional<String> cd(String path) {
|
||||
|
|
|
@ -43,8 +43,11 @@
|
|||
}
|
||||
|
||||
.browser .table-directory-view .table-view:drag-into-current {
|
||||
-fx-border-color: -color-success-muted;
|
||||
-fx-border-width: 2px;
|
||||
-fx-background-color: -color-success-muted;
|
||||
}
|
||||
|
||||
.browser .table-directory-view .table-view:drag-into-current .table-row-cell {
|
||||
-fx-opacity: 0.8;
|
||||
}
|
||||
|
||||
.browser .table-row-cell:drag-over {
|
||||
|
|
|
@ -65,6 +65,14 @@ public class FileNames {
|
|||
}
|
||||
|
||||
public static String getParent(String file) {
|
||||
if (split(file).size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (split(file).size() == 1) {
|
||||
return file.startsWith("/") && !file.equals("/") ? "/" : null;
|
||||
}
|
||||
|
||||
return file.substring(0, file.length() - getFileName(file).length() - 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
FileSystem fileSystem;
|
||||
@NonNull
|
||||
String path;
|
||||
@NonNull
|
||||
Instant date;
|
||||
boolean directory;
|
||||
boolean hidden;
|
||||
|
@ -29,7 +28,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
long size;
|
||||
|
||||
public FileEntry(
|
||||
@NonNull FileSystem fileSystem, @NonNull String path, @NonNull Instant date, boolean directory, boolean hidden, Boolean executable,
|
||||
@NonNull FileSystem fileSystem, @NonNull String path, Instant date, boolean directory, boolean hidden, Boolean executable,
|
||||
long size
|
||||
) {
|
||||
this.fileSystem = fileSystem;
|
||||
|
|
Loading…
Reference in a new issue