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