mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Rework browser
This commit is contained in:
parent
0064569fb2
commit
7263214894
76 changed files with 797 additions and 616 deletions
|
@ -12,72 +12,47 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
|||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||
import io.xpipe.app.util.FixedHierarchyStore;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
final class BrowserBookmarkComp extends SimpleComp {
|
||||
public final class BrowserBookmarkComp extends SimpleComp {
|
||||
|
||||
public static final Timer DROP_TIMER = new Timer("dnd", true);
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private final BrowserModel model;
|
||||
private Point2D lastOver = new Point2D(-1, -1);
|
||||
private TimerTask activeTask;
|
||||
private final ObservableValue<DataStoreEntry> selected;
|
||||
private final Predicate<StoreEntryWrapper> applicable;
|
||||
private final BiConsumer<StoreEntryWrapper, BooleanProperty> action;
|
||||
|
||||
BrowserBookmarkComp(BrowserModel model) {
|
||||
this.model = model;
|
||||
public BrowserBookmarkComp(
|
||||
ObservableValue<DataStoreEntry> selected,
|
||||
Predicate<StoreEntryWrapper> applicable,
|
||||
BiConsumer<StoreEntryWrapper, BooleanProperty> action
|
||||
) {
|
||||
this.selected = selected;
|
||||
this.applicable = applicable;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var filterText = new SimpleStringProperty();
|
||||
var open = PlatformThread.sync(model.getSelected());
|
||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|
||||
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
|
||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||
};
|
||||
var selectedCategory = new SimpleObjectProperty<>(
|
||||
StoreViewState.get().getActiveCategory().getValue());
|
||||
|
||||
BooleanProperty busy = new SimpleBooleanProperty(false);
|
||||
Consumer<StoreEntryWrapper> action = w -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var entry = w.getEntry();
|
||||
if (!entry.getValidity().isUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getStore() instanceof ShellStore fileSystem) {
|
||||
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||
} else if (entry.getStore() instanceof FixedHierarchyStore) {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
w.refreshChildren();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (s, comp) -> {
|
||||
comp.disable(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
|
@ -85,14 +60,15 @@ final class BrowserBookmarkComp extends SimpleComp {
|
|||
},
|
||||
busy));
|
||||
comp.apply(struc -> {
|
||||
open.addListener((observable, oldValue, newValue) -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
SELECTED,
|
||||
newValue != null
|
||||
&& newValue.getEntry()
|
||||
.get()
|
||||
.equals(s.getWrapper().getEntry()));
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
struc.get()
|
||||
.pseudoClassStateChanged(
|
||||
SELECTED,
|
||||
newValue != null
|
||||
&& newValue
|
||||
.equals(s.getWrapper().getEntry()));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -101,7 +77,7 @@ final class BrowserBookmarkComp extends SimpleComp {
|
|||
StoreSection.createTopLevel(
|
||||
StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory),
|
||||
augment,
|
||||
action,
|
||||
entryWrapper -> action.accept(entryWrapper, busy),
|
||||
true);
|
||||
var category = new DataStoreCategoryChoiceComp(
|
||||
StoreViewState.get().getAllConnectionsCategory(),
|
||||
|
@ -125,21 +101,4 @@ final class BrowserBookmarkComp extends SimpleComp {
|
|||
content.getStyleClass().add("bookmark-list");
|
||||
return content;
|
||||
}
|
||||
|
||||
private void handleHoverTimer(DataStore store, DragEvent event) {
|
||||
if (lastOver.getX() == event.getX() && lastOver.getY() == event.getY()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastOver = (new Point2D(event.getX(), event.getY()));
|
||||
activeTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (activeTask != this) {}
|
||||
|
||||
// Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded()));
|
||||
}
|
||||
};
|
||||
DROP_TIMER.schedule(activeTask, 500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Getter
|
||||
public class BrowserModel {
|
||||
|
||||
public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER, BrowserSavedStateImpl.load());
|
||||
|
||||
private final Mode mode;
|
||||
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
|
||||
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||
private final BrowserSavedState savedState;
|
||||
|
||||
@Setter
|
||||
private Consumer<List<FileReference>> onFinish;
|
||||
|
||||
public BrowserModel(Mode mode, BrowserSavedState savedState) {
|
||||
this.mode = mode;
|
||||
this.savedState = savedState;
|
||||
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
selection.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ListBindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreState(BrowserSavedState state) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
state.getEntries().forEach(e -> {
|
||||
restoreStateAsync(e, null);
|
||||
// Don't try to run everything in parallel as that can be taxing
|
||||
ThreadHelper.sleep(1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) {
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
storageEntry.ifPresent(entry -> {
|
||||
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
synchronized (BrowserModel.this) {
|
||||
for (OpenFileSystemModel o : new ArrayList<>(openFileSystems)) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (o.isBusy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
closeFileSystemSync(o);
|
||||
}
|
||||
if (savedState != null) {
|
||||
savedState.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
localTransfersStage.clear();
|
||||
}
|
||||
|
||||
public void finishChooser() {
|
||||
if (!getMode().isChooser()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
var chosen = new ArrayList<>(selection);
|
||||
|
||||
synchronized (BrowserModel.this) {
|
||||
for (OpenFileSystemModel openFileSystem : openFileSystems) {
|
||||
closeFileSystemAsync(openFileSystem);
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stores = chosen.stream()
|
||||
.map(entry -> new FileReference(
|
||||
selected.getValue().getEntry(), entry.getRawFileEntry().getPath()))
|
||||
.toList();
|
||||
onFinish.accept(stores);
|
||||
}
|
||||
|
||||
public void closeFileSystemAsync(OpenFileSystemModel open) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
closeFileSystemSync(open);
|
||||
});
|
||||
}
|
||||
|
||||
private void closeFileSystemSync(OpenFileSystemModel open) {
|
||||
if (DataStorage.get().getStoreEntries().contains(open.getEntry().get())
|
||||
&& savedState != null
|
||||
&& open.getCurrentPath().get() != null) {
|
||||
savedState.add(new BrowserSavedState.Entry(
|
||||
open.getEntry().get().getUuid(), open.getCurrentPath().get()));
|
||||
}
|
||||
open.closeSync();
|
||||
synchronized (BrowserModel.this) {
|
||||
openFileSystems.remove(open);
|
||||
}
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only load icons when a file system is opened
|
||||
ThreadHelper.runAsync(() -> {
|
||||
BrowserIconFileType.loadDefinitions();
|
||||
BrowserIconDirectoryType.loadDefinitions();
|
||||
FileIconManager.loadIfNecessary();
|
||||
});
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
OpenFileSystemModel model;
|
||||
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
model = new OpenFileSystemModel(this, store);
|
||||
model.initFileSystem();
|
||||
model.initSavedState();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserModel.this) {
|
||||
openFileSystems.add(model);
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selected.setValue(model);
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||
} else {
|
||||
model.initWithDefaultDirectory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum Mode {
|
||||
BROWSER(false, true, true, true),
|
||||
SINGLE_FILE_CHOOSER(true, false, true, false),
|
||||
SINGLE_FILE_SAVE(true, false, true, false),
|
||||
MULTIPLE_FILE_CHOOSER(true, true, true, false),
|
||||
SINGLE_DIRECTORY_CHOOSER(true, false, false, true),
|
||||
MULTIPLE_DIRECTORY_CHOOSER(true, true, false, true);
|
||||
|
||||
private final boolean chooser;
|
||||
private final boolean multiple;
|
||||
private final boolean acceptsFiles;
|
||||
private final boolean acceptsDirectories;
|
||||
|
||||
Mode(boolean chooser, boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
|
||||
this.chooser = chooser;
|
||||
this.multiple = multiple;
|
||||
this.acceptsFiles = acceptsFiles;
|
||||
this.acceptsDirectories = acceptsDirectories;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.file.FileSystemHelper;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
|
@ -36,7 +39,7 @@ public class BrowserTransferModel {
|
|||
t.setName("file downloader");
|
||||
return t;
|
||||
});
|
||||
BrowserModel browserModel;
|
||||
BrowserSessionModel browserSessionModel;
|
||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||
BooleanProperty downloading = new SimpleBooleanProperty();
|
||||
BooleanProperty allDownloaded = new SimpleBooleanProperty();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class StandaloneFileBrowser {
|
||||
|
||||
public static void localOpenFileChooser(
|
||||
Property<FileReference> fileStoreProperty, Window owner, Map<String, List<String>> extensions) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(AppI18n.get("anyFile"), "*"));
|
||||
extensions.forEach((key, value) -> {
|
||||
fileChooser
|
||||
.getExtensionFilters()
|
||||
.add(new FileChooser.ExtensionFilter(
|
||||
key, value.stream().map(v -> "*." + v).toArray(String[]::new)));
|
||||
});
|
||||
|
||||
File file = fileChooser.showOpenDialog(owner);
|
||||
if (file != null && file.exists()) {
|
||||
fileStoreProperty.setValue(FileReference.local(file.toPath()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void openSingleFile(
|
||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null);
|
||||
var comp = new BrowserComp(model)
|
||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
model.openFileSystemAsync(store.get(), null, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void saveSingleFile(Property<FileReference> file) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_SAVE, null);
|
||||
var comp = new BrowserComp(model)
|
||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.setValue(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
@ -47,7 +47,7 @@ import java.util.Objects;
|
|||
import static io.xpipe.app.util.HumanReadableFormat.byteCount;
|
||||
import static javafx.scene.control.TableColumn.SortType.ASCENDING;
|
||||
|
||||
final class BrowserFileListComp extends SimpleComp {
|
||||
public final class BrowserFileListComp extends SimpleComp {
|
||||
|
||||
private static final PseudoClass HIDDEN = PseudoClass.getPseudoClass("hidden");
|
||||
private static final PseudoClass EMPTY = PseudoClass.getPseudoClass("empty");
|
||||
|
@ -121,7 +121,7 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private void prepareTableSelectionModel(TableView<BrowserEntry> table) {
|
||||
if (!fileList.getMode().isMultiple()) {
|
||||
if (!fileList.getSelectionMode().isMultiple()) {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
} else {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
|
@ -141,9 +141,9 @@ final class BrowserFileListComp extends SimpleComp {
|
|||
.getPath()));
|
||||
// Remove unsuitable selection
|
||||
toSelect.removeIf(browserEntry -> (browserEntry.getRawFileEntry().getKind() == FileKind.DIRECTORY
|
||||
&& !fileList.getMode().isAcceptsDirectories())
|
||||
&& !fileList.getSelectionMode().isAcceptsDirectories())
|
||||
|| (browserEntry.getRawFileEntry().getKind() != FileKind.DIRECTORY
|
||||
&& !fileList.getMode().isAcceptsFiles()));
|
||||
&& !fileList.getSelectionMode().isAcceptsFiles()));
|
||||
fileList.getSelection().setAll(toSelect);
|
||||
|
||||
Platform.runLater(() -> {
|
|
@ -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;
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
@ -25,6 +26,8 @@ public final class BrowserFileListModel {
|
|||
static final Comparator<BrowserEntry> FILE_TYPE_COMPARATOR =
|
||||
Comparator.comparing(path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
||||
|
||||
private final OpenFileSystemModel.SelectionMode selectionMode;
|
||||
|
||||
private final OpenFileSystemModel fileSystemModel;
|
||||
private final Property<Comparator<BrowserEntry>> comparatorProperty =
|
||||
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
|
||||
|
@ -39,7 +42,8 @@ public final class BrowserFileListModel {
|
|||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||
private final Property<BrowserEntry> editing = new SimpleObjectProperty<>();
|
||||
|
||||
public BrowserFileListModel(OpenFileSystemModel fileSystemModel) {
|
||||
public BrowserFileListModel(OpenFileSystemModel.SelectionMode selectionMode, OpenFileSystemModel fileSystemModel) {
|
||||
this.selectionMode = selectionMode;
|
||||
this.fileSystemModel = fileSystemModel;
|
||||
|
||||
fileSystemModel.getFilter().addListener((observable, oldValue, newValue) -> {
|
||||
|
@ -51,10 +55,6 @@ public final class BrowserFileListModel {
|
|||
});
|
||||
}
|
||||
|
||||
public BrowserModel.Mode getMode() {
|
||||
return fileSystemModel.getBrowserModel().getMode();
|
||||
}
|
||||
|
||||
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
|
||||
try (var s = newFiles) {
|
||||
var parent = fileSystemModel.getCurrentParentDirectory();
|
||||
|
@ -135,12 +135,6 @@ public final class BrowserFileListModel {
|
|||
}
|
||||
|
||||
public void onDoubleClick(BrowserEntry entry) {
|
||||
if (entry.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY
|
||||
&& getMode().equals(BrowserModel.Mode.SINGLE_FILE_CHOOSER)) {
|
||||
getFileSystemModel().getBrowserModel().finishChooser();
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
||||
fileSystemModel.cdAsync(entry.getRawFileEntry().resolved().getPath());
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,7 +1,15 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.fs;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserFileListModel;
|
||||
import io.xpipe.app.browser.BrowserSavedState;
|
||||
import io.xpipe.app.browser.BrowserTransferProgress;
|
||||
import io.xpipe.app.browser.file.FileSystemHelper;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionEntry;
|
||||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
|
@ -27,45 +35,86 @@ import java.util.Optional;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
public final class OpenFileSystemModel {
|
||||
public final class OpenFileSystemModel extends BrowserSessionEntry {
|
||||
|
||||
private final DataStoreEntryRef<? extends FileSystemStore> entry;
|
||||
private final Property<String> filter = new SimpleStringProperty();
|
||||
private final BrowserFileListModel fileList;
|
||||
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
|
||||
private final OpenFileSystemHistory history = new OpenFileSystemHistory();
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
private final BrowserModel browserModel;
|
||||
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty inOverview = new SimpleBooleanProperty();
|
||||
private final String name;
|
||||
private final String tooltip;
|
||||
private final Property<BrowserTransferProgress> progress =
|
||||
new SimpleObjectProperty<>(BrowserTransferProgress.empty());
|
||||
private FileSystem fileSystem;
|
||||
private OpenFileSystemSavedState savedState;
|
||||
private OpenFileSystemCache cache;
|
||||
|
||||
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
||||
this.browserModel = browserModel;
|
||||
this.entry = entry;
|
||||
this.name = DataStorage.get().getStoreDisplayName(entry.get());
|
||||
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
|
||||
public OpenFileSystemModel(BrowserAbstractSessionModel<?> model, DataStoreEntryRef<? extends FileSystemStore> entry, SelectionMode selectionMode) {
|
||||
super(model, entry);
|
||||
this.inOverview.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return currentPath.get() == null;
|
||||
},
|
||||
currentPath));
|
||||
fileList = new BrowserFileListModel(this);
|
||||
fileList = new BrowserFileListModel(selectionMode, this);
|
||||
}
|
||||
|
||||
public boolean isBusy() {
|
||||
@Override
|
||||
public Comp<?> comp() {
|
||||
return new OpenFileSystemComp(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImmediatelyClose() {
|
||||
return !progress.getValue().done()
|
||||
|| (fileSystem != null
|
||||
&& fileSystem.getShell().isPresent()
|
||||
&& fileSystem.getShell().get().getLock().isLocked());
|
||||
&& fileSystem.getShell().isPresent()
|
||||
&& fileSystem.getShell().get().getLock().isLocked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
var fs = entry.getStore().createFileSystem();
|
||||
if (fs.getShell().isPresent()) {
|
||||
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
||||
fs.getShell().get().onKill(() -> {
|
||||
browserModel.closeAsync(this);
|
||||
});
|
||||
}
|
||||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
|
||||
this.cache = new OpenFileSystemCache(this);
|
||||
for (BrowserAction b : BrowserAction.ALL) {
|
||||
b.init(this);
|
||||
}
|
||||
});
|
||||
this.savedState = OpenFileSystemSavedState.loadForStore(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (fileSystem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DataStorage.get().getStoreEntries().contains(getEntry().get())
|
||||
&& savedState != null
|
||||
&& getCurrentPath().get() != null) {
|
||||
if (getBrowserModel() instanceof BrowserSessionModel bm) {
|
||||
bm.getSavedState().add(new BrowserSavedState.Entry(getEntry().get().getUuid(), getCurrentPath().get()));
|
||||
}
|
||||
}
|
||||
try {
|
||||
fileSystem.close();
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
fileSystem = null;
|
||||
}
|
||||
|
||||
|
||||
private void startIfNeeded() throws Exception {
|
||||
if (fileSystem == null) {
|
||||
return;
|
||||
|
@ -369,42 +418,10 @@ public final class OpenFileSystemModel {
|
|||
});
|
||||
}
|
||||
|
||||
void closeSync() {
|
||||
if (fileSystem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fileSystem.close();
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
fileSystem = null;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return fileSystem == null;
|
||||
}
|
||||
|
||||
public void initFileSystem() throws Exception {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
var fs = entry.getStore().createFileSystem();
|
||||
if (fs.getShell().isPresent()) {
|
||||
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
||||
fs.getShell().get().onKill(() -> {
|
||||
browserModel.closeFileSystemAsync(this);
|
||||
});
|
||||
}
|
||||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
|
||||
this.cache = new OpenFileSystemCache(this);
|
||||
for (BrowserAction b : BrowserAction.ALL) {
|
||||
b.init(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void initWithGivenDirectory(String dir) throws Exception {
|
||||
cdSyncWithoutCheck(dir);
|
||||
}
|
||||
|
@ -414,10 +431,6 @@ public final class OpenFileSystemModel {
|
|||
history.updateCurrent(null);
|
||||
}
|
||||
|
||||
void initSavedState() {
|
||||
this.savedState = OpenFileSystemSavedState.loadForStore(this);
|
||||
}
|
||||
|
||||
public void openTerminalAsync(String directory) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (fileSystem == null) {
|
||||
|
@ -445,4 +458,23 @@ public final class OpenFileSystemModel {
|
|||
public void forthSync(int i) throws Exception {
|
||||
cdSyncWithoutCheck(history.forth(i));
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum SelectionMode {
|
||||
SINGLE_FILE(false, true, false),
|
||||
MULTIPLE_FILE(true, true, false),
|
||||
SINGLE_DIRECTORY(false, false, true),
|
||||
MULTIPLE_DIRECTORY(true, false, true),
|
||||
ALL(true, true, true);
|
||||
|
||||
private final boolean multiple;
|
||||
private final boolean acceptsFiles;
|
||||
private final boolean acceptsDirectories;
|
||||
|
||||
SelectionMode(boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
|
||||
this.multiple = multiple;
|
||||
this.acceptsFiles = acceptsFiles;
|
||||
this.acceptsDirectories = acceptsDirectories;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,28 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BrowserAbstractSessionModel<T extends BrowserSessionEntry> {
|
||||
|
||||
protected final ObservableList<T> sessionEntries = FXCollections.observableArrayList();
|
||||
protected final Property<T> selectedEntry = new SimpleObjectProperty<>();
|
||||
|
||||
public void closeAsync(BrowserSessionEntry open) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
closeSync(open);
|
||||
});
|
||||
}
|
||||
|
||||
void closeSync(BrowserSessionEntry open) {
|
||||
open.close();
|
||||
synchronized (BrowserAbstractSessionModel.this) {
|
||||
this.sessionEntries.remove(open);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.browser.*;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BrowserChooserComp extends SimpleComp {
|
||||
|
||||
private final BrowserChooserModel model;
|
||||
|
||||
public BrowserChooserComp(BrowserChooserModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public static void openSingleFile(
|
||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
||||
var comp = new BrowserChooserComp(model)
|
||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("openFileTitle"), stage -> comp, false, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
model.openFileSystemAsync(store.get(), null, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void saveSingleFile(Property<FileReference> file) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
||||
var comp = new BrowserChooserComp(model)
|
||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("saveFileTitle"), stage -> comp, true, null);
|
||||
model.setOnFinish(fileStores -> {
|
||||
file.setValue(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||
window.close();
|
||||
});
|
||||
window.show();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
|
||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||
};
|
||||
BiConsumer<StoreEntryWrapper, BooleanProperty> action = (w, busy) -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var entry = w.getEntry();
|
||||
if (!entry.getValidity().isUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getStore() instanceof ShellStore fileSystem) {
|
||||
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var bookmarksList = new BrowserBookmarkComp(BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), applicable, action).vgrow();
|
||||
var stack = Comp.of(() -> {
|
||||
var s = new StackPane();
|
||||
model.getSelectedEntry().subscribe(selected -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (selected != null) {
|
||||
s.getChildren().setAll(new OpenFileSystemComp(selected).createRegion());
|
||||
} else {
|
||||
s.getChildren().clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
return s;
|
||||
});
|
||||
var splitPane = new SideSplitPaneComp(bookmarksList, stack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
});
|
||||
var r = addBottomBar(splitPane.createRegion());
|
||||
r.getStyleClass().add("browser");
|
||||
return r;
|
||||
}
|
||||
|
||||
private Region addBottomBar(Region r) {
|
||||
var selectedLabel = new Label("Selected: ");
|
||||
selectedLabel.setAlignment(Pos.CENTER);
|
||||
var selected = new HBox();
|
||||
selected.setAlignment(Pos.CENTER_LEFT);
|
||||
selected.setSpacing(10);
|
||||
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
selected.getChildren()
|
||||
.setAll(c.getList().stream()
|
||||
.map(s -> {
|
||||
var field =
|
||||
new TextField(s.getRawFileEntry().getPath());
|
||||
field.setEditable(false);
|
||||
field.setPrefWidth(500);
|
||||
return field;
|
||||
})
|
||||
.toList());
|
||||
});
|
||||
});
|
||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||
var button = new Button("Select");
|
||||
button.setPadding(new Insets(5, 10, 5, 10));
|
||||
button.setOnAction(event -> model.finishChooser());
|
||||
button.setDefaultButton(true);
|
||||
var bottomBar = new HBox(selectedLabel, selected, spacer, button);
|
||||
HBox.setHgrow(selected, Priority.ALWAYS);
|
||||
bottomBar.setAlignment(Pos.CENTER);
|
||||
bottomBar.getStyleClass().add("chooser-bar");
|
||||
|
||||
var layout = new VBox(r, bottomBar);
|
||||
VBox.setVgrow(r, Priority.ALWAYS);
|
||||
return layout;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.FileReference;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Getter
|
||||
public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
|
||||
|
||||
private final OpenFileSystemModel.SelectionMode selectionMode;
|
||||
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
|
||||
|
||||
@Setter
|
||||
private Consumer<List<FileReference>> onFinish;
|
||||
|
||||
public BrowserChooserModel(OpenFileSystemModel.SelectionMode selectionMode) {
|
||||
this.selectionMode = selectionMode;
|
||||
selectedEntry.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
fileSelection.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
||||
public void finishChooser() {
|
||||
var chosen = new ArrayList<>(fileSelection);
|
||||
|
||||
synchronized (BrowserChooserModel.this) {
|
||||
var open = selectedEntry.getValue();
|
||||
if (open != null) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
open.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stores = chosen.stream()
|
||||
.map(entry -> new FileReference(
|
||||
selectedEntry.getValue().getEntry(),
|
||||
entry.getRawFileEntry().getPath()))
|
||||
.toList();
|
||||
onFinish.accept(stores);
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only load icons when a file system is opened
|
||||
ThreadHelper.runAsync(() -> {
|
||||
BrowserIconFileType.loadDefinitions();
|
||||
BrowserIconDirectoryType.loadDefinitions();
|
||||
FileIconManager.loadIfNecessary();
|
||||
});
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
OpenFileSystemModel model;
|
||||
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
model = new OpenFileSystemModel(this, store, selectionMode);
|
||||
model.init();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserChooserModel.this) {
|
||||
selectedEntry.setValue(model);
|
||||
sessionEntries.add(model);
|
||||
}
|
||||
if (path != null) {
|
||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||
} else {
|
||||
model.initWithDefaultDirectory();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||
import io.xpipe.app.browser.BrowserTransferComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class BrowserSessionComp extends SimpleComp {
|
||||
|
||||
private final BrowserSessionModel model;
|
||||
|
||||
public BrowserSessionComp(BrowserSessionModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
|
||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||
};
|
||||
BiConsumer<StoreEntryWrapper, BooleanProperty> action = (w, busy) -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var entry = w.getEntry();
|
||||
if (!entry.getValidity().isUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getStore() instanceof ShellStore fileSystem) {
|
||||
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var bookmarksList = new BrowserBookmarkComp(BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), applicable, action).vgrow();
|
||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
||||
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (model.getSessionEntries().size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
model.getSessionEntries(),
|
||||
model.getSelectedEntry())));
|
||||
localDownloadStage.prefHeight(200);
|
||||
localDownloadStage.maxHeight(200);
|
||||
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
|
||||
|
||||
var tabs = new BrowserSessionTabsComp(model);
|
||||
var splitPane = new SideSplitPaneComp(vertical, tabs)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
});
|
||||
var r = splitPane.createRegion();
|
||||
r.getStyleClass().add("browser");
|
||||
return r;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class BrowserSessionEntry {
|
||||
|
||||
protected final DataStoreEntryRef<? extends FileSystemStore> entry;
|
||||
protected final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||
protected final String name;
|
||||
protected final String tooltip;
|
||||
|
||||
public BrowserSessionEntry(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
||||
this.browserModel = browserModel;
|
||||
this.entry = entry;
|
||||
this.name = DataStorage.get().getStoreDisplayName(entry.get());
|
||||
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
|
||||
}
|
||||
|
||||
public abstract Comp<?> comp();
|
||||
|
||||
public abstract boolean canImmediatelyClose();
|
||||
|
||||
public abstract void init() throws Exception;
|
||||
|
||||
public abstract void close();
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package io.xpipe.app.browser.session;
|
||||
|
||||
import io.xpipe.app.browser.BrowserSavedState;
|
||||
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
||||
import io.xpipe.app.browser.BrowserTransferModel;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Getter
|
||||
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionEntry> {
|
||||
|
||||
public static final BrowserSessionModel DEFAULT = new BrowserSessionModel(BrowserSavedStateImpl.load());
|
||||
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final BrowserSavedState savedState;
|
||||
|
||||
|
||||
public BrowserSessionModel(BrowserSavedState savedState) {
|
||||
this.savedState = savedState;
|
||||
}
|
||||
|
||||
public void restoreState(BrowserSavedState state) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
state.getEntries().forEach(e -> {
|
||||
restoreStateAsync(e, null);
|
||||
// Don't try to run everything in parallel as that can be taxing
|
||||
ThreadHelper.sleep(1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) {
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
storageEntry.ifPresent(entry -> {
|
||||
openFileSystemAsync(entry.ref(), model -> e.getPath(), busy);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
for (var o : new ArrayList<>(sessionEntries)) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (o.canImmediatelyClose()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
closeSync(o);
|
||||
}
|
||||
if (savedState != null) {
|
||||
savedState.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
localTransfersStage.clear();
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy) {
|
||||
if (store == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only load icons when a file system is opened
|
||||
ThreadHelper.runAsync(() -> {
|
||||
BrowserIconFileType.loadDefinitions();
|
||||
BrowserIconDirectoryType.loadDefinitions();
|
||||
FileIconManager.loadIfNecessary();
|
||||
});
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
OpenFileSystemModel model;
|
||||
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL);
|
||||
model.init();
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
sessionEntries.add(model);
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selectedEntry.setValue(model);
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model)));
|
||||
} else {
|
||||
model.initWithDefaultDirectory();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,19 +1,13 @@
|
|||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.session;
|
||||
|
||||
import atlantafx.base.controls.RingProgressIndicator;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.app.browser.BrowserWelcomeComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
|
@ -23,15 +17,12 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -39,100 +30,25 @@ import static atlantafx.base.theme.Styles.DENSE;
|
|||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
|
||||
|
||||
public class BrowserComp extends SimpleComp {
|
||||
public class BrowserSessionTabsComp extends SimpleComp {
|
||||
|
||||
private final BrowserModel model;
|
||||
private final BrowserSessionModel model;
|
||||
|
||||
public BrowserComp(BrowserModel model) {
|
||||
public BrowserSessionTabsComp(BrowserSessionModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var bookmarksList = new BrowserBookmarkComp(model).vgrow();
|
||||
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
|
||||
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (model.getOpenFileSystems().size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (model.getMode().isChooser()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
model.getOpenFileSystems(),
|
||||
model.getSelected())));
|
||||
localDownloadStage.prefHeight(200);
|
||||
localDownloadStage.maxHeight(200);
|
||||
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
|
||||
|
||||
var splitPane = new SideSplitPaneComp(vertical, createTabs())
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
});
|
||||
var r = addBottomBar(splitPane.createRegion());
|
||||
r.getStyleClass().add("browser");
|
||||
// AppFont.small(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private Region addBottomBar(Region r) {
|
||||
if (!model.getMode().isChooser()) {
|
||||
return r;
|
||||
}
|
||||
|
||||
var selectedLabel = new Label("Selected: ");
|
||||
selectedLabel.setAlignment(Pos.CENTER);
|
||||
var selected = new HBox();
|
||||
selected.setAlignment(Pos.CENTER_LEFT);
|
||||
selected.setSpacing(10);
|
||||
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
selected.getChildren()
|
||||
.setAll(c.getList().stream()
|
||||
.map(s -> {
|
||||
var field =
|
||||
new TextField(s.getRawFileEntry().getPath());
|
||||
field.setEditable(false);
|
||||
field.setPrefWidth(500);
|
||||
return field;
|
||||
})
|
||||
.toList());
|
||||
});
|
||||
});
|
||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||
var button = new Button("Select");
|
||||
button.setPadding(new Insets(5, 10, 5, 10));
|
||||
button.setOnAction(event -> model.finishChooser());
|
||||
button.setDefaultButton(true);
|
||||
var bottomBar = new HBox(selectedLabel, selected, spacer, button);
|
||||
HBox.setHgrow(selected, Priority.ALWAYS);
|
||||
bottomBar.setAlignment(Pos.CENTER);
|
||||
bottomBar.getStyleClass().add("chooser-bar");
|
||||
|
||||
var layout = new VBox(r, bottomBar);
|
||||
VBox.setVgrow(r, Priority.ALWAYS);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Comp<?> createTabs() {
|
||||
public Region createSimple() {
|
||||
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
|
||||
Comp.of(() -> createTabPane()),
|
||||
Bindings.isNotEmpty(model.getOpenFileSystems()),
|
||||
Bindings.isNotEmpty(model.getSessionEntries()),
|
||||
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
|
||||
Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return model.getOpenFileSystems().size() == 0
|
||||
&& !model.getMode().isChooser();
|
||||
return model.getSessionEntries().size() == 0;
|
||||
},
|
||||
model.getOpenFileSystems())));
|
||||
return multi;
|
||||
model.getSessionEntries())));
|
||||
return multi.createRegion();
|
||||
}
|
||||
|
||||
private TabPane createTabPane() {
|
||||
|
@ -144,16 +60,16 @@ public class BrowserComp extends SimpleComp {
|
|||
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
|
||||
toggleStyleClass(tabs, DENSE);
|
||||
|
||||
var map = new HashMap<OpenFileSystemModel, Tab>();
|
||||
var map = new HashMap<BrowserSessionEntry, Tab>();
|
||||
|
||||
// Restore state
|
||||
model.getOpenFileSystems().forEach(v -> {
|
||||
model.getSessionEntries().forEach(v -> {
|
||||
var t = createTab(tabs, v);
|
||||
map.put(v, t);
|
||||
tabs.getTabs().add(t);
|
||||
});
|
||||
tabs.getSelectionModel()
|
||||
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
|
||||
.select(model.getSessionEntries().indexOf(model.getSelectedEntry().getValue()));
|
||||
|
||||
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
|
||||
var modifying = new SimpleBooleanProperty();
|
||||
|
@ -165,7 +81,7 @@ public class BrowserComp extends SimpleComp {
|
|||
}
|
||||
|
||||
if (newValue == null) {
|
||||
model.getSelected().setValue(null);
|
||||
model.getSelectedEntry().setValue(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -175,11 +91,11 @@ public class BrowserComp extends SimpleComp {
|
|||
.findAny()
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
model.getSelected().setValue(source);
|
||||
model.getSelectedEntry().setValue(source);
|
||||
});
|
||||
|
||||
// Handle selection from model
|
||||
model.getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
model.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (newValue == null) {
|
||||
tabs.getSelectionModel().select(null);
|
||||
|
@ -201,7 +117,7 @@ public class BrowserComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> {
|
||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionEntry>) c -> {
|
||||
while (c.next()) {
|
||||
for (var r : c.getRemoved()) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
@ -238,14 +154,14 @@ public class BrowserComp extends SimpleComp {
|
|||
continue;
|
||||
}
|
||||
|
||||
model.closeFileSystemAsync(source.getKey());
|
||||
model.closeAsync(source.getKey());
|
||||
}
|
||||
}
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private Tab createTab(TabPane tabs, OpenFileSystemModel model) {
|
||||
private Tab createTab(TabPane tabs, BrowserSessionEntry model) {
|
||||
var tab = new Tab();
|
||||
|
||||
var ring = new RingProgressIndicator(0, false);
|
||||
|
@ -270,7 +186,7 @@ public class BrowserComp extends SimpleComp {
|
|||
PlatformThread.sync(model.getBusy())));
|
||||
tab.setText(model.getName());
|
||||
|
||||
tab.setContent(new OpenFileSystemComp(model).createSimple());
|
||||
tab.setContent(model.comp().createRegion());
|
||||
|
||||
var id = UUID.randomUUID().toString();
|
||||
tab.setId(id);
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.browser.BrowserComp;
|
||||
import io.xpipe.app.browser.BrowserModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionComp;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.comp.store.StoreLayoutComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.prefs.AppPrefsComp;
|
||||
|
@ -71,7 +71,7 @@ public class AppLayoutModel {
|
|||
|
||||
private List<Entry> createEntryList() {
|
||||
var l = new ArrayList<>(List.of(
|
||||
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)),
|
||||
new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserSessionComp(BrowserSessionModel.DEFAULT)),
|
||||
new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
||||
new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp()),
|
||||
new Entry(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import javafx.scene.Node;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
@ -13,11 +14,9 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
|
|||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
model.getBrowserModel()
|
||||
.openFileSystemAsync(
|
||||
model.getEntry(),
|
||||
m -> entries.getFirst().getRawFileEntry().getPath(),
|
||||
null);
|
||||
if (model.getBrowserModel() instanceof BrowserSessionModel bm) {
|
||||
bm.openFileSystemAsync(model.getEntry(), m -> entries.getFirst().getRawFileEntry().getPath(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,7 +41,7 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
|
|||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return entries.size() == 1
|
||||
return model.getBrowserModel() instanceof BrowserSessionModel && entries.size() == 1
|
||||
&& entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue