Rework browser

This commit is contained in:
crschnick 2024-04-06 16:22:38 +00:00
parent 0064569fb2
commit 7263214894
76 changed files with 797 additions and 616 deletions

View file

@ -12,72 +12,47 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import io.xpipe.app.util.FixedHierarchyStore;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Point2D;
import javafx.scene.control.Button;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
final class BrowserBookmarkComp extends SimpleComp {
public final class BrowserBookmarkComp extends SimpleComp {
public static final Timer DROP_TIMER = new Timer("dnd", true);
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private final BrowserModel model;
private Point2D lastOver = new Point2D(-1, -1);
private TimerTask activeTask;
private final ObservableValue<DataStoreEntry> selected;
private final Predicate<StoreEntryWrapper> applicable;
private final BiConsumer<StoreEntryWrapper, BooleanProperty> action;
BrowserBookmarkComp(BrowserModel model) {
this.model = model;
public BrowserBookmarkComp(
ObservableValue<DataStoreEntry> selected,
Predicate<StoreEntryWrapper> applicable,
BiConsumer<StoreEntryWrapper, BooleanProperty> action
) {
this.selected = selected;
this.applicable = applicable;
this.action = action;
}
@Override
protected Region createSimple() {
var filterText = new SimpleStringProperty();
var open = PlatformThread.sync(model.getSelected());
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
};
var selectedCategory = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue());
BooleanProperty busy = new SimpleBooleanProperty(false);
Consumer<StoreEntryWrapper> action = w -> {
ThreadHelper.runFailableAsync(() -> {
var entry = w.getEntry();
if (!entry.getValidity().isUsable()) {
return;
}
if (entry.getStore() instanceof ShellStore fileSystem) {
model.openFileSystemAsync(entry.ref(), null, busy);
} else if (entry.getStore() instanceof FixedHierarchyStore) {
BooleanScope.execute(busy, () -> {
w.refreshChildren();
});
}
});
};
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (s, comp) -> {
comp.disable(Bindings.createBooleanBinding(
() -> {
@ -85,14 +60,15 @@ final class BrowserBookmarkComp extends SimpleComp {
},
busy));
comp.apply(struc -> {
open.addListener((observable, oldValue, newValue) -> {
struc.get()
.pseudoClassStateChanged(
SELECTED,
newValue != null
&& newValue.getEntry()
.get()
.equals(s.getWrapper().getEntry()));
selected.addListener((observable, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
struc.get()
.pseudoClassStateChanged(
SELECTED,
newValue != null
&& newValue
.equals(s.getWrapper().getEntry()));
});
});
});
};
@ -101,7 +77,7 @@ final class BrowserBookmarkComp extends SimpleComp {
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory),
augment,
action,
entryWrapper -> action.accept(entryWrapper, busy),
true);
var category = new DataStoreCategoryChoiceComp(
StoreViewState.get().getAllConnectionsCategory(),
@ -125,21 +101,4 @@ final class BrowserBookmarkComp extends SimpleComp {
content.getStyleClass().add("bookmark-list");
return content;
}
private void handleHoverTimer(DataStore store, DragEvent event) {
if (lastOver.getX() == event.getX() && lastOver.getY() == event.getY()) {
return;
}
lastOver = (new Point2D(event.getX(), event.getY()));
activeTask = new TimerTask() {
@Override
public void run() {
if (activeTask != this) {}
// Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded()));
}
};
DROP_TIMER.schedule(activeTask, 500);
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Breadcrumbs;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.store.FileNames;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
import io.xpipe.app.browser.file.FileSystemHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ProcessControlProvider;

View file

@ -1,6 +1,7 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.TooltipAugment;

View file

@ -1,195 +0,0 @@
package io.xpipe.app.browser;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@Getter
public class BrowserModel {
public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER, BrowserSavedStateImpl.load());
private final Mode mode;
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final BrowserSavedState savedState;
@Setter
private Consumer<List<FileReference>> onFinish;
public BrowserModel(Mode mode, BrowserSavedState savedState) {
this.mode = mode;
this.savedState = savedState;
selected.addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
selection.clear();
return;
}
ListBindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
});
}
public void restoreState(BrowserSavedState state) {
ThreadHelper.runAsync(() -> {
state.getEntries().forEach(e -> {
restoreStateAsync(e, null);
// Don't try to run everything in parallel as that can be taxing
ThreadHelper.sleep(1000);
});
});
}
public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) {
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
storageEntry.ifPresent(entry -> {
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
});
}
public void reset() {
synchronized (BrowserModel.this) {
for (OpenFileSystemModel o : new ArrayList<>(openFileSystems)) {
// Don't close busy connections gracefully
// as we otherwise might lock up
if (o.isBusy()) {
continue;
}
closeFileSystemSync(o);
}
if (savedState != null) {
savedState.save();
}
}
// Delete all files
localTransfersStage.clear();
}
public void finishChooser() {
if (!getMode().isChooser()) {
throw new IllegalStateException();
}
var chosen = new ArrayList<>(selection);
synchronized (BrowserModel.this) {
for (OpenFileSystemModel openFileSystem : openFileSystems) {
closeFileSystemAsync(openFileSystem);
}
}
if (chosen.size() == 0) {
return;
}
var stores = chosen.stream()
.map(entry -> new FileReference(
selected.getValue().getEntry(), entry.getRawFileEntry().getPath()))
.toList();
onFinish.accept(stores);
}
public void closeFileSystemAsync(OpenFileSystemModel open) {
ThreadHelper.runAsync(() -> {
closeFileSystemSync(open);
});
}
private void closeFileSystemSync(OpenFileSystemModel open) {
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get())
&& savedState != null
&& open.getCurrentPath().get() != null) {
savedState.add(new BrowserSavedState.Entry(
open.getEntry().get().getUuid(), open.getCurrentPath().get()));
}
open.closeSync();
synchronized (BrowserModel.this) {
openFileSystems.remove(open);
}
}
public void openFileSystemAsync(
DataStoreEntryRef<? extends FileSystemStore> store,
FailableFunction<OpenFileSystemModel, String, Exception> path,
BooleanProperty externalBusy) {
if (store == null) {
return;
}
// Only load icons when a file system is opened
ThreadHelper.runAsync(() -> {
BrowserIconFileType.loadDefinitions();
BrowserIconDirectoryType.loadDefinitions();
FileIconManager.loadIfNecessary();
});
ThreadHelper.runFailableAsync(() -> {
OpenFileSystemModel model;
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new OpenFileSystemModel(this, store);
model.initFileSystem();
model.initSavedState();
// Prevent multiple calls from interfering with each other
synchronized (BrowserModel.this) {
openFileSystems.add(model);
// The tab pane doesn't automatically select new tabs
selected.setValue(model);
}
}
if (path != null) {
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
} else {
model.initWithDefaultDirectory();
}
});
}
@Getter
public enum Mode {
BROWSER(false, true, true, true),
SINGLE_FILE_CHOOSER(true, false, true, false),
SINGLE_FILE_SAVE(true, false, true, false),
MULTIPLE_FILE_CHOOSER(true, true, true, false),
SINGLE_DIRECTORY_CHOOSER(true, false, false, true),
MULTIPLE_DIRECTORY_CHOOSER(true, true, false, true);
private final boolean chooser;
private final boolean multiple;
private final boolean acceptsFiles;
private final boolean acceptsDirectories;
Mode(boolean chooser, boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
this.chooser = chooser;
this.multiple = multiple;
this.acceptsFiles = acceptsFiles;
this.acceptsDirectories = acceptsDirectories;
}
}
}

