mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-22 07:29:05 -04:00
Improve filter shortcuts and sftp handling
This commit is contained in:
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
|
||||
}
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
sidebar.setDisable(false);
|
||||
stack.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user