mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-06-24 00:53:02 +12:00
Various small fixes [release]
This commit is contained in:
parent
26a7592ed6
commit
417aa16f1d
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
() -> {
|
||||
|
|
|
@ -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")))
|
||||
|
|
|
@ -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;");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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\"'");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
* {
|
||||
.prefs * {
|
||||
-fx-text-fill: -color-fg-default;
|
||||
-fx-highlight-text-fill: -color-fg-default;
|
||||
-fx-highlight-fill: -color-neutral-muted;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -42,6 +42,8 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
FileSystemStore getStore();
|
||||
|
||||
Optional<ShellProcessControl> getShell();
|
||||
|
||||
FileSystem open() throws Exception;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
1
dist/changelogs/0.5.3.md
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
- Improvements and fixes for the file explorer
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue