Various fixes [stage]

This commit is contained in:
crschnick 2023-05-30 01:59:08 +00:00
parent 52cdcfa0aa
commit daa011ffe6
24 changed files with 380 additions and 92 deletions

View file

@ -2,6 +2,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.browser.icon.BrowserIcons; import io.xpipe.app.browser.icon.BrowserIcons;
import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.VBoxViewComp;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.augment.GrowAugment;
@ -13,16 +14,19 @@ import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import java.util.function.Function;
@Value @Value
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class BrowserFileOverviewComp extends SimpleComp { public class BrowserFileOverviewComp extends SimpleComp {
OpenFileSystemModel model; OpenFileSystemModel model;
ObservableList<FileSystem.FileEntry> list; ObservableList<FileSystem.FileEntry> list;
boolean grow;
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var c = new ListBoxViewComp<>(list, list, entry -> { Function<FileSystem.FileEntry, Comp<?>> factory = entry -> {
return Comp.of(() -> { return Comp.of(() -> {
var icon = BrowserIcons.createIcon(entry); var icon = BrowserIcons.createIcon(entry);
var l = new Button(entry.getPath(), icon.createRegion()); var l = new Button(entry.getPath(), icon.createRegion());
@ -34,8 +38,14 @@ public class BrowserFileOverviewComp extends SimpleComp {
GrowAugment.create(true,false).augment(l); GrowAugment.create(true,false).augment(l);
return l; return l;
}); });
}) };
.styleClass("overview-file-list");
return c.createRegion(); if (grow) {
var c = new ListBoxViewComp<>(list, list, factory).styleClass("overview-file-list");
return c.createRegion();
} else {
var c = new VBoxViewComp<>(list, list, factory).styleClass("overview-file-list");
return c.createRegion();
}
} }
} }

View file

@ -18,6 +18,9 @@ import javafx.css.PseudoClass;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
@ -67,6 +70,8 @@ public class BrowserNavBar extends SimpleComp {
}); });
struc.get().setPromptText("Overview of " + model.getName()); struc.get().setPromptText("Overview of " + model.getName());
}).shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
s.get().requestFocus();
}); });
var graphic = Bindings.createStringBinding( var graphic = Bindings.createStringBinding(

View file

@ -5,10 +5,13 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.List; import java.util.List;
@ -25,18 +28,34 @@ public class BrowserOverviewComp extends SimpleComp {
@SneakyThrows @SneakyThrows
protected Region createSimple() { protected Region createSimple() {
ShellControl sc = model.getFileSystem().getShell().orElseThrow(); ShellControl sc = model.getFileSystem().getShell().orElseThrow();
var common = sc.getOsType().determineInterestingPaths(sc).stream().map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s)).toList(); // TODO: May be move this into another thread
var commonOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(common)); var common = sc.getOsType().determineInterestingPaths(sc).stream()
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview); .map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.filter(entry -> {
try {
var b = sc.getShellDialect().directoryExists(sc, entry.getPath()).executeAndCheck();
return b;
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList();
var commonOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(common), false);
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview).apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));
var roots = sc.getShellDialect().listRoots(sc).map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s)).toList(); var roots = sc.getShellDialect()
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots)); .listRoots(sc)
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.toList();
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview); var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
var recent = BindingsHelper.mappedContentBinding(
var recent = BindingsHelper.mappedContentBinding(model.getSavedState().getRecentDirectories(), s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())); model.getSavedState().getRecentDirectories(),
var recentOverview = new BrowserFileOverviewComp(model, recent); s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview).vgrow(); var recentOverview = new BrowserFileOverviewComp(model, recent, true);
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
var vbox = new VerticalComp(List.of(commonPane, rootsPane, recentPane)).styleClass("overview"); var vbox = new VerticalComp(List.of(commonPane, rootsPane, recentPane)).styleClass("overview");
return vbox.createRegion(); return vbox.createRegion();

View file

