Improve filter shortcuts and sftp handling

This commit is contained in:
crschnick
2025-11-19 20:59:10 +00:00
parent 8b486c8d84
commit fee3c8ca34
16 changed files with 142 additions and 7 deletions

View File

@@ -14,10 +14,12 @@ import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.hub.comp.StoreEntryWrapper;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ObservableSubscriber;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.FilePath;
@@ -25,6 +27,9 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
@@ -116,7 +121,8 @@ public class BrowserFileChooserSessionComp extends ModalOverlayContentComp {
var category = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue());
var filter = new SimpleStringProperty();
var bookmarkTopBar = new BrowserConnectionListFilterComp(category, filter);
var filterTrigger = new ObservableSubscriber();
var bookmarkTopBar = new BrowserConnectionListFilterComp(filterTrigger, category, filter);
var bookmarksList = new BrowserConnectionListComp(
BindingsHelper.map(
model.getSelectedEntry(), v -> v != null ? v.getEntry().get() : null),
@@ -147,6 +153,10 @@ public class BrowserFileChooserSessionComp extends ModalOverlayContentComp {
}
});
});
InputHelper.onKeyCombination(s, new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), false, keyEvent -> {
filterTrigger.trigger();
keyEvent.consume();
});
return s;
});

View File

@@ -13,7 +13,9 @@ import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.hub.comp.StoreEntryWrapper;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.util.ObservableSubscriber;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
@@ -23,6 +25,9 @@ import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
@@ -42,7 +47,8 @@ public class BrowserFullSessionComp extends SimpleComp {
@Override
protected Region createSimple() {
var left = Comp.of(() -> createLeftSide());
var filterTrigger = new ObservableSubscriber();
var left = Comp.of(() -> createLeftSide(filterTrigger));
var leftSplit = new SimpleDoubleProperty();
var rightSplit = new SimpleDoubleProperty();
@@ -103,12 +109,18 @@ public class BrowserFullSessionComp extends SimpleComp {
}
});
});
splitPane.apply(struc -> {
InputHelper.onKeyCombination(struc.get(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), false, keyEvent -> {
filterTrigger.trigger();
keyEvent.consume();
});
});
splitPane.styleClass("browser");
var r = splitPane.createRegion();
return r;
}
private Region createLeftSide() {
private Region createLeftSide(ObservableSubscriber filterTrigger) {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
if (!storeEntryWrapper.getEntry().getValidity().isUsable()) {
return false;
@@ -138,7 +150,7 @@ public class BrowserFullSessionComp extends SimpleComp {
var category = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue());
var filter = new SimpleStringProperty();
var bookmarkTopBar = new BrowserConnectionListFilterComp(category, filter);
var bookmarkTopBar = new BrowserConnectionListFilterComp(filterTrigger, category, filter);
var bookmarksList = new BrowserConnectionListComp(
BindingsHelper.map(
model.getSelectedEntry(),

View File

@@ -8,6 +8,7 @@ import io.xpipe.app.hub.comp.DataStoreCategoryChoiceComp;
import io.xpipe.app.hub.comp.StoreCategoryWrapper;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.util.ObservableSubscriber;
import javafx.beans.property.Property;
import javafx.scene.layout.Region;
@@ -21,6 +22,7 @@ import java.util.List;
@AllArgsConstructor
public final class BrowserConnectionListFilterComp extends SimpleComp {
private final ObservableSubscriber filterTrigger;
private final Property<StoreCategoryWrapper> category;
private final Property<String> filter;
@@ -40,6 +42,9 @@ public final class BrowserConnectionListFilterComp extends SimpleComp {
.hgrow()
.apply(struc -> {
AppFontSizes.base(struc.get());
filterTrigger.subscribe(() -> {
struc.get().requestFocus();
});
});
var top = new HorizontalComp(List.of(category, filter))

View File

@@ -383,6 +383,12 @@ public class BrowserFileTransferOperation {
var fileSize = sourceFs.getFileSize(sourceFile);
updateProgress(new BrowserTransferProgress(sourceFile.getFileName(), 0, 0));
if (targetFs.writeInstantIfPossible(sourceFs, sourceFile, targetFile)) {
updateProgress(BrowserTransferProgress.finished(sourceFile.getFileName(), fileSize));
return;
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {

View File

@@ -100,6 +100,7 @@ public class BrowserStatusBarComp extends SimpleComp {
return null;
}
// Handle unknown transfers
if (p.getTotal() == 0) {
return HumanReadableFormat.byteCount(p.getTransferred());
}
@@ -132,6 +133,16 @@ public class BrowserStatusBarComp extends SimpleComp {
return null;
} else {
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
// Handle unknown transfers
if (p.getTotal() == 0) {
if (p.getTransferred() == 0) {
return "...";
} else {
return transferred;
}
}
var all = HumanReadableFormat.byteCount(p.getTotal());
return transferred + " / " + all;
}

View File

@@ -90,6 +90,7 @@ public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
}
PlatformThread.runNestedLoopIteration();
sidebar.setDisable(false);
stack.requestFocus();
}
@Override

View File

@@ -33,6 +33,11 @@ public class DelayedInitComp extends SimpleComp {
});
return true;
});
stack.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue && !stack.getChildren().isEmpty()) {
stack.getChildren().getFirst().requestFocus();
}
});
return stack;
}
}

