mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-06-22 14:29:18 -04:00
File browser improvements
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -19,7 +21,7 @@ public class FileBrowserClipboard {
|
||||
List<FileSystem.FileEntry> entries;
|
||||
}
|
||||
|
||||
public static Instance currentCopyClipboard;
|
||||
public static Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
|
||||
public static Instance currentDragClipboard;
|
||||
|
||||
@SneakyThrows
|
||||
@@ -34,12 +36,12 @@ public class FileBrowserClipboard {
|
||||
@SneakyThrows
|
||||
public static void startCopy(FileSystem.FileEntry base, List<FileSystem.FileEntry> selected) {
|
||||
var id = UUID.randomUUID();
|
||||
currentCopyClipboard = new Instance(id, base, new ArrayList<>(selected));
|
||||
currentCopyClipboard.setValue(new Instance(id, base, new ArrayList<>(selected)));
|
||||
}
|
||||
|
||||
public static Instance retrieveCopy() {
|
||||
var current = currentCopyClipboard;
|
||||
return current;
|
||||
return current.getValue();
|
||||
}
|
||||
|
||||
public static Instance retrieveDrag(Dragboard dragboard) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class FileBrowserStatusBarComp extends SimpleComp {
|
||||
|
||||
OpenFileSystemModel model;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var cc = PlatformThread.sync(FileBrowserClipboard.currentCopyClipboard);
|
||||
var ccCount = Bindings.createStringBinding(() -> {
|
||||
if (cc.getValue() != null && cc.getValue().getEntries().size() > 0) {
|
||||
return String.valueOf(cc.getValue().getEntries().size()) + " file" + (cc.getValue().getEntries().size() > 1 ? "s" : "") + " in clipboard";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, cc);
|
||||
|
||||
var selectedCount = PlatformThread.sync(Bindings.createIntegerBinding(() -> {
|
||||
return model.getFileList().getSelected().size();
|
||||
}, model.getFileList().getSelected()));
|
||||
|
||||
var allCount = PlatformThread.sync(Bindings.createIntegerBinding(() -> {
|
||||
return model.getFileList().getAll().getValue().size();
|
||||
}, model.getFileList().getAll()));
|
||||
|
||||
var selectedComp = new LabelComp(Bindings.createStringBinding(() -> {
|
||||
if (selectedCount.getValue().intValue() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return selectedCount.getValue() + " / " + allCount.getValue() + " selected";
|
||||
}
|
||||
}, selectedCount, allCount));
|
||||
|
||||
var bar = new ToolBar();
|
||||
bar.getItems().setAll(
|
||||
new LabelComp(ccCount).createRegion(),
|
||||
new Spacer(),
|
||||
selectedComp.createRegion()
|
||||
);
|
||||
bar.getStyleClass().add("status-bar");
|
||||
AppFont.small(bar);
|
||||
return bar;
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,31 @@ final class FileContextMenu extends ContextMenu {
|
||||
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
{
|
||||
|
||||
var copy = new MenuItem("Copy");
|
||||
copy.setOnAction(event -> {
|
||||
FileBrowserClipboard.startCopy(
|
||||
model.getCurrentDirectory(), model.getFileList().getSelected());
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(copy);
|
||||
|
||||
var paste = new MenuItem("Paste");
|
||||
paste.setOnAction(event -> {
|
||||
var clipboard = FileBrowserClipboard.retrieveCopy();
|
||||
if (clipboard != null) {
|
||||
var files = clipboard.getEntries();
|
||||
var target = model.getCurrentDirectory();
|
||||
model.dropFilesIntoAsync(target, files, true);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(paste);
|
||||
}
|
||||
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
var copyName = new MenuItem("Copy name");
|
||||
copyName.setOnAction(event -> {
|
||||
var selection = new StringSelection(FileNames.getFileName(entry.getPath()));
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.xpipe.app.util.HumanReadableFormat;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
@@ -87,7 +88,6 @@ final class FileListComp extends AnchorPane {
|
||||
mtimeCol.setCellFactory(col -> new FileTimeCell());
|
||||
mtimeCol.getStyleClass().add(Tweaks.ALIGN_RIGHT);
|
||||
|
||||
|
||||
var modeCol = new TableColumn<FileSystem.FileEntry, String>("Attributes");
|
||||
modeCol.setCellValueFactory(
|
||||
param -> new SimpleObjectProperty<>(param.getValue().getMode()));
|
||||
@@ -117,7 +117,8 @@ final class FileListComp extends AnchorPane {
|
||||
};
|
||||
var dirsFirst = Comparator.<FileSystem.FileEntry, Boolean>comparing(path -> !path.isDirectory());
|
||||
|
||||
Comparator<? super FileSystem.FileEntry> us = parentFirst.thenComparing(dirsFirst).thenComparing(comp);
|
||||
Comparator<? super FileSystem.FileEntry> us =
|
||||
parentFirst.thenComparing(dirsFirst).thenComparing(comp);
|
||||
FXCollections.sort(table.getItems(), us);
|
||||
return true;
|
||||
});
|
||||
@@ -133,11 +134,27 @@ final class FileListComp extends AnchorPane {
|
||||
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>)
|
||||
c -> {
|
||||
fileList.getSelected().setAll(c.getList());
|
||||
// Explicitly unselect synthetic entries since we can't use a custom selection model as that is bugged in JavaFX
|
||||
var toSelect = c.getList().stream()
|
||||
.filter(entry -> fileList.getFileSystemModel().getCurrentParentDirectory() == null
|
||||
|| !entry.getPath()
|
||||
.equals(fileList.getFileSystemModel()
|
||||
.getCurrentParentDirectory()
|
||||
.getPath()))
|
||||
.toList();
|
||||
fileList.getSelected().setAll(toSelect);
|
||||
fileList.getFileSystemModel()
|
||||
.getBrowserModel()
|
||||
.getSelectedFiles()
|
||||
.setAll(c.getList());
|
||||
.setAll(toSelect);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var toUnselect = table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(entry -> !toSelect.contains(entry))
|
||||
.toList();
|
||||
toUnselect.forEach(entry -> table.getSelectionModel()
|
||||
.clearSelection(table.getItems().indexOf(entry)));
|
||||
});
|
||||
});
|
||||
|
||||
table.setOnKeyPressed(event -> {
|
||||
@@ -183,12 +200,6 @@ final class FileListComp extends AnchorPane {
|
||||
var listEntry = Bindings.createObjectBinding(
|
||||
() -> new FileListCompEntry(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);
|
||||
@@ -252,7 +263,13 @@ final class FileListComp extends AnchorPane {
|
||||
}
|
||||
}
|
||||
|
||||
var hasAttributes = fileList.getFileSystemModel().getFileSystem() != null && !fileList.getFileSystemModel().getFileSystem().getShell().orElseThrow().getOsType().equals(OsType.WINDOWS);
|
||||
var hasAttributes = fileList.getFileSystemModel().getFileSystem() != null
|
||||
&& !fileList.getFileSystemModel()
|
||||
.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow()
|
||||
.getOsType()
|
||||
.equals(OsType.WINDOWS);
|
||||
if (!hasAttributes) {
|
||||
table.getColumns().remove(modeCol);
|
||||
} else {
|
||||
@@ -310,7 +327,9 @@ final class FileListComp extends AnchorPane {
|
||||
|
||||
private final StringProperty img = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
private final Node imageView = new SvgCacheComp(new SimpleDoubleProperty(24), new SimpleDoubleProperty(24), img, FileIconManager.getSvgCache()).createRegion();
|
||||
private final Node imageView = new SvgCacheComp(
|
||||
new SimpleDoubleProperty(24), new SimpleDoubleProperty(24), img, FileIconManager.getSvgCache())
|
||||
.createRegion();
|
||||
private final StackPane textField =
|
||||
new LazyTextFieldComp(text).createStructure().get();
|
||||
private final ChangeListener<String> listener;
|
||||
|
||||
@@ -84,7 +84,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
||||
|
||||
FileListComp directoryView = new FileListComp(model.getFileList());
|
||||
|
||||
var root = new VBox(topBar, directoryView);
|
||||
var root = new VBox(topBar, directoryView, new FileBrowserStatusBarComp(model).createRegion());
|
||||
VBox.setVgrow(directoryView, Priority.ALWAYS);
|
||||
root.setPadding(Insets.EMPTY);
|
||||
model.getFileList().predicateProperty().set(PREDICATE_NOT_HIDDEN);
|
||||
|
||||
Reference in New Issue
Block a user