@ -46,23 +46,10 @@ public class OpenFileSystemComp extends SimpleComp {
overview.setOnAction(e -> model.cd(null)); overview.setOnAction(e -> model.cd(null));
overview.disableProperty().bind(model.getInOverview()); overview.disableProperty().bind(model.getInOverview());
var backBtn = new Button(null, new FontIcon("fth-arrow-left")); var backBtn = BrowserAction.byId("back").toButton(model, List.of());
backBtn.setOnAction(e -> model.back()); var forthBtn = BrowserAction.byId("forward").toButton(model, List.of());
backBtn.disableProperty().bind(model.getHistory().canGoBackProperty().not()); var refreshBtn = BrowserAction.byId("refresh").toButton(model, List.of());
var forthBtn = new Button(null, new FontIcon("fth-arrow-right"));
forthBtn.setOnAction(e -> model.forth());
forthBtn.disableProperty().bind(model.getHistory().canGoForthProperty().not());
var refreshBtn = new Button(null, new FontIcon("mdmz-refresh"));
refreshBtn.setOnAction(e -> model.refresh());
Shortcuts.addShortcut(refreshBtn, new KeyCodeCombination(KeyCode.F5));
refreshBtn.disableProperty().bind(model.getInOverview());
var terminalBtn = BrowserAction.byId("openTerminal").toButton(model, List.of()); var terminalBtn = BrowserAction.byId("openTerminal").toButton(model, List.of());
terminalBtn.setOnAction(
e -> model.openTerminalAsync(model.getCurrentPath().get()));
terminalBtn.disableProperty().bind(model.getInOverview());
var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open")); var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open"));
new ContextMenuAugment<>( new ContextMenuAugment<>(

View file

@ -9,7 +9,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
final class OpenFileSystemHistory { public final class OpenFileSystemHistory {
private final IntegerProperty cursor = new SimpleIntegerProperty(-1); private final IntegerProperty cursor = new SimpleIntegerProperty(-1);
private final List<String> history = new ArrayList<>(); private final List<String> history = new ArrayList<>();

View file

@ -22,7 +22,6 @@ import org.apache.commons.lang3.function.FailableConsumer;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -54,7 +53,6 @@ public final class OpenFileSystemModel {
return currentPath.get() == null; return currentPath.get() == null;
}, currentPath)); }, currentPath));
fileList = new BrowserFileListModel(this); fileList = new BrowserFileListModel(this);
addListeners();
} }
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) { public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
@ -74,17 +72,6 @@ public final class OpenFileSystemModel {
}); });
} }
private void addListeners() {
// savedState.addListener((observable, oldValue, newValue) -> {
// if (store == null) {
// return;
// }
//
// var storageEntry = DataStorage.get().getStoreEntryIfPresent(store);
// storageEntry.ifPresent(entry -> AppCache.update("browser-state-" + entry.getUuid(), newValue));
// });
}
@SneakyThrows @SneakyThrows
public void refresh() { public void refresh() {
BusyProperty.execute(busy, () -> { BusyProperty.execute(busy, () -> {
@ -194,9 +181,9 @@ public final class OpenFileSystemModel {
// path = FileSystemHelper.normalizeDirectoryPath(this, path); // path = FileSystemHelper.normalizeDirectoryPath(this, path);
filter.setValue(null); filter.setValue(null);
currentPath.set(path);
savedState.cd(path); savedState.cd(path);
history.updateCurrent(path); history.updateCurrent(path);
currentPath.set(path);
loadFilesSync(path); loadFilesSync(path);
} }
@ -206,10 +193,7 @@ public final class OpenFileSystemModel {
var stream = getFileSystem().listFiles(dir); var stream = getFileSystem().listFiles(dir);
fileList.setAll(stream); fileList.setAll(stream);
} else { } else {
var stream = getFileSystem().listRoots().stream() fileList.setAll(Stream.of());
.map(s -> new FileSystem.FileEntry(
getFileSystem(), s, Instant.now(), true, false, false, 0, null));
fileList.setAll(stream);
} }
return true; return true;
} catch (Exception e) { } catch (Exception e) {
@ -314,6 +298,10 @@ public final class OpenFileSystemModel {
fileSystem = null; fileSystem = null;
} }
public boolean isClosed() {
return fileSystem == null;
}
public void initFileSystem() throws Exception { public void initFileSystem() throws Exception {
BusyProperty.execute(busy, () -> { BusyProperty.execute(busy, () -> {
var fs = store.createFileSystem(); var fs = store.createFileSystem();
@ -337,7 +325,7 @@ public final class OpenFileSystemModel {
} }
private void initState() { private void initState() {
this.savedState = OpenFileSystemSavedState.loadForStore(store); this.savedState = OpenFileSystemSavedState.loadForStore(this);
} }
public void openTerminalAsync(String directory) { public void openTerminalAsync(String directory) {
@ -365,15 +353,11 @@ public final class OpenFileSystemModel {
return history; return history;
} }
public void back() { public void backSync() throws Exception {
try (var ignored = new BusyProperty(busy)) { cdSyncWithoutCheck(history.back());
cd(history.back());
}
} }
public void forth() { public void forthSync() throws Exception {
try (var ignored = new BusyProperty(busy)) { cdSyncWithoutCheck(history.forth());
cd(history.forth());
}
} }
} }

View file

@ -14,7 +14,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppCache;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.JacksonMapper;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -63,15 +62,15 @@ public class OpenFileSystemSavedState {
} }
} }
static OpenFileSystemSavedState loadForStore(FileSystemStore store) { static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
var storageEntry = DataStorage.get() var storageEntry = DataStorage.get()
.getStoreEntryIfPresent(store) .getStoreEntryIfPresent(model.getStore())
.map(entry -> entry.getUuid()) .map(entry -> entry.getUuid())
.orElse(UUID.randomUUID()); .orElse(UUID.randomUUID());
var state = AppCache.get("fs-state-" + storageEntry, OpenFileSystemSavedState.class, () -> { var state = AppCache.get("fs-state-" + storageEntry, OpenFileSystemSavedState.class, () -> {
return new OpenFileSystemSavedState(); return new OpenFileSystemSavedState();
}); });
state.store = store; state.setModel(model);
return state; return state;
} }
@ -84,7 +83,8 @@ public class OpenFileSystemSavedState {
Instant time; Instant time;
} }
private FileSystemStore store; @Setter
private OpenFileSystemModel model;
private String lastDirectory; private String lastDirectory;
@NonNull @NonNull
private ObservableList<RecentEntry> recentDirectories; private ObservableList<RecentEntry> recentDirectories;
@ -103,11 +103,11 @@ public class OpenFileSystemSavedState {
} }
public void save() { public void save() {
if (store == null) { if (model == null) {
return; return;
} }
var storageEntry = DataStorage.get().getStoreEntryIfPresent(store); var storageEntry = DataStorage.get().getStoreEntryIfPresent(model.getStore());
storageEntry.ifPresent(entry -> AppCache.update("fs-state-" + entry.getUuid(), this)); storageEntry.ifPresent(entry -> AppCache.update("fs-state-" + entry.getUuid(), this));
} }
@ -123,6 +123,10 @@ public class OpenFileSystemSavedState {
public void run() { public void run() {
// Synchronize with platform thread // Synchronize with platform thread
Platform.runLater(() -> { Platform.runLater(() -> {
if (model.isClosed()) {
return;
}
if (Objects.equals(lastDirectory, dir)) { if (Objects.equals(lastDirectory, dir)) {
updateRecent(dir); updateRecent(dir);
save(); save();
@ -130,7 +134,7 @@ public class OpenFileSystemSavedState {
}); });
} }
}, },
20000); 200);
} }
private void updateRecent(String dir) { private void updateRecent(String dir) {

View file

@ -36,7 +36,12 @@ public interface LeafAction extends BrowserAction {
b.setGraphic(graphic); b.setGraphic(graphic);
} }
b.setMnemonicParsing(false); b.setMnemonicParsing(false);
b.setDisable(!isActive(model, selected)); b.setDisable(!isActive(model, selected));
model.getCurrentPath().addListener((observable, oldValue, newValue) -> {
b.setDisable(!isActive(model, selected));
});
return b; return b;
} }

