Various small fixes [release]

This commit is contained in:
crschnick 2023-02-26 20:26:45 +00:00
parent 26a7592ed6
commit 417aa16f1d
40 changed files with 394 additions and 231 deletions

View file

@ -1,22 +1,23 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.RingProgressIndicator;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.store.FileSystem;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.*;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.*;
import java.util.HashMap;
@ -26,8 +27,6 @@ import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
public class FileBrowserComp extends SimpleComp {
private static final double TAB_MIN_HEIGHT = 60;
private final FileBrowserModel model;
public FileBrowserComp(FileBrowserModel model) {
@ -44,7 +43,40 @@ public class FileBrowserComp extends SimpleComp {
// set sidebar width in pixels depending on split pane width
(obs, old, val) -> splitPane.setDividerPosition(0, 230 / splitPane.getWidth()));
return splitPane;
return addBottomBar(splitPane);
}
private Region addBottomBar(Region r) {
if (model.getMode().equals(FileBrowserModel.Mode.BROWSER)) {
return r;
}
var selectedLabel = new Label("Selected: ");
selectedLabel.setAlignment(Pos.CENTER);
var selected = new HBox();
selected.setAlignment(Pos.CENTER_LEFT);
selected.setSpacing(10);
model.getSelectedFiles().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());
});
var spacer = new Spacer(Orientation.HORIZONTAL);
var button = new Button("Select");
button.setOnAction(event -> model.finishChooser());
button.setDefaultButton(true);
var bottomBar = new HBox(selectedLabel, selected, spacer, button);
HBox.setHgrow(selected, Priority.ALWAYS);
bottomBar.setAlignment(Pos.CENTER);
bottomBar.setPadding(new Insets(15));
bottomBar.getStyleClass().add("chooser-bar");
var layout = new VBox(r, bottomBar);
VBox.setVgrow(r, Priority.ALWAYS);
return layout;
}
private Node createTabs() {
@ -54,6 +86,15 @@ public class FileBrowserComp extends SimpleComp {
var map = new HashMap<OpenFileSystemModel, Tab>();
// Restore state
model.getOpenFileSystems().forEach(v -> {
var t = createTab(tabs, v);
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
// Handle selection from platform
tabs.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.intValue() == -1) {
model.getSelected().setValue(null);
@ -63,34 +104,29 @@ public class FileBrowserComp extends SimpleComp {
model.getSelected().setValue(model.getOpenFileSystems().get(newValue.intValue()));
});
model.getOpenFileSystems().forEach(v -> {
var t = createTab(tabs, v);
map.put(v, t);
tabs.getTabs().add(t);
});
if (model.getOpenFileSystems().size() > 0) {
tabs.getSelectionModel().select(0);
}
model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> {
PlatformThread.runLaterIfNeededBlocking(() -> {
while (c.next()) {
for (var r : c.getRemoved()) {
while (c.next()) {
for (var r : c.getRemoved()) {
PlatformThread.runLaterIfNeeded(() -> {
var t = map.remove(r);
tabs.getTabs().remove(t);
}
});
}
for (var a : c.getAddedSubList()) {
for (var a : c.getAddedSubList()) {
PlatformThread.runLaterIfNeeded(() -> {
var t = createTab(tabs, a);
map.put(a, t);
tabs.getTabs().add(t);
}
});
}
});
}
});
model.getSelected().addListener((observable, oldValue, newValue) -> {
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(newValue));
PlatformThread.runLaterIfNeeded(() -> {
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(newValue));
});
});
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
@ -100,7 +136,13 @@ public class FileBrowserComp extends SimpleComp {
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(r))
.findAny()
.orElseThrow();
.orElse(null);
// Only handle close events that are triggered from the platform
if (source == null) {
continue;
}
model.closeFileSystem(source.getKey());
}
}
@ -110,22 +152,20 @@ public class FileBrowserComp extends SimpleComp {
return stack;
}
private Node createSingular() {
var stack =
new StackPane(new OpenFileSystemComp(model.getOpenFileSystems().get(0)).createSimple());
return stack;
}
private TabPane createTabPane() {
var tabs = new TabPane();
tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
tabs.setTabClosingPolicy(ALL_TABS);
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
// tabs.setStyle("-fx-open-tab-animation:none;-fx-close-tab-animation:none;");
toggleStyleClass(tabs, DENSE);
tabs.setMinHeight(TAB_MIN_HEIGHT);
tabs.setTabMinWidth(Region.USE_COMPUTED_SIZE);
if (!model.getMode().equals(FileBrowserModel.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);
}
return tabs;
}
@ -180,6 +220,12 @@ public class FileBrowserComp extends SimpleComp {
PlatformThread.sync(model.getBusy())));
tab.setGraphic(label);
if (!this.model.getMode().equals(FileBrowserModel.Mode.BROWSER)) {
label.setManaged(false);
label.setVisible(false);
}
tab.setContent(new OpenFileSystemComp(model).createSimple());
return tab;
}