View file

@ -1,6 +1,8 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;

View file

@ -1,5 +1,7 @@
package io.xpipe.app.browser;
import io.xpipe.app.browser.file.BrowserFileOverviewComp;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.comp.base.SimpleTitledPaneComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;

View file

@ -1,6 +1,7 @@
package io.xpipe.app.browser;
import javafx.collections.ObservableList;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@ -18,6 +19,7 @@ public interface BrowserSavedState {
@Value
@Jacksonized
@Builder
@AllArgsConstructor
class Entry {
UUID uuid;

View file

@ -27,7 +27,7 @@ public class BrowserSavedStateImpl implements BrowserSavedState {
this.lastSystems = FXCollections.observableArrayList(lastSystems);
}
static BrowserSavedStateImpl load() {
public static BrowserSavedStateImpl load() {
return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> {
return new BrowserSavedStateImpl(FXCollections.observableArrayList());
});

View file

@ -1,6 +1,9 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.file.BrowserFileListCompEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
@ -11,6 +14,7 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.util.HumanReadableFormat;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
@ -124,6 +128,6 @@ public class BrowserStatusBarComp extends SimpleComp {
});
// Use status bar as an extension of file list
new ContextMenuAugment<>(mouseEvent -> mouseEvent.isSecondaryButtonDown(), null, () -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(r));
new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, null, () -> new BrowserContextMenu(model, null)).augment(new SimpleCompStructure<>(r));
}
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
@ -122,11 +123,13 @@ public class BrowserTransferComp extends SimpleComp {
return;
}
if (!(model.getBrowserSessionModel().getSelectedEntry().getValue() instanceof OpenFileSystemModel fileSystemModel)) {
return;
}
var files = drag.getEntries();
model.drop(
model.getBrowserModel()
.getSelected()
.getValue(),
fileSystemModel,
files);
event.setDropCompleted(true);
event.consume();

View file

@ -1,5 +1,8 @@
package io.xpipe.app.browser;
import io.xpipe.app.browser.file.FileSystemHelper;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ShellTemp;
@ -36,7 +39,7 @@ public class BrowserTransferModel {
t.setName("file downloader");
return t;
});
BrowserModel browserModel;
BrowserSessionModel browserSessionModel;
ObservableList<Item> items = FXCollections.observableArrayList();
BooleanProperty downloading = new SimpleBooleanProperty();
BooleanProperty allDownloaded = new SimpleBooleanProperty();