View file

@ -5,10 +5,7 @@ import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.comp.about.AboutTabComp; import io.xpipe.app.comp.about.AboutTabComp;
import io.xpipe.app.comp.base.SideMenuBarComp; import io.xpipe.app.comp.base.SideMenuBarComp;
import io.xpipe.app.comp.storage.store.StoreLayoutComp; import io.xpipe.app.comp.storage.store.StoreLayoutComp;
import io.xpipe.app.core.AppActionLinkDetector; import io.xpipe.app.core.*;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure;
@ -33,7 +30,7 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
public AppLayoutComp() { public AppLayoutComp() {
entries = createEntryList(); entries = createEntryList();
selected = new SimpleObjectProperty<>(entries.get(0)); selected = new SimpleObjectProperty<>(AppState.get().isInitialLaunch() ? entries.get(1) : entries.get(0));
shortcut(new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN), structure -> { shortcut(new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN), structure -> {
AppActionLinkDetector.detectOnPaste(); AppActionLinkDetector.detectOnPaste();
@ -43,11 +40,11 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
@SneakyThrows @SneakyThrows
private List<SideMenuBarComp.Entry> createEntryList() { private List<SideMenuBarComp.Entry> createEntryList() {
var l = new ArrayList<>(List.of( var l = new ArrayList<>(List.of(
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
new SideMenuBarComp.Entry( new SideMenuBarComp.Entry(
AppI18n.observable("browser"), AppI18n.observable("browser"),
"mdi2f-file-cabinet", "mdi2f-file-cabinet",
new BrowserComp(BrowserModel.DEFAULT)), new BrowserComp(BrowserModel.DEFAULT)),
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
// new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()), // new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
new SideMenuBarComp.Entry( new SideMenuBarComp.Entry(
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)), AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),

View file

@ -0,0 +1,75 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class VBoxViewComp<T> extends Comp<CompStructure<VBox>> {
private final ObservableList<T> shown;
private final ObservableList<T> all;
private final Function<T, Comp<?>> compFunction;
public VBoxViewComp(ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction) {
this.shown = PlatformThread.sync(shown);
this.all = PlatformThread.sync(all);
this.compFunction = compFunction;
}
@Override
public CompStructure<VBox> createBase() {
Map<T, Region> cache = new HashMap<>();
VBox vbox = new VBox();
vbox.setFocusTraversable(false);
refresh(vbox, shown, cache, false);
vbox.requestLayout();
shown.addListener((ListChangeListener<? super T>) (c) -> {
refresh(vbox, c.getList(), cache, true);
});
all.addListener((ListChangeListener<? super T>) c -> {
cache.keySet().retainAll(c.getList());
});
return new SimpleCompStructure<>(vbox);
}
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
Runnable update = () -> {
var newShown = c.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, compFunction.apply(v).createRegion());
}
return cache.get(v);
})
.toList();
if (!listView.getChildren().equals(newShown)) {
listView.getChildren().setAll(newShown);
listView.layout();
}
};
if (asynchronous) {
Platform.runLater(update);
} else {
PlatformThread.runLaterIfNeeded(update);
}
}
}

View file

@ -32,8 +32,8 @@ public class StoreEntryListHeaderComp extends SimpleComp {
} }
private Region createGroupListFilter() { private Region createGroupListFilter() {
var filledHerProperty = new SimpleStringProperty(); var filterProperty = new SimpleStringProperty();
filledHerProperty.addListener((observable, oldValue, newValue) -> { filterProperty.addListener((observable, oldValue, newValue) -> {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
StoreViewState.get().getFilter().filterProperty().setValue(newValue); StoreViewState.get().getFilter().filterProperty().setValue(newValue);
}); });

View file

@ -22,6 +22,7 @@ import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -84,7 +85,15 @@ public class AppLogs {
var logDir = AppProperties.get().getDataDir().resolve("logs"); var logDir = AppProperties.get().getDataDir().resolve("logs");
var shouldLogToFile = shouldWriteLogs(); var shouldLogToFile = shouldWriteLogs();
Path usedLogsDir = logDir.resolve(FORMATTER.format(Instant.now())); var now = Instant.now();
var name = FORMATTER.format(now);
Path usedLogsDir = logDir.resolve(name);
// When two instances are being launched within the same second, add milliseconds
if (Files.exists(usedLogsDir)) {
usedLogsDir = logDir.resolve(name + "_" + now.get(ChronoField.MILLI_OF_SECOND));
}
if (shouldLogToFile) { if (shouldLogToFile) {
try { try {
Files.createDirectories(usedLogsDir); Files.createDirectories(usedLogsDir);

View file

@ -28,6 +28,7 @@ public class FilterComp extends Comp<FilterComp.Structure> {
var bgLabel = new Label("Search ...", fi); var bgLabel = new Label("Search ...", fi);
bgLabel.getStyleClass().add("background"); bgLabel.getStyleClass().add("background");
var filter = new TextField(); var filter = new TextField();
filter.setAccessibleText("Filter");
SimpleChangeListener.apply(filterText, val -> { SimpleChangeListener.apply(filterText, val -> {
PlatformThread.runLaterIfNeeded(() -> filter.setText(val)); PlatformThread.runLaterIfNeeded(() -> filter.setText(val));

View file

@ -46,7 +46,7 @@ public class Shortcuts {
if (s != null) { if (s != null) {
scene.set(s); scene.set(s);
SHORTCUTS.put(region, comb); s.addEventHandler(KeyEvent.KEY_PRESSED, filter);
} }
}); });
} }

View file

@ -105,9 +105,6 @@
.browser .path-text:invisible { .browser .path-text:invisible {
-fx-text-fill: transparent; -fx-text-fill: transparent;
} }
.browser .path-graphic-button {
-fx-padding: 0 5px 0 5px;
}
.browser .overview-file-list { .browser .overview-file-list {
-fx-border-width: 1px; -fx-border-width: 1px;

View file

@ -53,7 +53,11 @@ public sealed interface OsType permits OsType.Windows, OsType.Linux, OsType.MacO
@Override @Override
public List<String> determineInterestingPaths(ShellControl pc) throws Exception { public List<String> determineInterestingPaths(ShellControl pc) throws Exception {
var home = getHomeDirectory(pc); var home = getHomeDirectory(pc);
return List.of(home, FileNames.join(home, "Documents"), FileNames.join(home, "Downloads"), FileNames.join(home, "Desktop")); return List.of(
home,
FileNames.join(home, "Documents"),
FileNames.join(home, "Downloads"),
FileNames.join(home, "Desktop"));
} }
@Override @Override
@ -111,7 +115,8 @@ public sealed interface OsType permits OsType.Windows, OsType.Linux, OsType.MacO
@Override @Override
public List<String> determineInterestingPaths(ShellControl pc) throws Exception { public List<String> determineInterestingPaths(ShellControl pc) throws Exception {
var home = getHomeDirectory(pc); var home = getHomeDirectory(pc);
return List.of(FileNames.join(home, "Desktop")); return List.of(
home, FileNames.join(home, "Downloads"), FileNames.join(home, "Documents"), "/etc", "/tmp", "/var");
} }
@Override @Override
@ -180,7 +185,16 @@ public sealed interface OsType permits OsType.Windows, OsType.Linux, OsType.MacO
@Override @Override
public List<String> determineInterestingPaths(ShellControl pc) throws Exception { public List<String> determineInterestingPaths(ShellControl pc) throws Exception {
var home = getHomeDirectory(pc); var home = getHomeDirectory(pc);
return List.of(FileNames.join(home, "Desktop")); return List.of(
home,
FileNames.join(home, "Downloads"),
FileNames.join(home, "Documents"),
FileNames.join(home, "Desktop"),
"/Applications",
"/Library",
"/System",
"/etc"
);
} }
@Override @Override

View file

@ -6,35 +6,40 @@ import lombok.Getter;
public class ProcessOutputException extends Exception { public class ProcessOutputException extends Exception {
public static ProcessOutputException of(String customPrefix, ProcessOutputException ex) { public static ProcessOutputException of(String customPrefix, ProcessOutputException ex) {
var messageSuffix = ex.getOutput() != null && ! ex.getOutput().isBlank()?": " + ex.getOutput() : ""; var messageSuffix = ex.getOutput() != null && !ex.getOutput().isBlank() ? ": " + ex.getOutput() : "";
var message = customPrefix + messageSuffix; var message = customPrefix + messageSuffix;
return new ProcessOutputException(message, ex.getExitCode(), ex.getOutput()); return new ProcessOutputException(message, ex.getExitCode(), ex.getOutput());
} }
public static ProcessOutputException of(int exitCode, String output, String accumulatedError) { public static ProcessOutputException of(int exitCode, String output, String accumulatedError) {
var combinedError = (accumulatedError != null ? accumulatedError.trim() + "\n" : "") + (output != null ? output.trim() : ""); var combinedError = (accumulatedError != null ? accumulatedError.trim() + "\n" : "")
var message = switch (exitCode) { + (output != null ? output.trim() : "");
case CommandControl.KILLED_EXIT_CODE -> "Process timed out (exit code " + exitCode + ") " + combinedError; var hasMessage = !combinedError.trim().isEmpty();
case CommandControl.TIMEOUT_EXIT_CODE -> "Process timed out (exit code " + exitCode + ") " + combinedError; var errorSuffix = hasMessage ? ": " + combinedError : "";
default -> "Process returned with exit code " + exitCode + ": " + combinedError; var message =
}; switch (exitCode) {
case CommandControl.KILLED_EXIT_CODE -> "Process timed out and had to be killed" + errorSuffix;
case CommandControl.TIMEOUT_EXIT_CODE -> "Wait for process exit timed out"
+ errorSuffix;
default -> "Process returned exit code " + exitCode + errorSuffix;
};
return new ProcessOutputException(message, exitCode, combinedError); return new ProcessOutputException(message, exitCode, combinedError);
} }
private final int exitCode; private final int exitCode;
private final String output; private final String output;
private ProcessOutputException(String message, int exitCode, String output) { private ProcessOutputException(String message, int exitCode, String output) {
super(message); super(message);
this.exitCode = exitCode; this.exitCode = exitCode;
this.output = output; this.output = output;
} }
public boolean isTimeOut() { public boolean isTimeOut() {
return exitCode == CommandControl.TIMEOUT_EXIT_CODE; return exitCode == CommandControl.TIMEOUT_EXIT_CODE;
} }
public boolean isKill() { public boolean isKill() {
return exitCode == CommandControl.KILLED_EXIT_CODE; return exitCode == CommandControl.KILLED_EXIT_CODE;
} }
} }

5
dist/changelogs/1.0.2.md vendored Normal file
View file

@ -0,0 +1,5 @@
## Changes in 1.0.2
- Add new overview page for file browser sessions
- Fix file IO being corrupted and freezing on Windows systems with global UTF8 enabled
- Many small miscellaneous fixes and improvements

View file

@ -0,0 +1,54 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class BackAction implements LeafAction {
public String getId() {
return "back";
}
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
model.backSync();
}
@Override
public Category getCategory() {
return null;
}
@Override
public Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new FontIcon("fth-arrow-left");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return false;
}
@Override
public boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getHistory().canGoBackProperty().get();
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.LEFT, KeyCombination.ALT_DOWN);
}
@Override
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return "Back";
}
}

View file

@ -0,0 +1,54 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class ForwardAction implements LeafAction {
public String getId() {
return "forward";
}
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
model.forthSync();
}
@Override
public Category getCategory() {
return null;
}
@Override
public Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new FontIcon("fth-arrow-right");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return false;
}
@Override
public boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getHistory().canGoForthProperty().get();
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.ALT_DOWN);
}
@Override
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return "Forward";
}
}

View file

@ -29,6 +29,11 @@ public class OpenTerminalAction implements LeafAction {
} }
} }
@Override
public boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
return !model.getInOverview().get();
}
@Override @Override
public Category getCategory() { public Category getCategory() {
return Category.OPEN; return Category.OPEN;

View file

@ -0,0 +1,55 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class RefreshAction implements LeafAction {
public String getId() {
return "refresh";
}
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
model.refreshSync();
}
@Override
public BrowserAction.Category getCategory() {
return null;
}
@Override
public boolean isActive(OpenFileSystemModel model, List<BrowserEntry> entries) {
return !model.getInOverview().get();
}
@Override
public Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new FontIcon("mdmz-refresh");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return false;
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.F5);
}
@Override
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return "Refresh";
}
}

View file

@ -27,6 +27,9 @@ open module io.xpipe.ext.base {
requires com.sun.jna.platform; requires com.sun.jna.platform;
provides BrowserAction with provides BrowserAction with
BackAction,
ForwardAction,
RefreshAction,
OpenFileDefaultAction, OpenFileDefaultAction,
OpenFileWithAction, OpenFileWithAction,
OpenDirectoryAction, OpenDirectoryAction,

View file

@ -1 +1 @@
1.0.1 1.0.2