View File

@@ -60,7 +60,6 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
filter.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
filter.clear();
filter.getScene().getRoot().requestFocus();
event.consume();
}
});

View File

@@ -5,6 +5,7 @@ import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.platform.PlatformThread;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
@@ -36,6 +37,18 @@ public class MultiContentComp extends SimpleComp {
}
});
stack.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
var selected = content.entrySet().stream()
.filter(e -> e.getValue().getValue())
.map(e -> m.get(e.getKey()))
.findFirst();
if (selected.isPresent()) {
selected.get().requestFocus();
}
}
});
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> e : content.entrySet()) {
var name = e.getKey().getClass().getSimpleName();
if (log) {
@@ -49,6 +62,11 @@ public class MultiContentComp extends SimpleComp {
PlatformThread.runLaterIfNeeded(() -> {
r.setManaged(val);
r.setVisible(val);
if (val) {
Platform.runLater(() -> {
r.requestFocus();
});
}
});
});
m.put(e.getKey(), r);

View File

@@ -33,6 +33,11 @@ public class ConnectionFileSystem implements FileSystem {
this.shellControl = shellControl;
}
@Override
public boolean writeInstantIfPossible(FileSystem sourceFs, FilePath sourceFile, FilePath targetFile) {
return false;
}
@Override
public String getSuffix() {
return null;

View File

@@ -14,6 +14,8 @@ import java.util.stream.Stream;
public interface FileSystem extends Closeable, AutoCloseable {
boolean writeInstantIfPossible(FileSystem sourceFs, FilePath sourceFile, FilePath targetFile) throws Exception;
String getSuffix();
boolean isRunning();

View File

@@ -24,6 +24,11 @@ public class WrapperFileSystem implements FileSystem {
this.runningCheck = runningCheck;
}
@Override
public boolean writeInstantIfPossible(FileSystem sourceFs, FilePath sourceFile, FilePath targetFile) throws Exception {
return fs.writeInstantIfPossible(sourceFs, sourceFile, targetFile);
}
@Override
public String getSuffix() {
return fs.getSuffix();

View File

@@ -8,7 +8,9 @@ import io.xpipe.app.comp.base.IconButtonComp;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.util.ObservableSubscriber;
import io.xpipe.core.OsType;
import javafx.beans.binding.Bindings;
@@ -19,6 +21,9 @@ import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.Separator;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@@ -26,12 +31,17 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import atlantafx.base.theme.Styles;
import javafx.util.Subscription;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.function.Function;
public class StoreEntryListOverviewComp extends SimpleComp {
private final ObservableSubscriber filterTrigger;
public StoreEntryListOverviewComp(ObservableSubscriber filterTrigger) {this.filterTrigger = filterTrigger;}
private Region createGroupListHeader() {
var label = new Label();
var name = BindingsHelper.flatMap(
@@ -88,6 +98,9 @@ public class StoreEntryListOverviewComp extends SimpleComp {
private Region createGroupListFilter() {
var filter = new FilterComp(StoreViewState.get().getFilterString()).createRegion();
filterTrigger.subscribe(() -> {
filter.requestFocus();
});
var add = createAddButton();
var batchMode = createBatchModeButton().createRegion();
var hbox = new HBox(add, filter, batchMode);

View File

@@ -7,6 +7,11 @@ import io.xpipe.app.comp.base.LeftSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppMainWindow;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.util.ObservableSubscriber;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
public class StoreLayoutComp extends SimpleComp {
@@ -20,7 +25,8 @@ public class StoreLayoutComp extends SimpleComp {
}
private Region createContent() {
var left = new StoreSidebarComp();
var filterTrigger = new ObservableSubscriber();
var left = new StoreSidebarComp(filterTrigger);
left.hide(AppMainWindow.get().getStage().widthProperty().lessThan(1000));
left.minWidth(270);
left.maxWidth(500);
@@ -35,6 +41,12 @@ public class StoreLayoutComp extends SimpleComp {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
});
comp.styleClass("store-layout");
comp.apply(struc -> {
InputHelper.onKeyCombination(struc.get(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), false, keyEvent -> {
filterTrigger.trigger();
keyEvent.consume();
});
});
return comp.createRegion();
}
}

View File

@@ -4,16 +4,21 @@ import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.comp.base.VerticalComp;
import io.xpipe.app.util.ObservableSubscriber;
import javafx.scene.layout.Region;
import java.util.List;
public class StoreSidebarComp extends SimpleComp {
private final ObservableSubscriber filterTrigger;
public StoreSidebarComp(ObservableSubscriber filterTrigger) {this.filterTrigger = filterTrigger;}
@Override
protected Region createSimple() {
var sideBar = new VerticalComp(List.of(
new StoreEntryListOverviewComp()
new StoreEntryListOverviewComp(filterTrigger)
.styleClass("color-box")
.styleClass("gray")
.styleClass("bar"),

View File

@@ -0,0 +1,26 @@
package io.xpipe.app.util;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class ObservableSubscriber implements Observable {
private final IntegerProperty property = new SimpleIntegerProperty();
public void trigger() {
property.set(property.get() + 1);
property.getValue();
}
@Override
public void addListener(InvalidationListener listener) {
property.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
property.removeListener(listener);
}
}