View file

@ -1,29 +1,88 @@
package io.xpipe.app.browser;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@Getter
public class FileBrowserModel {
public static final FileBrowserModel DEFAULT = new FileBrowserModel();
public FileBrowserModel(Mode mode) {
this.mode = mode;
}
public static enum Mode {
BROWSER,
SINGLE_FILE_CHOOSER,
SINGLE_FILE_SAVE,
MULTIPLE_FILE_CHOOSER,
DIRECTORY_CHOOSER
}
public static final FileBrowserModel DEFAULT = new FileBrowserModel(Mode.BROWSER);
private final Mode mode;
private final ObservableList<FileSystem.FileEntry> selectedFiles = FXCollections.observableArrayList();
@Setter
private Consumer<List<FileStore>> onFinish;
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
public void finishChooser() {
if (getMode().equals(Mode.BROWSER)) {
throw new IllegalStateException();
}
closeFileSystem(openFileSystems.get(0));
if (selectedFiles.size() == 0) {
return;
}
var stores = selectedFiles.stream().map(entry -> new FileStore(entry.getFileSystem().getStore(), entry.getPath())).toList();
onFinish.accept(stores);
}
public void closeFileSystem(OpenFileSystemModel open) {
ThreadHelper.runAsync(() -> {
if (Objects.equals(selected.getValue(), open)) {
selected.setValue(null);
}
open.closeSync();
openFileSystems.remove(open);
});
}
public void openFileSystem(ShellStore store) {
// Prevent multiple tabs in non browser modes
if (!mode.equals(Mode.BROWSER)) {
ThreadHelper.runAsync(() -> {
var open = openFileSystems.size() > 0 ? openFileSystems.get(0) : null;
if (open != null) {
open.closeSync();
openFileSystems.remove(open);
}
var model = new OpenFileSystemModel(this);
openFileSystems.add(model);
selected.setValue(model);
model.switchAsync(store);
});
return;
}
var found = openFileSystems.stream()
.filter(fileSystemModel -> fileSystemModel.getStore().getValue().equals(store))
.findFirst();
@ -32,7 +91,7 @@ public class FileBrowserModel {
return;
}
var model = new OpenFileSystemModel();
var model = new OpenFileSystemModel(this);
openFileSystems.add(model);
selected.setValue(model);
model.switchAsync(store);

View file

@ -2,7 +2,10 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.util.*;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.FileSystem;
@ -103,6 +106,14 @@ final class FileContextMenu extends ContextMenu {
getItems().add(open);
}
var pipe = new MenuItem("Pipe");
pipe.setOnAction(event -> {
var store = new FileStore(model.getFileSystem().getStore(), entry.getPath());
GuiDsCreatorMultiStep.showForStore(DataSourceProvider.Category.STREAM, store, null);
event.consume();
});
getItems().add(pipe);
var edit = new MenuItem("Edit");
edit.setOnAction(event -> {
FileOpener.openInTextEditor(entry);

View file

@ -15,6 +15,7 @@ import io.xpipe.core.impl.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -85,10 +86,19 @@ final class FileListComp extends AnchorPane {
table.getColumns().setAll(filenameCol, sizeCol, mtimeCol);
table.getSortOrder().add(filenameCol);
table.setSortPolicy(param -> true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
filenameCol.minWidthProperty().bind(table.widthProperty().multiply(0.5));
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER) || fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
} else {
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
}
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
fileList.getModel().getBrowserModel().getSelectedFiles().setAll(c.getList());
});
var draggedOverDirectory = new SimpleBooleanProperty();
table.setOnKeyPressed(event -> {
@ -127,7 +137,7 @@ final class FileListComp extends AnchorPane {
row.setOnMouseClicked(e -> {
if (e.getClickCount() == 2 && !row.isEmpty()) {
fileList.onClick(row.getItem());
fileList.onDoubleClick(row.getItem());
}
});

View file

@ -41,6 +41,10 @@ final class FileListModel {
});
}
public FileBrowserModel.Mode getMode() {
return model.getBrowserModel().getMode();
}
public void setAll(List<FileSystem.FileEntry> newFiles) {
all.setValue(newFiles);
refreshShown();
@ -80,7 +84,12 @@ final class FileListModel {
}
}
public void onClick(FileSystem.FileEntry entry) {
public void onDoubleClick(FileSystem.FileEntry entry) {
if (!entry.isDirectory() && getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)) {
getModel().getBrowserModel().finishChooser();
return;
}
if (entry.isDirectory()) {
model.navigate(entry.getPath(), true);
} else {

View file

@ -2,9 +2,9 @@ package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.ShellStore;
import java.nio.file.Files;
import java.nio.file.Path;
@ -13,8 +13,6 @@ import java.util.List;
public class FileSystemHelper {
private static OpenFileSystemModel local;
public static String normalizeDirectoryPath(OpenFileSystemModel model, String path) {
if (path == null) {
return null;
@ -37,19 +35,9 @@ public class FileSystemHelper {
return FileNames.toDirectory(path);
}
public static OpenFileSystemModel getLocal() throws Exception {
if (local == null) {
var model = new OpenFileSystemModel();
model.switchFileSystem(ShellStore.local());
local = model;
}
return local;
}
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
return new FileSystem.FileEntry(
getLocal().getFileSystem(),
LocalStore.getFileSystem(),
file.toString(),
Files.getLastModifiedTime(file).toInstant(),
Files.isDirectory(file),

View file

@ -30,8 +30,10 @@ final class OpenFileSystemModel {
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
private final FileBrowserNavigationHistory history = new FileBrowserNavigationHistory();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final FileBrowserModel browserModel;
public OpenFileSystemModel() {
public OpenFileSystemModel(FileBrowserModel browserModel) {
this.browserModel = browserModel;
fileList = new FileListModel(this);
}

View file

@ -0,0 +1,59 @@
package io.xpipe.app.browser;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.core.impl.FileStore;
import javafx.beans.property.Property;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import java.io.File;
import java.util.List;
import java.util.Map;
public class StandaloneFileBrowser {
public static void localOpenFileChooser(Property<FileStore> fileStoreProperty, Window owner, Map<String, List<String>> extensions) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "*"));
extensions.forEach((key, value) -> {
fileChooser
.getExtensionFilters()
.add(new FileChooser.ExtensionFilter(
key, value.stream().map(v -> "*." + v).toArray(String[]::new)));
});
File file = fileChooser.showOpenDialog(owner);
if (file != null && file.exists()) {
fileStoreProperty.setValue(FileStore.local(file.toPath()));
}
}
public static void openSingleFile(Property<FileStore> file) {
var model = new FileBrowserModel(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER);
var comp = new FileBrowserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get()));
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, true, null);
model.setOnFinish(fileStores -> {
file.setValue(fileStores.size() > 0 ? fileStores.get(0) : null);
window.close();
});
window.show();
}
public static void saveSingleFile(Property<FileStore> file) {
var model = new FileBrowserModel(FileBrowserModel.Mode.SINGLE_FILE_SAVE);
var comp = new FileBrowserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get()));
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
model.setOnFinish(fileStores -> {
file.setValue(fileStores.size() > 0 ? fileStores.get(0) : null);
window.close();
});
window.show();
}
}

View file

@ -4,9 +4,11 @@ import io.xpipe.app.browser.FileBrowserComp;
import io.xpipe.app.browser.FileBrowserModel;
import io.xpipe.app.comp.about.AboutTabComp;
import io.xpipe.app.comp.base.SideMenuBarComp;
import io.xpipe.app.comp.storage.collection.SourceCollectionLayoutComp;
import io.xpipe.app.comp.storage.store.StoreLayoutComp;
import io.xpipe.app.core.*;
import io.xpipe.app.core.AppActionLinkDetector;
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.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
@ -28,9 +30,6 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
private final Property<SideMenuBarComp.Entry> selected;
public AppLayoutComp() {
var firstTime = AppCache.get("firstTimeLayout", Boolean.class, () -> true);
AppCache.update("firstTimeLayout", false);
entries = createEntryList();
selected = new SimpleObjectProperty<>(entries.get(0));
@ -44,7 +43,7 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
var l = new ArrayList<>(List.of(
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
new SideMenuBarComp.Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new FileBrowserComp(FileBrowserModel.DEFAULT)),
new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
//new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
new SideMenuBarComp.Entry(
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new

View file

@ -214,7 +214,7 @@ public abstract class MultiStepComp extends Comp<CompStructure<VBox>> {
buttons.setAlignment(Pos.CENTER_RIGHT);
var nextText = Bindings.createStringBinding(
() -> isLastPage() ? AppI18n.get("finishStep") : AppI18n.get("nextStep"), currentStep);
var nextButton = new ButtonComp(nextText, null, comp::next).styleClass("next");
var nextButton = new ButtonComp(nextText, null, comp::next).apply(struc -> struc.get().setDefaultButton(true)).styleClass("next");
var previousButton = new ButtonComp(AppI18n.observable("previousStep"), null, comp::previous)
.styleClass("next")

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp.source.store;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
@ -9,34 +10,29 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.JfxHelper;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button;
import javafx.scene.layout.Region;
import javafx.stage.FileChooser;
import lombok.AllArgsConstructor;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
@AllArgsConstructor
public class DsLocalFileBrowseComp extends Comp<CompStructure<Button>> {
private final ObservableValue<DataSourceProvider<?>> provider;
private final Property<DataStore> chosenFile;
private final Property<FileStore> chosenFile;
private final DsStreamStoreChoiceComp.Mode mode;
@Override
public CompStructure<Button> createBase() {
var button = new AtomicReference<Button>();
button.set(new ButtonComp(null, getGraphic(), () -> {
var fileChooser = createChooser();
File file = mode == DsStreamStoreChoiceComp.Mode.OPEN
? fileChooser.showOpenDialog(button.get().getScene().getWindow())
: fileChooser.showSaveDialog(button.get().getScene().getWindow());
if (file != null && file.exists()) {
chosenFile.setValue(FileStore.local(file.toPath()));
if (mode == DsStreamStoreChoiceComp.Mode.OPEN) {
StandaloneFileBrowser.openSingleFile(chosenFile);
} else {
StandaloneFileBrowser.saveSingleFile(chosenFile);
}
})
.createStructure()
@ -67,36 +63,6 @@ public class DsLocalFileBrowseComp extends Comp<CompStructure<Button>> {
return provider != null && provider.getValue() != null;
}
private FileChooser createChooser() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
if (!hasProvider()) {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "*"));
return fileChooser;
}
if (hasProvider()) {
provider.getValue().getFileProvider().getFileExtensions().forEach((key, value) -> {
var name = AppI18n.get(key);
if (value != null) {
fileChooser
.getExtensionFilters()
.add(new FileChooser.ExtensionFilter(
name, value.stream().map(v -> "*." + v).toArray(String[]::new)));
} else {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(name, "*"));
}
});
if (!provider.getValue().getFileProvider().getFileExtensions().containsValue(null)) {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "*"));
}
}
return fileChooser;
}
private Region getGraphic() {
var graphic = hasProvider() ? provider.getValue().getDisplayIconFileName() : "file_icon.png";
if (chosenFile.getValue() == null || !(chosenFile.getValue() instanceof FileStore f) || f.getFile() == null) {

View file

@ -2,11 +2,9 @@ package io.xpipe.app.comp.source.store;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FileSystemStoreChoiceComp;
import io.xpipe.app.util.DynamicOptionsBuilder;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -26,7 +24,6 @@ public class DsRemoteFileChoiceComp extends SimpleComp {
var machine = new SimpleObjectProperty<FileSystemStore>();
var fileName = new SimpleStringProperty();
return new DynamicOptionsBuilder(false)
.addComp(AppI18n.observable("machine"), new FileSystemStoreChoiceComp(machine), machine)
.addString(AppI18n.observable("file"), fileName, true)
.bind(
() -> {

View file

@ -71,11 +71,11 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
protected Region createSimple() {
var isNamedStore =
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent();
var localStore = new SimpleObjectProperty<DataStore>(
var localStore = new SimpleObjectProperty<FileStore>(
!isNamedStore
&& selected.getValue() instanceof FileStore fileStore
&& fileStore.getFileSystem() instanceof LocalStore
? selected.getValue()
? fileStore
: null);
var browseComp = new DsLocalFileBrowseComp(provider, localStore, mode).apply(GrowAugment.create(true, false));
var dragAndDropLabel = Comp.of(() -> new Label(AppI18n.get("dragAndDropFilesHere")))

View file

@ -50,6 +50,10 @@ public class AppFont {
}
public static void setSize(Node node, int off) {
if (node.getStyle().contains("-fx-font-size: ")) {
return;
}
var baseSize = AppPrefs.get() != null ? AppPrefs.get().fontSize.getValue() : 12;
node.setStyle(node.getStyle() + "-fx-font-size: " + (baseSize + off) + "pt;");
}

View file

@ -1,30 +1,28 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.core.AppI18n;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.MachineStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.stage.FileChooser;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.File;
import java.util.List;
public class FileStoreChoiceComp extends SimpleComp {
private final List<MachineStore> availableFileSystems;
private final boolean onlyLocal;
private final Property<FileStore> selected;
public FileStoreChoiceComp(List<MachineStore> availableFileSystems, Property<FileStore> selected) {
this.availableFileSystems = availableFileSystems;
public FileStoreChoiceComp(boolean onlyLocal, Property<FileStore> selected) {
this.onlyLocal = onlyLocal;
this.selected = selected;
}
@ -36,42 +34,29 @@ public class FileStoreChoiceComp extends SimpleComp {
protected Region createSimple() {
var fileProperty = new SimpleStringProperty(
selected.getValue() != null ? selected.getValue().getFile() : null);
var fileSystemProperty = new SimpleObjectProperty<>(
selected.getValue() != null ? selected.getValue().getFileSystem() : availableFileSystems.get(0));
fileProperty.addListener((observable, oldValue, newValue) -> {
setSelected(fileSystemProperty.get(), fileProperty.get());
setSelected(selected.getValue().getFileSystem(), newValue);
});
fileSystemProperty.addListener((observable, oldValue, newValue) -> {
setSelected(fileSystemProperty.get(), fileProperty.get());
selected.addListener((observable, oldValue, newValue) -> {
fileProperty.setValue(newValue.getFile());
});
var fileSystemChoiceComp = new FileSystemStoreChoiceComp(fileSystemProperty);
if (availableFileSystems.size() == 1) {
var fileSystemChoiceComp = new FileSystemStoreChoiceComp(selected).grow(false, true).styleClass(Styles.LEFT_PILL);
if (onlyLocal) {
fileSystemChoiceComp.hide(new SimpleBooleanProperty(true));
}
var fileNameComp = new TextFieldComp(fileProperty).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
var fileBrowseButton = new IconButtonComp("mdi2f-folder-open-outline", () -> {
if (fileSystemProperty.get() != null && fileSystemProperty.get() instanceof LocalStore) {
var fileChooser = createChooser();
File file = fileChooser.showOpenDialog(null);
if (file != null && file.exists()) {
fileProperty.setValue(file.toString());
}
}
})
.hide(fileSystemProperty.isNotEqualTo(new LocalStore()));
var fileNameComp = new TextFieldComp(fileProperty)
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
.styleClass(onlyLocal ? Styles.LEFT_PILL : Styles.CENTER_PILL);
var layout = new HorizontalComp(List.of(fileSystemChoiceComp, fileNameComp, fileBrowseButton));
var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> {
StandaloneFileBrowser.openSingleFile(selected);
})
.styleClass(Styles.RIGHT_PILL).grow(false, true);
var layout = new HorizontalComp(List.of(fileSystemChoiceComp, fileNameComp, fileBrowseButton)).apply(struc -> struc.get().setFillHeight(true));
return layout.createRegion();
}
private FileChooser createChooser() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "*"));
return fileChooser;
}
}

View file

@ -4,8 +4,10 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
@ -14,9 +16,9 @@ import javafx.scene.layout.Region;
public class FileSystemStoreChoiceComp extends SimpleComp {
private final Property<FileSystemStore> selected;
private final Property<FileStore> selected;
public FileSystemStoreChoiceComp(Property<FileSystemStore> selected) {
public FileSystemStoreChoiceComp(Property<FileStore> selected) {
this.selected = selected;
}
@ -35,16 +37,37 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
return new Label(getName(s), img.createRegion());
}
private Region createDisplayGraphic(FileSystemStore s) {
var provider = DataStoreProviders.byStore(s);
var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16);
return new Label(null, img.createRegion());
}
@Override
protected Region createSimple() {
var comboBox = new CustomComboBoxBuilder<>(selected, this::createGraphic, null, v -> true);
comboBox.addFilter((v, s) -> getName(v).toLowerCase().contains(s));
var fileSystemProperty = new SimpleObjectProperty<>(
selected.getValue() != null ? selected.getValue().getFileSystem() : null);
fileSystemProperty.addListener((observable, oldValue, newValue) -> {
selected.setValue(FileStore.builder()
.fileSystem(newValue)
.file(selected.getValue() != null ? selected.getValue().getFile() : null)
.build());
});
selected.addListener((observable, oldValue, newValue) -> {
fileSystemProperty.setValue(newValue.getFileSystem());
});
var comboBox =
new CustomComboBoxBuilder<FileSystemStore>(fileSystemProperty, this::createGraphic, null, v -> true);
comboBox.setSelectedDisplay(this::createDisplayGraphic);
XPipeDaemon.getInstance().getNamedStores().stream()
.filter(e -> e instanceof FileSystemStore)
.map(e -> (FileSystemStore) e)
.forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("choice-comp");
cb.setMaxWidth(45);
return cb;
}
}

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
@ -51,9 +52,11 @@ public class PrettyImageComp extends SimpleComp {
},
aspectRatioProperty);
var image = new SimpleStringProperty();
var currentNode = new SimpleObjectProperty<Node>();
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
var requiresChange = value.getValue() == null || (value.getValue().endsWith(".svg") && !(currentNode.get() instanceof WebView) ||
image.set(val);
var requiresChange = val == null || (val.endsWith(".svg") && !(currentNode.get() instanceof WebView) ||
!(currentNode.get() instanceof ImageView));
if (!requiresChange) {
return;
@ -61,19 +64,19 @@ public class PrettyImageComp extends SimpleComp {
aspectRatioProperty.unbind();
if (value.getValue() == null) {
if (val == null) {
currentNode.set(new Region());
}
else if (value.getValue().endsWith(".svg")) {
else if (val.endsWith(".svg")) {
var storeIcon = SvgComp.create(
Bindings.createStringBinding(() -> {
if (!AppImages.hasSvgImage(value.getValue())) {
if (!AppImages.hasSvgImage(image.getValue())) {
return null;
}
return AppImages.svgImage(value.getValue());
}, value));
return AppImages.svgImage(image.getValue());
}, image));
var ar = Bindings.createDoubleBinding(
() -> {
return storeIcon.getWidth().getValue().doubleValue()
@ -96,13 +99,13 @@ public class PrettyImageComp extends SimpleComp {
.imageProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (!AppImages.hasNormalImage(value.getValue())) {
if (!AppImages.hasNormalImage(image.getValue())) {
return null;
}
return AppImages.image(value.getValue());
return AppImages.image(image.getValue());
},
PlatformThread.sync(value)));
image));
var ar = Bindings.createDoubleBinding(
() -> {
if (storeIcon.getImage() == null) {

View file

@ -56,7 +56,7 @@ public class AppPrefs {
!AppProperties.get().getDataDir().equals(AppProperties.DEFAULT_DATA_DIR);
private static final String LOG_LEVEL_PROP = "io.xpipe.app.logLevel";
// Lets keep this at trace for now, at least for the alpha
private static final String DEFAULT_LOG_LEVEL = "trace";
private static final String DEFAULT_LOG_LEVEL = "debug";
private static final boolean LOG_LEVEL_FIXED = System.getProperty(LOG_LEVEL_PROP) != null;
private static final String DEVELOPER_MODE_PROP = "io.xpipe.app.developerMode";
private static AppPrefs INSTANCE;

View file

@ -38,7 +38,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
protected Optional<Path> getApplicationPath() {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
try (ShellProcessControl pc = ShellStore.createLocal().create().start()) {
try (var c = pc.command(String.format(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister "
+ "-dump | grep -o \"/.*%s.app\" | grep -v -E \"Caches|TimeMachine|Temporary|/Volumes/%s\" | uniq",

View file

@ -146,7 +146,7 @@ public class AppInstaller {
@Override
public void installLocal(String file) throws Exception {
var shellProcessControl = ShellStore.local().create().start();
var shellProcessControl = ShellStore.createLocal().create().start();
var exec = XPipeInstallation.getInstallationExecutable(
shellProcessControl,
XPipeInstallation.getDefaultInstallationBasePath(shellProcessControl, false));

View file

@ -25,6 +25,7 @@ public class CustomComboBoxBuilder<T> {
private final Property<T> selected;
private final Function<T, Node> nodeFunction;
private Function<T, Node> selectedDisplayNodeFunction;
private final Map<Node, T> nodeMap = new HashMap<>();
private final Map<Node, Runnable> actionsMap = new HashMap<>();
private final List<Node> nodes = new ArrayList<>();
@ -41,10 +42,15 @@ public class CustomComboBoxBuilder<T> {
Property<T> selected, Function<T, Node> nodeFunction, Node emptyNode, Predicate<T> veto) {
this.selected = selected;
this.nodeFunction = nodeFunction;
this.selectedDisplayNodeFunction = nodeFunction;
this.emptyNode = emptyNode;
this.veto = veto;
}
public void setSelectedDisplay(Function<T, Node> nodeFunction) {
selectedDisplayNodeFunction = nodeFunction;
}
public void addAction(Node node, Runnable run) {
nodes.add(node);
actionsMap.put(node, run);
@ -172,7 +178,7 @@ public class CustomComboBoxBuilder<T> {
}
var val = nodeMap.get(item);
var newNode = nodeFunction.apply(val);
var newNode = selectedDisplayNodeFunction.apply(val);
setGraphic(newNode);
}
}

View file

@ -21,7 +21,7 @@ public class DesktopShortcuts {
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.TargetPath = '%%TARGET%%'; $S.Save()"
""",
target, name, icon.toString());
ShellStore.local().create().executeSimpleCommand(content);
ShellStore.createLocal().create().executeSimpleCommand(content);
}
private static void createLinuxShortcut(String target, String name) throws Exception {

View file

@ -65,7 +65,7 @@ public class FileOpener {
}
public static void openInDefaultApplication(String file) {
try (var pc = ShellStore.local().create().start()) {
try (var pc = ShellStore.createLocal().create().start()) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
pc.executeSimpleCommand("\"" + file + "\"");
} else if (pc.getOsType().equals(OsType.LINUX)) {

View file

@ -14,7 +14,7 @@ public class MacOsPermissions {
public static boolean waitForAccessibilityPermissions() throws Exception {
AtomicReference<Alert> alert = new AtomicReference<>();
var state = new SimpleBooleanProperty(true);
try (var pc = ShellStore.local().create().start()) {
try (var pc = ShellStore.createLocal().create().start()) {
while (state.get()) {
var success = pc.executeBooleanSimpleCommand(
"osascript -e 'tell application \"System Events\" to keystroke \"t\"'");

View file

@ -42,7 +42,7 @@ public class ScriptHelper {
@SneakyThrows
public static String createLocalExecScript(String content) {
try (var l = ShellStore.local().create().start()) {
try (var l = ShellStore.createLocal().create().start()) {
return createExecScript(l, content);
}
}

View file

@ -33,6 +33,7 @@ open module io.xpipe.app {
exports io.xpipe.app.fxcomps.util;
exports io.xpipe.app.fxcomps.augment;
exports io.xpipe.app.test;
exports io.xpipe.app.browser;
requires com.sun.jna;
requires com.sun.jna.platform;

View file

@ -12,8 +12,23 @@
-fx-padding: 0;
}
.chooser-bar {
-fx-border-color: -color-neutral-emphasis;
-fx-border-width: 0.1em 0 0 0;
-fx-padding: 1em;
-fx-background-color: -color-neutral-muted;
}
.browser .tab-content-area { -fx-padding: 0; }
.browser .singular {
-fx-tab-max-height: 0 ;
}
.browser .singular .tab-header-area {
visibility: hidden ;
}
.browser .table-directory-view .table-view {
-color-header-bg: -color-bg-default;
-color-cell-bg-selected: -color-neutral-emphasis;

View file

@ -1,4 +1,4 @@
* {
.prefs * {
-fx-text-fill: -color-fg-default;
-fx-highlight-text-fill: -color-fg-default;
-fx-highlight-fill: -color-neutral-muted;

View file

@ -52,7 +52,7 @@ public class BeaconProxyImpl extends ProxyProvider {
public <T> T downstreamTransform(T object, ShellStore proxy) {
var proxyNode = JacksonMapper.getDefault().valueToTree(proxy);
var inputNode = JacksonMapper.getDefault().valueToTree(object);
var localNode = JacksonMapper.getDefault().valueToTree(ShellStore.local());
var localNode = JacksonMapper.getDefault().valueToTree(ShellStore.createLocal());
var result = replace(inputNode, node -> node.equals(proxyNode) ? Optional.of(localNode) : Optional.empty());
return (T) JacksonMapper.getDefault().treeToValue(result, object.getClass());
}

View file

@ -1,7 +1,7 @@
package io.xpipe.core.charsetter;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import lombok.Value;
@ -108,7 +108,7 @@ public abstract class Charsetter {
}
}
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof ShellStore m) {
if (result.getNewLine() == null) {
result = new Result(
result.getCharset(),

View file

@ -13,9 +13,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
@JsonTypeName("local")
public class LocalStore extends JacksonizedValue implements FileSystemStore, MachineStore {
public class LocalStore extends JacksonizedValue implements ShellStore {
private static ShellProcessControl local;
private static FileSystem localFileSystem;
public static ShellProcessControl getShell() throws Exception {
if (local == null) {
@ -25,14 +26,22 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
return local;
}
@Override
public boolean isLocal() {
return true;
public static FileSystem getFileSystem() throws Exception {
if (localFileSystem == null) {
localFileSystem = new LocalStore().createFileSystem();
}
return localFileSystem;
}
@Override
public FileSystem createFileSystem() {
return new ConnectionFileSystem(ShellStore.local().create()) {
return new ConnectionFileSystem(ShellStore.createLocal().create(), LocalStore.this) {
@Override
public FileSystemStore getStore() {
return LocalStore.this;
}
@Override
public InputStream openInput(String file) throws Exception {

View file

@ -17,8 +17,12 @@ public class ConnectionFileSystem implements FileSystem {
@JsonIgnore
private final ShellProcessControl shellProcessControl;
public ConnectionFileSystem(ShellProcessControl shellProcessControl) {
@JsonIgnore
private final ShellStore store;
public ConnectionFileSystem(ShellProcessControl shellProcessControl, ShellStore store) {
this.shellProcessControl = shellProcessControl;
this.store = store;
}
@Override
@ -34,6 +38,11 @@ public class ConnectionFileSystem implements FileSystem {
return shellProcessControl.getShellDialect().listFiles(this, shellProcessControl, file);
}
@Override
public FileSystemStore getStore() {
return store;
}
@Override
public Optional<ShellProcessControl> getShell() {
return Optional.of(shellProcessControl);

View file

@ -42,6 +42,8 @@ public interface FileSystem extends Closeable, AutoCloseable {
}
}
FileSystemStore getStore();
Optional<ShellProcessControl> getShell();
FileSystem open() throws Exception;

View file

@ -1,13 +0,0 @@
package io.xpipe.core.store;
public interface MachineStore extends FileSystemStore, ShellStore {
public default boolean isLocal() {
return false;
}
@Override
default FileSystem createFileSystem() {
return new ConnectionFileSystem(create());
}
}

View file

@ -1,32 +1,25 @@
package io.xpipe.core.store;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellProcessControl;
import java.nio.charset.Charset;
public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStore, FileSystemStore {
public static ShellStore local() {
public static ShellStore createLocal() {
return new LocalStore();
}
static void withLocal(Charsetter.FailableConsumer<ShellProcessControl, Exception> c) throws Exception {
try (var l = local().create().start()) {
c.accept(l);
}
}
static boolean isLocal(ShellStore s) {
return s instanceof LocalStore;
}
@Override
default FileSystem createFileSystem() {
return new ConnectionFileSystem(create());
return new ConnectionFileSystem(create(), this);
}
@Override

1
dist/changelogs/0.5.3.md vendored Normal file
View file

@ -0,0 +1 @@
- Improvements and fixes for the file explorer

View file

@ -17,7 +17,7 @@ public class LocalStoreProvider implements DataStoreProvider {
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
try (var pc = ShellStore.local().create().start()) {
try (var pc = ShellStore.createLocal().create().start()) {
return OsType.getLocal().determineOperatingSystemName(pc);
}
}

View file

@ -1,18 +0,0 @@
package io.xpipe.ext.base;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.MachineStore;
import lombok.experimental.SuperBuilder;
import java.util.List;
@SuperBuilder
public class MachineRootContainer extends SimpleCollectionSource {
MachineStore store;
@Override
protected List<DataSource<?>> get() throws Exception {
return List.of();
}
}

View file

@ -1,18 +1,18 @@
package io.xpipe.ext.base.actions;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.StreamDataStore;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.stage.FileChooser;
import lombok.Value;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
public class StreamExportAction implements ActionProvider {
@ -23,25 +23,22 @@ public class StreamExportAction implements ActionProvider {
@Override
public boolean requiresPlatform() {
return false;
return true;
}
@Override
public void execute() throws Exception {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "."));
var outputFile = fileChooser.showSaveDialog(null);
if (outputFile == null) {
var outputFile = new SimpleObjectProperty<FileStore>();
StandaloneFileBrowser.saveSingleFile(outputFile);
if (outputFile.get() == null) {
return;
}
ThreadHelper.runAsync(() -> {
try (InputStream inputStream = store.openInput()) {
try (OutputStream outputStream = Files.newOutputStream(outputFile.toPath())) {
try (OutputStream outputStream = outputFile.get().openOutput()) {
inputStream.transferTo(outputStream);
}
DesktopHelper.browseFileInDirectory(outputFile.toPath());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}

View file

@ -1 +1 @@
0.5.2
0.5.3