diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java index 2be82c138..736de4643 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java @@ -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 selected; + private final Predicate applicable; + private final BiConsumer action; - BrowserBookmarkComp(BrowserModel model) { - this.model = model; + public BrowserBookmarkComp( + ObservableValue selected, + Predicate applicable, + BiConsumer 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 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 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>> 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); - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java index f6d3bd3b8..585e2b144 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java index d3a09b826..6010fe912 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java index 839a393fa..2171e4b1f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java deleted file mode 100644 index 0d3737320..000000000 --- a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java +++ /dev/null @@ -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 openFileSystems = FXCollections.observableArrayList(); - private final Property selected = new SimpleObjectProperty<>(); - private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); - private final ObservableList selection = FXCollections.observableArrayList(); - private final BrowserSavedState savedState; - - @Setter - private Consumer> 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 store, - FailableFunction 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; - } - } -} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index 120de20bb..d77f573dd 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java index a545860ce..e4522d3ad 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java b/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java index 396a45486..0d0794f0d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java index 55d498c79..15bdb8c3d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java @@ -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()); }); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java index 39e2b0e30..ca9de277d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -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)); } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java index 5955f3bbe..c275f117b 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java index 23e686fa1..e403aceec 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java @@ -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 items = FXCollections.observableArrayList(); BooleanProperty downloading = new SimpleBooleanProperty(); BooleanProperty allDownloaded = new SimpleBooleanProperty(); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java index 6dc36aed6..738606419 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java @@ -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); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java index a652dda26..8e1b95267 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java b/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java deleted file mode 100644 index 35e70e95f..000000000 --- a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java +++ /dev/null @@ -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 fileStoreProperty, Window owner, Map> 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> store, Consumer 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 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(); - }); - } -} diff --git a/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java b/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java index 005cab2fa..be7385715 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java b/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java index 31e7a4ce1..6d77f5a5a 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java index 7b10bdb13..f1536114b 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/BrowserActionFormatter.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserActionFormatter.java index bb8111ca3..39a8cddb0 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BrowserActionFormatter.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserActionFormatter.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/ExecuteApplicationAction.java b/app/src/main/java/io/xpipe/app/browser/action/ExecuteApplicationAction.java index 7153cc596..4d1fec818 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/ExecuteApplicationAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/ExecuteApplicationAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java index a47044622..eeb2a7456 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java index d5312186d..6a83bdb71 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/action/ToFileCommandAction.java b/app/src/main/java/io/xpipe/app/browser/action/ToFileCommandAction.java index 3202aae99..a14311e3d 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/ToFileCommandAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/ToFileCommandAction.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java similarity index 99% rename from app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java index 487dbd46f..f7b359ec9 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserAlerts.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java similarity index 96% rename from app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java index a31eb56e9..4b5ea899f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java similarity index 95% rename from app/src/main/java/io/xpipe/app/browser/BrowserEntry.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java index 9fdb6cd7a..7ac1013c6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java similarity index 98% rename from app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 0fea76579..afa5c29c8 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -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 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(() -> { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java similarity index 98% rename from app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java index 3a04eb42a..97dd5eefa 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java similarity index 92% rename from app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java index 7e7ae8232..2510bbc10 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java @@ -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 FILE_TYPE_COMPARATOR = Comparator.comparing(path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); + private final OpenFileSystemModel.SelectionMode selectionMode; + private final OpenFileSystemModel fileSystemModel; private final Property> comparatorProperty = new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR); @@ -39,7 +42,8 @@ public final class BrowserFileListModel { private final Property draggedOverEmpty = new SimpleBooleanProperty(); private final Property 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 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()); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java similarity index 95% rename from app/src/main/java/io/xpipe/app/browser/BrowserFileOverviewComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java index 87d70f23a..2bb265e73 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java similarity index 94% rename from app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java index fe992ced5..3a2fcbe8c 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java similarity index 99% rename from app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index d56216e5d..fdf0547b2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java similarity index 99% rename from app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java rename to app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java index 809a1fca4..b7299c473 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemCache.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java similarity index 95% rename from app/src/main/java/io/xpipe/app/browser/OpenFileSystemCache.java rename to app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java index 2ec81bf6e..d3f7b9f4c 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemCache.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java similarity index 92% rename from app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java rename to app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java index bf123a358..6f29718c6 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemHistory.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java similarity index 98% rename from app/src/main/java/io/xpipe/app/browser/OpenFileSystemHistory.java rename to app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java index 0b841fe6a..ede908c1b 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemHistory.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java similarity index 86% rename from app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java rename to app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java index ed601c4ab..775344615 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java @@ -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 entry; private final Property filter = new SimpleStringProperty(); private final BrowserFileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); private final OpenFileSystemHistory history = new OpenFileSystemHistory(); - private final BooleanProperty busy = new SimpleBooleanProperty(); - private final BrowserModel browserModel; private final Property overlay = new SimpleObjectProperty<>(); private final BooleanProperty inOverview = new SimpleBooleanProperty(); - private final String name; - private final String tooltip; private final Property progress = new SimpleObjectProperty<>(BrowserTransferProgress.empty()); private FileSystem fileSystem; private OpenFileSystemSavedState savedState; private OpenFileSystemCache cache; - public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef 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 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; + } + } } diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java similarity index 99% rename from app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java rename to app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java index 780fae2b0..580c480f7 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java new file mode 100644 index 000000000..10eb1a33a --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java @@ -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 { + + protected final ObservableList sessionEntries = FXCollections.observableArrayList(); + protected final Property 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); + } + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java new file mode 100644 index 000000000..14c9ccd88 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java @@ -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> store, Consumer 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 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 applicable = storeEntryWrapper -> { + return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore) + && storeEntryWrapper.getEntry().getValidity().isUsable(); + }; + BiConsumer 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) 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; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java new file mode 100644 index 000000000..2c24178ef --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java @@ -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 { + + private final OpenFileSystemModel.SelectionMode selectionMode; + private final ObservableList fileSelection = FXCollections.observableArrayList(); + + @Setter + private Consumer> 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 store, + FailableFunction 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(); + } + } + }); + } + +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java new file mode 100644 index 000000000..5b2f4d80a --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java @@ -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 applicable = storeEntryWrapper -> { + return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore) + && storeEntryWrapper.getEntry().getValidity().isUsable(); + }; + BiConsumer 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; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionEntry.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionEntry.java new file mode 100644 index 000000000..8b14eb456 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionEntry.java @@ -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 entry; + protected final BooleanProperty busy = new SimpleBooleanProperty(); + protected final BrowserAbstractSessionModel browserModel; + protected final String name; + protected final String tooltip; + + public BrowserSessionEntry(BrowserAbstractSessionModel browserModel, DataStoreEntryRef 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(); +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java new file mode 100644 index 000000000..6756d4fb4 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java @@ -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 { + + 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 store, + FailableFunction 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(); + } + }); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java similarity index 61% rename from app/src/main/java/io/xpipe/app/browser/BrowserComp.java rename to app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java index eaf7b3a90..d60acdd8e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java @@ -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) 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., ObservableValue>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(); + var map = new HashMap(); // 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) c -> { + model.getSessionEntries().addListener((ListChangeListener) 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); diff --git a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java index b15f8dda2..dbe70cb2b 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java +++ b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java @@ -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 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( diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index 4fb42aa71..397219c0d 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java index 02d93a4b8..c6751244f 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java @@ -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); diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java index 7ac8f0bba..7dd26404a 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java @@ -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); } } diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index ffff35305..6824b40ad 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java index aec80275b..c4572d251 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java @@ -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(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java index 1a3c877a3..767ffa334 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java index 5dd23c056..b4dff63de 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java index b64f5115e..99829f267 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java index 084f1fac5..bb24fa729 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java index 9f866a43b..58e434c78 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java index f2b1fa34c..fbd14d58d 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteLinkAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteLinkAction.java index 47d316634..ffb4a9e33 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteLinkAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteLinkAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java index 84591c684..be8b0bf38 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java index 3037aa465..a65ea5c86 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java index 337f77747..6d79cab19 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java index 6179ac706..4b5a718d5 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java index b8c10c902..d5ad7e03b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java index 2ecf3846a..dc5357d45 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java index f2da9b6e8..a08d202d6 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java index f1b9e60dd..59b395070 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java index 4c00907f3..b94bb4ac4 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java @@ -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 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 entries) { - return entries.size() == 1 + return model.getBrowserModel() instanceof BrowserSessionModel && entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java index f56495a12..b3b1537a2 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java index 832126fd9..5fd5e1803 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java index c30caaf92..f34b3916f 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java index cdfe1e673..b55d9465a 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java index 220a7c238..f6cedd731 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java index 151007c7f..78b48f28c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java index 416e4e341..28161cae6 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java index 70f79f750..2a7f74535 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java @@ -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; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java index 962f9594c..b505c34fe 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java @@ -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;