View file

@ -9,7 +9,7 @@ public class BrowserTransferProgress {
long transferred;
long total;
static BrowserTransferProgress empty() {
public static BrowserTransferProgress empty() {
return new BrowserTransferProgress(null, 0, 0);
}
@ -17,7 +17,7 @@ public class BrowserTransferProgress {
return new BrowserTransferProgress(name, 0, size);
}
static BrowserTransferProgress finished(String name, long size) {
public static BrowserTransferProgress finished(String name, long size) {
return new BrowserTransferProgress(name, size, size);
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.TileButtonComp;
@ -33,9 +34,9 @@ import java.util.List;
public class BrowserWelcomeComp extends SimpleComp {
private final BrowserModel model;
private final BrowserSessionModel model;
public BrowserWelcomeComp(BrowserModel model) {
public BrowserWelcomeComp(BrowserSessionModel model) {
this.model = model;
}

View file

@ -1,73 +0,0 @@
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.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FileReference;
import io.xpipe.core.store.FileSystemStore;
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;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class StandaloneFileBrowser {
public static void localOpenFileChooser(
Property<FileReference> fileStoreProperty, Window owner, Map<String, List<String>> extensions) {
PlatformThread.runLaterIfNeeded(() -> {
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(FileReference.local(file.toPath()));
}
});
}
public static void openSingleFile(
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
var comp = new BrowserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get()));
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
model.setOnFinish(fileStores -> {
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
window.close();
});
window.show();
model.openFileSystemAsync(store.get(), null, null);
});
}
public static void saveSingleFile(Property<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_SAVE, null);
var comp = new BrowserComp(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.getFirst() : null);
window.close();
});
window.show();
});
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import java.util.List;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import java.util.List;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.ModuleLayerLoader;
import javafx.scene.Node;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.file.BrowserEntry;
import java.util.List;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.core.process.ShellControl;
import java.util.List;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.Shortcuts;
import io.xpipe.app.util.BooleanScope;
@ -23,7 +23,7 @@ public interface LeafAction extends BrowserAction {
var b = new Button();
b.setOnAction(event -> {
// Only accept shortcut actions in the current tab
if (!model.equals(model.getBrowserModel().getSelected().getValue())) {
if (!model.equals(model.getBrowserModel().getSelectedEntry().getValue())) {
return;
}

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.CommandBuilder;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.util.FileOpener;
import io.xpipe.core.process.ShellControl;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;

View file

@ -1,8 +1,9 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.util.LicenseProvider;
import javafx.scene.control.ContextMenu;
@ -13,7 +14,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.List;
final class BrowserContextMenu extends ContextMenu {
public final class BrowserContextMenu extends ContextMenu {
private final OpenFileSystemModel model;
private final BrowserEntry source;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.file.BrowserFileListModel;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.core.store.FileKind;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
@ -47,7 +47,7 @@ import java.util.Objects;
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
import static javafx.scene.control.TableColumn.SortType.ASCENDING;
final class BrowserFileListComp extends SimpleComp {
public final class BrowserFileListComp extends SimpleComp {
private static final PseudoClass HIDDEN = PseudoClass.getPseudoClass("hidden");
private static final PseudoClass EMPTY = PseudoClass.getPseudoClass("empty");
@ -121,7 +121,7 @@ final class BrowserFileListComp extends SimpleComp {
}
private void prepareTableSelectionModel(TableView<BrowserEntry> table) {
if (!fileList.getMode().isMultiple()) {
if (!fileList.getSelectionMode().isMultiple()) {
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
} else {
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
@ -141,9 +141,9 @@ final class BrowserFileListComp extends SimpleComp {
.getPath()));
// Remove unsuitable selection
toSelect.removeIf(browserEntry -> (browserEntry.getRawFileEntry().getKind() == FileKind.DIRECTORY
&& !fileList.getMode().isAcceptsDirectories())
&& !fileList.getSelectionMode().isAcceptsDirectories())
|| (browserEntry.getRawFileEntry().getKind() != FileKind.DIRECTORY
&& !fileList.getMode().isAcceptsFiles()));
&& !fileList.getSelectionMode().isAcceptsFiles()));
fileList.getSelection().setAll(toSelect);
Platform.runLater(() -> {

View file

@ -1,5 +1,7 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.BrowserClipboard;
import io.xpipe.app.browser.BrowserSelectionListComp;
import io.xpipe.core.store.FileKind;
import javafx.geometry.Point2D;
import javafx.scene.Node;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileKind;
@ -25,6 +26,8 @@ public final class BrowserFileListModel {
static final Comparator<BrowserEntry> FILE_TYPE_COMPARATOR =
Comparator.comparing(path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
private final OpenFileSystemModel.SelectionMode selectionMode;
private final OpenFileSystemModel fileSystemModel;
private final Property<Comparator<BrowserEntry>> comparatorProperty =
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
@ -39,7 +42,8 @@ public final class BrowserFileListModel {
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
private final Property<BrowserEntry> editing = new SimpleObjectProperty<>();
public BrowserFileListModel(OpenFileSystemModel fileSystemModel) {
public BrowserFileListModel(OpenFileSystemModel.SelectionMode selectionMode, OpenFileSystemModel fileSystemModel) {
this.selectionMode = selectionMode;
this.fileSystemModel = fileSystemModel;
fileSystemModel.getFilter().addListener((observable, oldValue, newValue) -> {
@ -51,10 +55,6 @@ public final class BrowserFileListModel {
});
}
public BrowserModel.Mode getMode() {
return fileSystemModel.getBrowserModel().getMode();
}
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
try (var s = newFiles) {
var parent = fileSystemModel.getCurrentParentDirectory();
@ -135,12 +135,6 @@ public final class BrowserFileListModel {
}
public void onDoubleClick(BrowserEntry entry) {
if (entry.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY
&& getMode().equals(BrowserModel.Mode.SINGLE_FILE_CHOOSER)) {
getFileSystemModel().getBrowserModel().finishChooser();
return;
}
if (entry.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
fileSystemModel.cdAsync(entry.getRawFileEntry().resolved().getPath());
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.BrowserIcons;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.VBoxViewComp;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.util.InputHelper;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.util.BooleanAnimationTimer;

View file

@ -1,5 +1,7 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.BrowserTransferProgress;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileKind;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.fs;
import io.xpipe.app.util.ShellControlCache;
import io.xpipe.core.process.ShellControl;

View file

@ -1,7 +1,13 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.fs;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.BrowserFilterComp;
import io.xpipe.app.browser.BrowserNavBar;
import io.xpipe.app.browser.BrowserOverviewComp;
import io.xpipe.app.browser.BrowserStatusBarComp;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.file.BrowserFileListComp;
import io.xpipe.app.comp.base.ModalOverlayComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.fxcomps.Comp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.fs;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;

View file

@ -1,7 +1,15 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.fs;
import io.xpipe.app.browser.file.BrowserFileListModel;
import io.xpipe.app.browser.BrowserSavedState;
import io.xpipe.app.browser.BrowserTransferProgress;
import io.xpipe.app.browser.file.FileSystemHelper;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.browser.session.BrowserSessionEntry;
import io.xpipe.app.comp.base.ModalOverlayComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
@ -27,45 +35,86 @@ import java.util.Optional;
import java.util.stream.Stream;
@Getter
public final class OpenFileSystemModel {
public final class OpenFileSystemModel extends BrowserSessionEntry {
private final DataStoreEntryRef<? extends FileSystemStore> entry;
private final Property<String> filter = new SimpleStringProperty();
private final BrowserFileListModel fileList;
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final BrowserModel browserModel;
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
private final BooleanProperty inOverview = new SimpleBooleanProperty();
private final String name;
private final String tooltip;
private final Property<BrowserTransferProgress> progress =
new SimpleObjectProperty<>(BrowserTransferProgress.empty());
private FileSystem fileSystem;
private OpenFileSystemSavedState savedState;
private OpenFileSystemCache cache;
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
this.browserModel = browserModel;
this.entry = entry;
this.name = DataStorage.get().getStoreDisplayName(entry.get());
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
public OpenFileSystemModel(BrowserAbstractSessionModel<?> model, DataStoreEntryRef<? extends FileSystemStore> entry, SelectionMode selectionMode) {
super(model, entry);
this.inOverview.bind(Bindings.createBooleanBinding(
() -> {
return currentPath.get() == null;
},
currentPath));
fileList = new BrowserFileListModel(this);
fileList = new BrowserFileListModel(selectionMode, this);
}
public boolean isBusy() {
@Override
public Comp<?> comp() {
return new OpenFileSystemComp(this);
}
@Override
public boolean canImmediatelyClose() {
return !progress.getValue().done()
|| (fileSystem != null
&& fileSystem.getShell().isPresent()
&& fileSystem.getShell().get().getLock().isLocked());
&& fileSystem.getShell().isPresent()
&& fileSystem.getShell().get().getLock().isLocked());
}
@Override
public void init() throws Exception {
BooleanScope.execute(busy, () -> {
var fs = entry.getStore().createFileSystem();
if (fs.getShell().isPresent()) {
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
fs.getShell().get().onKill(() -> {
browserModel.closeAsync(this);
});
}
fs.open();
this.fileSystem = fs;
this.cache = new OpenFileSystemCache(this);
for (BrowserAction b : BrowserAction.ALL) {
b.init(this);
}
});
this.savedState = OpenFileSystemSavedState.loadForStore(this);
}
@Override
public void close() {
if (fileSystem == null) {
return;
}
if (DataStorage.get().getStoreEntries().contains(getEntry().get())
&& savedState != null
&& getCurrentPath().get() != null) {
if (getBrowserModel() instanceof BrowserSessionModel bm) {
bm.getSavedState().add(new BrowserSavedState.Entry(getEntry().get().getUuid(), getCurrentPath().get()));
}
}
try {
fileSystem.close();
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
fileSystem = null;
}
private void startIfNeeded() throws Exception {
if (fileSystem == null) {
return;
@ -369,42 +418,10 @@ public final class OpenFileSystemModel {
});
}
void closeSync() {
if (fileSystem == null) {
return;
}
try {
fileSystem.close();
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
fileSystem = null;
}
public boolean isClosed() {
return fileSystem == null;
}
public void initFileSystem() throws Exception {
BooleanScope.execute(busy, () -> {
var fs = entry.getStore().createFileSystem();
if (fs.getShell().isPresent()) {
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
fs.getShell().get().onKill(() -> {
browserModel.closeFileSystemAsync(this);
});
}
fs.open();
this.fileSystem = fs;
this.cache = new OpenFileSystemCache(this);
for (BrowserAction b : BrowserAction.ALL) {
b.init(this);
}
});
}
public void initWithGivenDirectory(String dir) throws Exception {
cdSyncWithoutCheck(dir);
}
@ -414,10 +431,6 @@ public final class OpenFileSystemModel {
history.updateCurrent(null);
}
void initSavedState() {
this.savedState = OpenFileSystemSavedState.loadForStore(this);
}
public void openTerminalAsync(String directory) {
ThreadHelper.runFailableAsync(() -> {
if (fileSystem == null) {
@ -445,4 +458,23 @@ public final class OpenFileSystemModel {
public void forthSync(int i) throws Exception {
cdSyncWithoutCheck(history.forth(i));
}
@Getter
public enum SelectionMode {
SINGLE_FILE(false, true, false),
MULTIPLE_FILE(true, true, false),
SINGLE_DIRECTORY(false, false, true),
MULTIPLE_DIRECTORY(true, false, true),
ALL(true, true, true);
private final boolean multiple;
private final boolean acceptsFiles;
private final boolean acceptsDirectories;
SelectionMode(boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
this.multiple = multiple;
this.acceptsFiles = acceptsFiles;
this.acceptsDirectories = acceptsDirectories;
}
}
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.fs;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;

View file

@ -0,0 +1,28 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
@Getter
public class BrowserAbstractSessionModel<T extends BrowserSessionEntry> {
protected final ObservableList<T> sessionEntries = FXCollections.observableArrayList();
protected final Property<T> selectedEntry = new SimpleObjectProperty<>();
public void closeAsync(BrowserSessionEntry open) {
ThreadHelper.runAsync(() -> {
closeSync(open);
});
}
void closeSync(BrowserSessionEntry open) {
open.close();
synchronized (BrowserAbstractSessionModel.this) {
this.sessionEntries.remove(open);
}
}
}

View file

@ -0,0 +1,158 @@
package io.xpipe.app.browser.session;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.*;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemComp;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class BrowserChooserComp extends SimpleComp {
private final BrowserChooserModel model;
public BrowserChooserComp(BrowserChooserModel model) {
this.model = model;
}
public static void openSingleFile(
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
var comp = new BrowserChooserComp(model)
.apply(struc -> struc.get().setPrefSize(1200, 700))
.apply(struc -> AppFont.normal(struc.get()));
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
model.setOnFinish(fileStores -> {
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
window.close();
});
window.show();
model.openFileSystemAsync(store.get(), null, null);
});
}
public static void saveSingleFile(Property<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
var comp = new BrowserChooserComp(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.getFirst() : null);
window.close();
});
window.show();
});
}
@Override
protected Region createSimple() {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
};
BiConsumer<StoreEntryWrapper, BooleanProperty> action = (w, busy) -> {
ThreadHelper.runFailableAsync(() -> {
var entry = w.getEntry();
if (!entry.getValidity().isUsable()) {
return;
}
if (entry.getStore() instanceof ShellStore fileSystem) {
model.openFileSystemAsync(entry.ref(), null, busy);
}
});
};
var bookmarksList = new BrowserBookmarkComp(BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), applicable, action).vgrow();
var stack = Comp.of(() -> {
var s = new StackPane();
model.getSelectedEntry().subscribe(selected -> {
PlatformThread.runLaterIfNeeded(() -> {
if (selected != null) {
s.getChildren().setAll(new OpenFileSystemComp(selected).createRegion());
} else {
s.getChildren().clear();
}
});
});
return s;
});
var splitPane = new SideSplitPaneComp(bookmarksList, stack)
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
return r;
}
private Region addBottomBar(Region r) {
var selectedLabel = new Label("Selected: ");
selectedLabel.setAlignment(Pos.CENTER);
var selected = new HBox();
selected.setAlignment(Pos.CENTER_LEFT);
selected.setSpacing(10);
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
})
.toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
var button = new Button("Select");
button.setPadding(new Insets(5, 10, 5, 10));
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.getStyleClass().add("chooser-bar");
var layout = new VBox(r, bottomBar);
VBox.setVgrow(r, Priority.ALWAYS);
return layout;
}
}

View file

@ -0,0 +1,107 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@Getter
public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
private final OpenFileSystemModel.SelectionMode selectionMode;
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
@Setter
private Consumer<List<FileReference>> onFinish;
public BrowserChooserModel(OpenFileSystemModel.SelectionMode selectionMode) {
this.selectionMode = selectionMode;
selectedEntry.addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
fileSelection.clear();
return;
}
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
});
}
public void finishChooser() {
var chosen = new ArrayList<>(fileSelection);
synchronized (BrowserChooserModel.this) {
var open = selectedEntry.getValue();
if (open != null) {
ThreadHelper.runAsync(() -> {
open.close();
});
}
}
if (chosen.size() == 0) {
return;
}
var stores = chosen.stream()
.map(entry -> new FileReference(
selectedEntry.getValue().getEntry(),
entry.getRawFileEntry().getPath()))
.toList();
onFinish.accept(stores);
}
public void openFileSystemAsync(
DataStoreEntryRef<? extends FileSystemStore> store,
FailableFunction<OpenFileSystemModel, String, Exception> path,
BooleanProperty externalBusy) {
if (store == null) {
return;
}
// Only load icons when a file system is opened
ThreadHelper.runAsync(() -> {
BrowserIconFileType.loadDefinitions();
BrowserIconDirectoryType.loadDefinitions();
FileIconManager.loadIfNecessary();
});
ThreadHelper.runFailableAsync(() -> {
OpenFileSystemModel model;
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new OpenFileSystemModel(this, store, selectionMode);
model.init();
// Prevent multiple calls from interfering with each other
synchronized (BrowserChooserModel.this) {
selectedEntry.setValue(model);
sessionEntries.add(model);
}
if (path != null) {
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
} else {
model.initWithDefaultDirectory();
}
}
});
}
}

View file

@ -0,0 +1,77 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.browser.BrowserBookmarkComp;
import io.xpipe.app.browser.BrowserTransferComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.ShellStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.scene.layout.Region;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
public class BrowserSessionComp extends SimpleComp {
private final BrowserSessionModel model;
public BrowserSessionComp(BrowserSessionModel model) {
this.model = model;
}
@Override
protected Region createSimple() {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
};
BiConsumer<StoreEntryWrapper, BooleanProperty> action = (w, busy) -> {
ThreadHelper.runFailableAsync(() -> {
var entry = w.getEntry();
if (!entry.getValidity().isUsable()) {
return;
}
if (entry.getStore() instanceof ShellStore fileSystem) {
model.openFileSystemAsync(entry.ref(), null, busy);
}
});
};
var bookmarksList = new BrowserBookmarkComp(BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), applicable, action).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getSessionEntries().size() == 0) {
return true;
}
return false;
},
model.getSessionEntries(),
model.getSelectedEntry())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var tabs = new BrowserSessionTabsComp(model);
var splitPane = new SideSplitPaneComp(vertical, tabs)
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = splitPane.createRegion();
r.getStyleClass().add("browser");
return r;
}
}

View file

@ -0,0 +1,34 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.Getter;
@Getter
public abstract class BrowserSessionEntry {
protected final DataStoreEntryRef<? extends FileSystemStore> entry;
protected final BooleanProperty busy = new SimpleBooleanProperty();
protected final BrowserAbstractSessionModel<?> browserModel;
protected final String name;
protected final String tooltip;
public BrowserSessionEntry(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
this.browserModel = browserModel;
this.entry = entry;
this.name = DataStorage.get().getStoreDisplayName(entry.get());
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
}
public abstract Comp<?> comp();
public abstract boolean canImmediatelyClose();
public abstract void init() throws Exception;
public abstract void close();
}

View file

@ -0,0 +1,108 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.browser.BrowserSavedState;
import io.xpipe.app.browser.BrowserSavedStateImpl;
import io.xpipe.app.browser.BrowserTransferModel;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.Getter;
import java.util.ArrayList;
@Getter
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionEntry> {
public static final BrowserSessionModel DEFAULT = new BrowserSessionModel(BrowserSavedStateImpl.load());
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final BrowserSavedState savedState;
public BrowserSessionModel(BrowserSavedState savedState) {
this.savedState = savedState;
}
public void restoreState(BrowserSavedState state) {
ThreadHelper.runAsync(() -> {
state.getEntries().forEach(e -> {
restoreStateAsync(e, null);
// Don't try to run everything in parallel as that can be taxing
ThreadHelper.sleep(1000);
});
});
}
public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) {
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
storageEntry.ifPresent(entry -> {
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
});
}
public void reset() {
synchronized (BrowserSessionModel.this) {
for (var o : new ArrayList<>(sessionEntries)) {
// Don't close busy connections gracefully
// as we otherwise might lock up
if (o.canImmediatelyClose()) {
continue;
}
closeSync(o);
}
if (savedState != null) {
savedState.save();
}
}
// Delete all files
localTransfersStage.clear();
}
public void openFileSystemAsync(
DataStoreEntryRef<? extends FileSystemStore> store,
FailableFunction<OpenFileSystemModel, String, Exception> path,
BooleanProperty externalBusy) {
if (store == null) {
return;
}
// Only load icons when a file system is opened
ThreadHelper.runAsync(() -> {
BrowserIconFileType.loadDefinitions();
BrowserIconDirectoryType.loadDefinitions();
FileIconManager.loadIfNecessary();
});
ThreadHelper.runFailableAsync(() -> {
OpenFileSystemModel model;
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL);
model.init();
// Prevent multiple calls from interfering with each other
synchronized (BrowserSessionModel.this) {
sessionEntries.add(model);
// The tab pane doesn't automatically select new tabs
selectedEntry.setValue(model);
}
}
if (path != null) {
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
} else {
model.initWithDefaultDirectory();
}
});
}
}

View file

@ -1,19 +1,13 @@
package io.xpipe.app.browser;
package io.xpipe.app.browser.session;
import atlantafx.base.controls.RingProgressIndicator;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.BrowserWelcomeComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
@ -23,15 +17,12 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -39,100 +30,25 @@ import static atlantafx.base.theme.Styles.DENSE;
import static atlantafx.base.theme.Styles.toggleStyleClass;
import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
public class BrowserComp extends SimpleComp {
public class BrowserSessionTabsComp extends SimpleComp {
private final BrowserModel model;
private final BrowserSessionModel model;
public BrowserComp(BrowserModel model) {
public BrowserSessionTabsComp(BrowserSessionModel model) {
this.model = model;
}
@Override
protected Region createSimple() {
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
return false;
},
model.getOpenFileSystems(),
model.getSelected())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var splitPane = new SideSplitPaneComp(vertical, createTabs())
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
// AppFont.small(r);
return r;
}
private Region addBottomBar(Region r) {
if (!model.getMode().isChooser()) {
return r;
}
var selectedLabel = new Label("Selected: ");
selectedLabel.setAlignment(Pos.CENTER);
var selected = new HBox();
selected.setAlignment(Pos.CENTER_LEFT);
selected.setSpacing(10);
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
})
.toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
var button = new Button("Select");
button.setPadding(new Insets(5, 10, 5, 10));
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.getStyleClass().add("chooser-bar");
var layout = new VBox(r, bottomBar);
VBox.setVgrow(r, Priority.ALWAYS);
return layout;
}
private Comp<?> createTabs() {
public Region createSimple() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
Bindings.isNotEmpty(model.getOpenFileSystems()),
Bindings.isNotEmpty(model.getSessionEntries()),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(
() -> {
return model.getOpenFileSystems().size() == 0
&& !model.getMode().isChooser();
return model.getSessionEntries().size() == 0;
},
model.getOpenFileSystems())));
return multi;
model.getSessionEntries())));
return multi.createRegion();
}
private TabPane createTabPane() {
@ -144,16 +60,16 @@ public class BrowserComp extends SimpleComp {
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
toggleStyleClass(tabs, DENSE);
var map = new HashMap<OpenFileSystemModel, Tab>();
var map = new HashMap<BrowserSessionEntry, Tab>();
// Restore state
model.getOpenFileSystems().forEach(v -> {
model.getSessionEntries().forEach(v -> {
var t = createTab(tabs, v);
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel()
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
.select(model.getSessionEntries().indexOf(model.getSelectedEntry().getValue()));
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
var modifying = new SimpleBooleanProperty();
@ -165,7 +81,7 @@ public class BrowserComp extends SimpleComp {
}
if (newValue == null) {
model.getSelected().setValue(null);
model.getSelectedEntry().setValue(null);
return;
}
@ -175,11 +91,11 @@ public class BrowserComp extends SimpleComp {
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
model.getSelected().setValue(source);
model.getSelectedEntry().setValue(source);
});
// Handle selection from model
model.getSelected().addListener((observable, oldValue, newValue) -> {
model.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
if (newValue == null) {
tabs.getSelectionModel().select(null);
@ -201,7 +117,7 @@ public class BrowserComp extends SimpleComp {
});
});
model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> {
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionEntry>) c -> {
while (c.next()) {
for (var r : c.getRemoved()) {
PlatformThread.runLaterIfNeeded(() -> {
@ -238,14 +154,14 @@ public class BrowserComp extends SimpleComp {
continue;
}
model.closeFileSystemAsync(source.getKey());
model.closeAsync(source.getKey());
}
}
});
return tabs;
}
private Tab createTab(TabPane tabs, OpenFileSystemModel model) {
private Tab createTab(TabPane tabs, BrowserSessionEntry model) {
var tab = new Tab();
var ring = new RingProgressIndicator(0, false);
@ -270,7 +186,7 @@ public class BrowserComp extends SimpleComp {
PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(new OpenFileSystemComp(model).createSimple());
tab.setContent(model.comp().createRegion());
var id = UUID.randomUUID().toString();
tab.setId(id);

View file

@ -1,7 +1,7 @@
package io.xpipe.app.core;
import io.xpipe.app.browser.BrowserComp;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.browser.session.BrowserSessionComp;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.store.StoreLayoutComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.prefs.AppPrefsComp;
@ -71,7 +71,7 @@ public class AppLayoutModel {
private List<Entry> createEntryList() {
var l = new ArrayList<>(List.of(
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserSessionComp(BrowserSessionModel.DEFAULT)),
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp()),
new Entry(

View file

@ -1,6 +1,6 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.core.check.AppAvCheck;
@ -75,7 +75,7 @@ public class BaseMode extends OperationMode {
@Override
public void finalTeardown() {
TrackEvent.info("Background mode shutdown started");
BrowserModel.DEFAULT.reset();
BrowserSessionModel.DEFAULT.reset();
StoreViewState.reset();
DataStorage.reset();
AppPrefs.reset();

View file

@ -1,7 +1,7 @@
package io.xpipe.app.fxcomps.impl;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.browser.session.BrowserChooserComp;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
@ -52,7 +52,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
.grow(false, true);
var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> {
StandaloneFileBrowser.openSingleFile(() -> fileSystem.getValue(), fileStore -> {
BrowserChooserComp.openSingleFile(() -> fileSystem.getValue(), fileStore -> {
if (fileStore == null) {
filePath.setValue(null);
fileSystem.setValue(null);

View file

@ -1,6 +1,6 @@
package io.xpipe.app.launcher;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.ext.ActionProvider;
@ -119,7 +119,7 @@ public abstract class LauncherInput {
var dir = Files.isDirectory(file) ? file : file.getParent();
AppLayoutModel.get().selectBrowser();
BrowserModel.DEFAULT.openFileSystemAsync(DataStorage.get().local().ref(), model -> dir.toString(), null);
BrowserSessionModel.DEFAULT.openFileSystemAsync(DataStorage.get().local().ref(), model -> dir.toString(), null);
}
}

View file

@ -42,6 +42,9 @@ open module io.xpipe.app {
exports io.xpipe.app.browser.icon;
exports io.xpipe.app.core.check;
exports io.xpipe.app.terminal;
exports io.xpipe.app.browser.session;
exports io.xpipe.app.browser.fs;
exports io.xpipe.app.browser.file;
requires com.sun.jna;
requires com.sun.jna.platform;

View file

@ -1,6 +1,6 @@
package io.xpipe.ext.base.action;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ActionProvider;
@ -56,7 +56,7 @@ public class BrowseStoreAction implements ActionProvider {
@Override
public void execute() {
BrowserModel.DEFAULT.openFileSystemAsync(entry.ref(), null, new SimpleBooleanProperty());
BrowserSessionModel.DEFAULT.openFileSystemAsync(entry.ref(), null, new SimpleBooleanProperty());
AppLayoutModel.get().selectBrowser();
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.process.CommandBuilder;

View file

@ -1,8 +1,8 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserClipboard;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.action.BrowserActionFormatter;

View file

@ -1,9 +1,9 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserAlerts;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.FileSystemHelper;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserAlerts;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.FileSystemHelper;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;

View file

@ -1,8 +1,8 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.FileSystemHelper;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.FileSystemHelper;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.FileOpener;

View file

@ -1,11 +1,10 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.icon.BrowserIcons;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import javafx.scene.Node;
import java.util.List;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BrowserActionFormatter;
import io.xpipe.app.browser.action.MultiExecuteAction;
import io.xpipe.app.browser.icon.BrowserIconFileType;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BrowserActionFormatter;
import io.xpipe.app.browser.action.ToFileCommandAction;
import io.xpipe.app.browser.icon.BrowserIconFileType;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.action.LeafAction;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;

View file

@ -1,8 +1,9 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;
import org.kordamp.ikonli.javafx.FontIcon;
@ -13,11 +14,9 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
model.getBrowserModel()
.openFileSystemAsync(
model.getEntry(),
m -> entries.getFirst().getRawFileEntry().getPath(),
null);
if (model.getBrowserModel() instanceof BrowserSessionModel bm) {
bm.openFileSystemAsync(model.getEntry(), m -> entries.getFirst().getRawFileEntry().getPath(), null);
}
}
@Override
@ -42,7 +41,7 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return entries.size() == 1
return model.getBrowserModel() instanceof BrowserSessionModel && entries.size() == 1
&& entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY);
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.util.FileOpener;
import io.xpipe.core.store.FileKind;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.util.FileOpener;
import io.xpipe.core.process.OsType;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.OsType;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.store.FileKind;

View file

@ -1,8 +1,8 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserClipboard;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.core.store.FileKind;
import javafx.scene.Node;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.MultiExecuteAction;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;

View file

@ -1,7 +1,7 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.action.ExecuteApplicationAction;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.core.process.OsType;