mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Browser mode fixes
This commit is contained in:
parent
8e7f3f5aab
commit
9d2122831d
12 changed files with 110 additions and 79 deletions
|
@ -59,7 +59,7 @@ public class BrowserComp extends SimpleComp {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!model.getMode().equals(BrowserModel.Mode.BROWSER)) {
|
||||
if (model.getMode().isChooser()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ public class BrowserComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private Region addBottomBar(Region r) {
|
||||
if (model.getMode().equals(BrowserModel.Mode.BROWSER)) {
|
||||
if (!model.getMode().isChooser()) {
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -98,14 +98,19 @@ public class BrowserComp extends SimpleComp {
|
|||
var selected = new HBox();
|
||||
selected.setAlignment(Pos.CENTER_LEFT);
|
||||
selected.setSpacing(10);
|
||||
// model.getSelected().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
|
||||
// selected.getChildren().setAll(c.getList().stream().map(s -> {
|
||||
// var field = new TextField(s.getPath());
|
||||
// field.setEditable(false);
|
||||
// field.setPrefWidth(400);
|
||||
// return field;
|
||||
// }).toList());
|
||||
// });
|
||||
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
selected.getChildren()
|
||||
.setAll(c.getList().stream()
|
||||
.map(s -> {
|
||||
var field = new TextField(s.getRawFileEntry().getPath());
|
||||
field.setEditable(false);
|
||||
field.setPrefWidth(400);
|
||||
return field;
|
||||
})
|
||||
.toList());
|
||||
});
|
||||
});
|
||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||
var button = new Button("Select");
|
||||
button.setOnAction(event -> model.finishChooser());
|
||||
|
@ -198,7 +203,7 @@ public class BrowserComp extends SimpleComp {
|
|||
continue;
|
||||
}
|
||||
|
||||
model.closeFileSystem(source.getKey());
|
||||
model.closeFileSystemAsync(source.getKey());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -209,16 +214,9 @@ public class BrowserComp extends SimpleComp {
|
|||
var tabs = new TabPane();
|
||||
tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
|
||||
tabs.setTabMinWidth(Region.USE_COMPUTED_SIZE);
|
||||
|
||||
if (!model.getMode().equals(BrowserModel.Mode.BROWSER)) {
|
||||
tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
|
||||
tabs.getStyleClass().add("singular");
|
||||
} else {
|
||||
tabs.setTabClosingPolicy(ALL_TABS);
|
||||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||
toggleStyleClass(tabs, DENSE);
|
||||
}
|
||||
|
||||
tabs.setTabClosingPolicy(ALL_TABS);
|
||||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||
toggleStyleClass(tabs, DENSE);
|
||||
return tabs;
|
||||
}
|
||||
|
||||
|
@ -257,12 +255,6 @@ public class BrowserComp extends SimpleComp {
|
|||
|
||||
tab.setGraphic(label);
|
||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(label));
|
||||
|
||||
if (!this.model.getMode().equals(BrowserModel.Mode.BROWSER)) {
|
||||
label.setManaged(false);
|
||||
label.setVisible(false);
|
||||
}
|
||||
|
||||
tab.setContent(new OpenFileSystemComp(model).createSimple());
|
||||
return tab;
|
||||
}
|
||||
|
|
|
@ -4,30 +4,34 @@ import io.xpipe.app.browser.action.BranchAction;
|
|||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class BrowserContextMenu extends ContextMenu {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final boolean empty;
|
||||
private final BrowserEntry source;
|
||||
|
||||
public BrowserContextMenu(OpenFileSystemModel model, boolean empty) {
|
||||
super();
|
||||
public BrowserContextMenu(OpenFileSystemModel model, BrowserEntry source) {
|
||||
this.model = model;
|
||||
this.empty = empty;
|
||||
this.source = source;
|
||||
createMenu();
|
||||
}
|
||||
|
||||
private void createMenu() {
|
||||
AppFont.normal(this.getStyleableNode());
|
||||
|
||||
var selected = empty || model.getFileList().getSelected().isEmpty()
|
||||
? FXCollections.observableArrayList(
|
||||
new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false))
|
||||
: model.getFileList().getSelected();
|
||||
var empty = source == null;
|
||||
var selected = new ArrayList<>(empty ? List.of() : model.getFileList().getSelection());
|
||||
if (source != null && !selected.contains(source)) {
|
||||
selected.add(source);
|
||||
} else if (source == null && model.getFileList().getSelection().isEmpty()) {
|
||||
selected.add(new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false));
|
||||
}
|
||||
|
||||
for (BrowserAction.Category cat : BrowserAction.Category.values()) {
|
||||
var all = BrowserAction.ALL.stream()
|
||||
|
|
|
@ -30,7 +30,10 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.control.skin.TableViewSkin;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
|
@ -126,25 +129,28 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private void prepareTableSelectionModel(TableView<BrowserEntry> table) {
|
||||
if (fileList.getMode().equals(BrowserModel.Mode.SINGLE_FILE_CHOOSER)
|
||||
|| fileList.getMode().equals(BrowserModel.Mode.DIRECTORY_CHOOSER)) {
|
||||
if (!fileList.getMode().isMultiple()) {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
} else {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
}
|
||||
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
var toSelect = new ArrayList<>(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.getRawFileEntry()
|
||||
.getPath()
|
||||
.equals(fileList.getFileSystemModel()
|
||||
.getCurrentParentDirectory()
|
||||
.getPath()))
|
||||
.toList();
|
||||
fileList.getSelected().setAll(toSelect);
|
||||
toSelect.removeIf(entry -> fileList.getFileSystemModel().getCurrentParentDirectory() != null
|
||||
&& entry.getRawFileEntry()
|
||||
.getPath()
|
||||
.equals(fileList.getFileSystemModel()
|
||||
.getCurrentParentDirectory()
|
||||
.getPath()));
|
||||
// Remove unsuitable selection
|
||||
toSelect.removeIf(browserEntry -> (browserEntry.getRawFileEntry().isDirectory()
|
||||
&& !fileList.getMode().isAcceptsDirectories())
|
||||
|| (!browserEntry.getRawFileEntry().isDirectory()
|
||||
&& !fileList.getMode().isAcceptsFiles()));
|
||||
fileList.getSelection().setAll(toSelect);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var toUnselect = table.getSelectionModel().getSelectedItems().stream()
|
||||
|
@ -155,7 +161,7 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
fileList.getSelected().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
if (c.getList().equals(table.getSelectionModel().getSelectedItems())) {
|
||||
return;
|
||||
}
|
||||
|
@ -178,7 +184,7 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
|
||||
private void prepareTableShortcuts(TableView<BrowserEntry> table) {
|
||||
table.setOnKeyPressed(event -> {
|
||||
var selected = fileList.getSelected();
|
||||
var selected = fileList.getSelection();
|
||||
BrowserAction.getFlattened().stream()
|
||||
.filter(browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected)
|
||||
&& browserAction.isActive(fileList.getFileSystemModel(), selected))
|
||||
|
@ -218,7 +224,7 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
return null;
|
||||
}
|
||||
|
||||
return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem() == null);
|
||||
return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem());
|
||||
})
|
||||
.augment(new SimpleCompStructure<>(row));
|
||||
var listEntry = Bindings.createObjectBinding(
|
||||
|
|
|
@ -32,7 +32,7 @@ public class BrowserFileListCompEntry {
|
|||
@SuppressWarnings("unchecked")
|
||||
public void onMouseClick(MouseEvent t) {
|
||||
if (item == null) {
|
||||
model.getSelected().clear();
|
||||
model.getSelection().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class BrowserFileListCompEntry {
|
|||
var max = tv.getSelectionModel().getSelectedItems().stream().mapToInt(entry -> all.indexOf(entry)).max().orElse(all.size() - 1);
|
||||
var end = all.indexOf(item);
|
||||
var start = end > min ? min : max;
|
||||
model.getSelected().setAll(all.subList(Math.min(start, end), Math.max(start, end) + 1));
|
||||
model.getSelection().setAll(all.subList(Math.min(start, end), Math.max(start, end) + 1));
|
||||
t.consume();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ public final class BrowserFileListModel {
|
|||
private final Property<List<BrowserEntry>> shown = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final ObjectProperty<Predicate<BrowserEntry>> predicateProperty =
|
||||
new SimpleObjectProperty<>(path -> true);
|
||||
private final ObservableList<BrowserEntry> selected = FXCollections.observableArrayList();
|
||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||
private final ObservableList<FileSystem.FileEntry> selectedRaw =
|
||||
BindingsHelper.mappedContentBinding(selected, entry -> entry.getRawFileEntry());
|
||||
BindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
|
||||
|
||||
private final Property<BrowserEntry> draggedOverDirectory = new SimpleObjectProperty<BrowserEntry>();
|
||||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
@ -10,6 +11,7 @@ import javafx.collections.ObservableList;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -19,14 +21,36 @@ public class BrowserModel {
|
|||
|
||||
public BrowserModel(Mode mode) {
|
||||
this.mode = mode;
|
||||
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static enum Mode {
|
||||
BROWSER,
|
||||
SINGLE_FILE_CHOOSER,
|
||||
SINGLE_FILE_SAVE,
|
||||
MULTIPLE_FILE_CHOOSER,
|
||||
DIRECTORY_CHOOSER
|
||||
BROWSER(false, true, true, true),
|
||||
SINGLE_FILE_CHOOSER(true, false, true, false),
|
||||
SINGLE_FILE_SAVE(true, false, true, false),
|
||||
MULTIPLE_FILE_CHOOSER(true, true, true, false),
|
||||
SINGLE_DIRECTORY_CHOOSER(true, false, false, true),
|
||||
MULTIPLE_DIRECTORY_CHOOSER(true, true, false, true);
|
||||
|
||||
private final boolean chooser;
|
||||
private final boolean multiple;
|
||||
private final boolean acceptsFiles;
|
||||
private final boolean acceptsDirectories;
|
||||
|
||||
Mode(boolean chooser, boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
|
||||
this.chooser = chooser;
|
||||
this.multiple = multiple;
|
||||
this.acceptsFiles = acceptsFiles;
|
||||
this.acceptsDirectories = acceptsDirectories;
|
||||
}
|
||||
}
|
||||
|
||||
public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER);
|
||||
|
@ -39,19 +63,23 @@ public class BrowserModel {
|
|||
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
|
||||
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel();
|
||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||
|
||||
public void finishChooser() {
|
||||
if (getMode().equals(Mode.BROWSER)) {
|
||||
if (!getMode().isChooser()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
var selectedFiles = openFileSystems.get(0).getFileList().getSelected();
|
||||
closeFileSystem(openFileSystems.get(0));
|
||||
var chosen = new ArrayList<>(selection);
|
||||
for (OpenFileSystemModel openFileSystem : openFileSystems) {
|
||||
closeFileSystemAsync(openFileSystem);
|
||||
}
|
||||
|
||||
if (selectedFiles.size() == 0) {
|
||||
if (chosen.size() == 0) {
|
||||
return;
|
||||
}
|
||||
var stores = selectedFiles.stream()
|
||||
|
||||
var stores = chosen.stream()
|
||||
.map(entry -> new FileStore(
|
||||
entry.getRawFileEntry().getFileSystem().getStore(),
|
||||
entry.getRawFileEntry().getPath()))
|
||||
|
@ -59,7 +87,7 @@ public class BrowserModel {
|
|||
onFinish.accept(stores);
|
||||
}
|
||||
|
||||
public void closeFileSystem(OpenFileSystemModel open) {
|
||||
public void closeFileSystemAsync(OpenFileSystemModel open) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
if (Objects.equals(selected.getValue(), open)) {
|
||||
selected.setValue(null);
|
||||
|
|
|
@ -31,8 +31,8 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
}, cc);
|
||||
|
||||
var selectedCount = PlatformThread.sync(Bindings.createIntegerBinding(() -> {
|
||||
return model.getFileList().getSelected().size();
|
||||
}, model.getFileList().getSelected()));
|
||||
return model.getFileList().getSelection().size();
|
||||
}, model.getFileList().getSelection()));
|
||||
|
||||
var allCount = PlatformThread.sync(Bindings.createIntegerBinding(() -> {
|
||||
return (int) model.getFileList().getAll().getValue().stream().filter(entry -> !entry.isSynthetic()).count();
|
||||
|
@ -60,7 +60,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
AppFont.small(bar);
|
||||
|
||||
// Use status bar as an extension of file list
|
||||
new ContextMenuAugment<>(false, () -> new BrowserContextMenu(model, true)).augment(new SimpleCompStructure<>(bar));
|
||||
new ContextMenuAugment<>(false, () -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(bar));
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
terminalBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory()));
|
||||
|
||||
var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open"));
|
||||
new ContextMenuAugment<>(true, () -> new BrowserContextMenu(model, true)).augment(new SimpleCompStructure<>(menuButton));
|
||||
new ContextMenuAugment<>(true, () -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(menuButton));
|
||||
|
||||
var filter = new BrowserFilterComp(model.getFilter()).createStructure();
|
||||
Shortcuts.addShortcut(filter.toggleButton(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN));
|
||||
|
@ -69,9 +69,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
var directoryView = new BrowserFileListComp(model.getFileList()).createRegion();
|
||||
|
||||
var root = new VBox(topBar, directoryView);
|
||||
if (model.getBrowserModel().getMode() == BrowserModel.Mode.BROWSER) {
|
||||
root.getChildren().add(new BrowserStatusBarComp(model).createRegion());
|
||||
}
|
||||
root.getChildren().add(new BrowserStatusBarComp(model).createRegion());
|
||||
VBox.setVgrow(directoryView, Priority.ALWAYS);
|
||||
root.setPadding(Insets.EMPTY);
|
||||
model.getFileList().predicateProperty().set(PREDICATE_NOT_HIDDEN);
|
||||
|
|
|
@ -73,7 +73,7 @@ public class App extends Application {
|
|||
var titleBinding = Bindings.createStringBinding(
|
||||
() -> {
|
||||
var base = String.format(
|
||||
"xpipe Desktop (%s)", AppProperties.get().getVersion());
|
||||
"XPipe Desktop (%s)", AppProperties.get().getVersion());
|
||||
var prefix = AppProperties.get().isStaging() ? "[STAGE] " : "";
|
||||
var suffix = XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null
|
||||
? String.format(
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.xpipe.core.process.ShellDialect;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenInNativeManagerAction implements LeafAction {
|
||||
public class BrowseInNativeManagerAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
|
||||
|
@ -19,7 +19,11 @@ public class OpenInNativeManagerAction implements LeafAction {
|
|||
var e = entry.getRawFileEntry().getPath();
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> {
|
||||
sc.executeSimpleCommand("explorer " + d.fileArgument(e));
|
||||
if (entry.getRawFileEntry().isDirectory()) {
|
||||
sc.executeSimpleCommand("explorer " + d.fileArgument(e));
|
||||
} else {
|
||||
sc.executeSimpleCommand("explorer /select," + d.fileArgument(e));
|
||||
}
|
||||
}
|
||||
case OsType.Linux linux -> {
|
||||
var action = entry.getRawFileEntry().isDirectory() ? "org.freedesktop.FileManager1.ShowFolders" : "org.freedesktop.FileManager1.ShowItems";
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserEntry;
|
||||
import io.xpipe.app.browser.BrowserModel;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
|
@ -31,7 +30,7 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
|
|||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory()) && model.getBrowserModel().getMode() == BrowserModel.Mode.BROWSER;
|
||||
return entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,7 +33,7 @@ open module io.xpipe.ext.base {
|
|||
OpenDirectoryInNewTabAction,
|
||||
OpenTerminalAction,
|
||||
OpenNativeFileDetailsAction,
|
||||
OpenInNativeManagerAction,
|
||||
BrowseInNativeManagerAction,
|
||||
EditFileAction,
|
||||
RunAction,
|
||||
CopyAction,
|
||||
|
|
Loading…
Reference in a new issue