mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-02 10:41:40 +12:00
Rework actions
This commit is contained in:
parent
8038e88b28
commit
69ed60b611
59 changed files with 1949 additions and 622 deletions
|
@ -3,7 +3,9 @@ package io.xpipe.app.browser;
|
|||
import atlantafx.base.controls.RingProgressIndicator;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.browser.icon.DirectoryType;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
|
@ -12,7 +14,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -42,6 +43,8 @@ public class FileBrowserComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
FileType.loadDefinitions();
|
||||
DirectoryType.loadDefinitions();
|
||||
ThreadHelper.runAsync( () -> {
|
||||
FileIconManager.loadIfNecessary();
|
||||
});
|
||||
|
@ -78,14 +81,14 @@ public class FileBrowserComp extends SimpleComp {
|
|||
var selected = new HBox();
|
||||
selected.setAlignment(Pos.CENTER_LEFT);
|
||||
selected.setSpacing(10);
|
||||
model.getSelectedFiles().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
|
||||
selected.getChildren().setAll(c.getList().stream().map(s -> {
|
||||
var field = new TextField(s.getPath());
|
||||
field.setEditable(false);
|
||||
field.setPrefWidth(400);
|
||||
return field;
|
||||
}).toList());
|
||||
});
|
||||
// model.getSelected().addListener((ListChangeListener<? super FileSystem.FileEntry>) c -> {
|
||||
// selected.getChildren().setAll(c.getList().stream().map(s -> {
|
||||
// var field = new TextField(s.getPath());
|
||||
// field.setEditable(false);
|
||||
// field.setPrefWidth(400);
|
||||
// return field;
|
||||
// }).toList());
|
||||
// });
|
||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
||||
var button = new Button("Select");
|
||||
button.setOnAction(event -> model.finishChooser());
|
||||
|
|
67
app/src/main/java/io/xpipe/app/browser/FileBrowserEntry.java
Normal file
67
app/src/main/java/io/xpipe/app/browser/FileBrowserEntry.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.icon.DirectoryType;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class FileBrowserEntry {
|
||||
|
||||
private final FileListModel model;
|
||||
private final FileSystem.FileEntry rawFileEntry;
|
||||
private final boolean synthetic;
|
||||
private final FileType fileType;
|
||||
private final DirectoryType directoryType;
|
||||
|
||||
public FileBrowserEntry(FileSystem.FileEntry rawFileEntry, FileListModel model, boolean synthetic) {
|
||||
this.rawFileEntry = rawFileEntry;
|
||||
this.model = model;
|
||||
this.synthetic = synthetic;
|
||||
this.fileType = fileType(rawFileEntry);
|
||||
this.directoryType = directoryType(rawFileEntry);
|
||||
}
|
||||
|
||||
private static FileType fileType(FileSystem.FileEntry rawFileEntry) {
|
||||
if (rawFileEntry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var f : FileType.ALL) {
|
||||
if (f.matches(rawFileEntry)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
|
||||
if (!rawFileEntry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var f : DirectoryType.ALL) {
|
||||
if (f.matches(rawFileEntry)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return FileNames.getFileName(getRawFileEntry().getPath());
|
||||
}
|
||||
|
||||
public String getOptionallyQuotedFileName() {
|
||||
var n = getFileName();
|
||||
return FileNames.quoteIfNecessary(n);
|
||||
}
|
||||
|
||||
public String getOptionallyQuotedFilePath() {
|
||||
var n = rawFileEntry.getPath();
|
||||
return FileNames.quoteIfNecessary(n);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -33,7 +32,7 @@ public class FileBrowserModel {
|
|||
public static final FileBrowserModel DEFAULT = new FileBrowserModel(Mode.BROWSER);
|
||||
|
||||
private final Mode mode;
|
||||
private final ObservableList<FileSystem.FileEntry> selectedFiles = FXCollections.observableArrayList();
|
||||
private final ObservableList<FileBrowserEntry> selectedFiles = FXCollections.observableArrayList();
|
||||
|
||||
@Setter
|
||||
private Consumer<List<FileStore>> onFinish;
|
||||
|
@ -52,7 +51,11 @@ public class FileBrowserModel {
|
|||
if (selectedFiles.size() == 0) {
|
||||
return;
|
||||
}
|
||||
var stores = selectedFiles.stream().map(entry -> new FileStore(entry.getFileSystem().getStore(), entry.getPath())).toList();
|
||||
var stores = selectedFiles.stream()
|
||||
.map(entry -> new FileStore(
|
||||
entry.getRawFileEntry().getFileSystem().getStore(),
|
||||
entry.getRawFileEntry().getPath()))
|
||||
.toList();
|
||||
onFinish.accept(stores);
|
||||
}
|
||||
|
||||
|
@ -67,7 +70,9 @@ public class FileBrowserModel {
|
|||
}
|
||||
|
||||
public void openExistingFileSystemIfPresent(ShellStore store) {
|
||||
var found = openFileSystems.stream().filter(model -> Objects.equals(model.getStore().getValue(), store)).findFirst();
|
||||
var found = openFileSystems.stream()
|
||||
.filter(model -> Objects.equals(model.getStore().getValue(), store))
|
||||
.findFirst();
|
||||
if (found.isPresent()) {
|
||||
selected.setValue(found.get());
|
||||
} else {
|
||||
|
@ -75,6 +80,14 @@ public class FileBrowserModel {
|
|||
}
|
||||
}
|
||||
|
||||
public void openFileSystemSync(ShellStore store, String path) throws Exception {
|
||||
var model = new OpenFileSystemModel(this);
|
||||
openFileSystems.add(model);
|
||||
selected.setValue(model);
|
||||
model.switchSync(store);
|
||||
model.cd(path);
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(ShellStore store) {
|
||||
// Prevent multiple tabs in non browser modes
|
||||
if (!mode.equals(Mode.BROWSER)) {
|
||||
|
|
|
@ -33,7 +33,7 @@ public class FileBrowserStatusBarComp extends SimpleComp {
|
|||
}, model.getFileList().getSelected()));
|
||||
|
||||
var allCount = PlatformThread.sync(Bindings.createIntegerBinding(() -> {
|
||||
return model.getFileList().getAll().getValue().size();
|
||||
return (int) model.getFileList().getAll().getValue().stream().filter(entry -> !entry.isSynthetic()).count();
|
||||
}, model.getFileList().getAll()));
|
||||
|
||||
var selectedComp = new LabelComp(Bindings.createStringBinding(() -> {
|
||||
|
@ -52,6 +52,13 @@ public class FileBrowserStatusBarComp extends SimpleComp {
|
|||
);
|
||||
bar.getStyleClass().add("status-bar");
|
||||
AppFont.small(bar);
|
||||
|
||||
// Use status bar as an extension of file list
|
||||
bar.setOnMouseClicked(event -> {
|
||||
model.getFileList().getSelected().clear();
|
||||
event.consume();
|
||||
});
|
||||
|
||||
return bar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,192 +2,98 @@
|
|||
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
|
||||
import io.xpipe.app.ext.DataSourceProvider;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
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.util.BusyProperty;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
final class FileContextMenu extends ContextMenu {
|
||||
|
||||
public boolean isExecutable(FileSystem.FileEntry e) {
|
||||
if (e.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.getExecutable() != null && e.getExecutable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var shell = e.getFileSystem().getShell();
|
||||
if (shell.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var os = shell.get().getOsType();
|
||||
var ending = FilenameUtils.getExtension(e.getPath()).toLowerCase();
|
||||
if (os.equals(OsType.WINDOWS) && List.of("exe", "bat", "ps1", "cmd").contains(ending)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (List.of("sh", "command").contains(ending)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final FileSystem.FileEntry entry;
|
||||
private final Property<FileSystem.FileEntry> editing;
|
||||
private final boolean empty;
|
||||
|
||||
public FileContextMenu(OpenFileSystemModel model, FileSystem.FileEntry entry, Property<FileSystem.FileEntry> editing) {
|
||||
public FileContextMenu(OpenFileSystemModel model, boolean empty) {
|
||||
super();
|
||||
this.model = model;
|
||||
this.entry = entry;
|
||||
this.editing = editing;
|
||||
this.empty = empty;
|
||||
createMenu();
|
||||
}
|
||||
|
||||
private void createMenu() {
|
||||
if (entry.isDirectory()) {
|
||||
var terminal = new MenuItem("Open terminal");
|
||||
terminal.setOnAction(event -> {
|
||||
event.consume();
|
||||
model.openTerminalAsync(entry.getPath());
|
||||
});
|
||||
getItems().add(terminal);
|
||||
} else {
|
||||
if (isExecutable(entry)) {
|
||||
var execute = new MenuItem("Run in terminal");
|
||||
execute.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ShellControl pc = model.getFileSystem().getShell().orElseThrow();
|
||||
var e = pc.getShellDialect().getMakeExecutableCommand(entry.getPath());
|
||||
if (e != null) {
|
||||
pc.executeSimpleBooleanCommand(e);
|
||||
}
|
||||
var cmd = pc.command("\"" + entry.getPath() + "\"").prepareTerminalOpen("?");
|
||||
TerminalHelper.open(FilenameUtils.getBaseName(entry.getPath()), cmd);
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(execute);
|
||||
var selected = empty ? FXCollections.<FileBrowserEntry>observableArrayList() : model.getFileList().getSelected();
|
||||
|
||||
var executeInBackground = new MenuItem("Run in background");
|
||||
executeInBackground.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ShellControl pc = model.getFileSystem().getShell().orElseThrow();
|
||||
var e = pc.getShellDialect().getMakeExecutableCommand(entry.getPath());
|
||||
if (e != null) {
|
||||
pc.executeSimpleBooleanCommand(e);
|
||||
}
|
||||
var cmd = ScriptHelper.createDetachCommand(pc, "\"" + entry.getPath() + "\"");
|
||||
pc.executeSimpleBooleanCommand(cmd);
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(executeInBackground);
|
||||
} else {
|
||||
var open = new MenuItem("Open default");
|
||||
open.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
FileOpener.openInDefaultApplication(entry);
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(open);
|
||||
for (BrowserAction.Category cat : BrowserAction.Category.values()) {
|
||||
var all = BrowserAction.ALL.stream().filter(browserAction -> browserAction.getCategory() == cat).filter(browserAction -> {
|
||||
if (!browserAction.isApplicable(model, selected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!browserAction.acceptsEmptySelection() && selected.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
if (all.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var pipe = new MenuItem("Pipe");
|
||||
pipe.setOnAction(event -> {
|
||||
var store = new FileStore(model.getFileSystem().getStore(), entry.getPath());
|
||||
GuiDsCreatorMultiStep.showForStore(DataSourceProvider.Category.STREAM, store, null);
|
||||
event.consume();
|
||||
});
|
||||
// getItems().add(pipe);
|
||||
if (getItems().size() > 0) {
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
||||
var edit = new MenuItem("Edit");
|
||||
edit.setOnAction(event -> {
|
||||
ThreadHelper.runAsync(() -> FileOpener.openInTextEditor(entry));
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(edit);
|
||||
}
|
||||
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
{
|
||||
|
||||
var copy = new MenuItem("Copy");
|
||||
copy.setOnAction(event -> {
|
||||
FileBrowserClipboard.startCopy(
|
||||
model.getCurrentDirectory(), model.getFileList().getSelected());
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(copy);
|
||||
|
||||
var paste = new MenuItem("Paste");
|
||||
paste.setOnAction(event -> {
|
||||
var clipboard = FileBrowserClipboard.retrieveCopy();
|
||||
if (clipboard != null) {
|
||||
var files = clipboard.getEntries();
|
||||
var target = entry.isDirectory() ? entry : model.getCurrentDirectory();
|
||||
model.dropFilesIntoAsync(target, files, true);
|
||||
for (BrowserAction a : all) {
|
||||
if (a instanceof LeafAction la) {
|
||||
getItems().add(toItem(la, selected, s -> s));
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(paste);
|
||||
|
||||
if (a instanceof BranchAction la) {
|
||||
var m = new Menu(a.getName(model, selected) + " ...");
|
||||
for (LeafAction sub : la.getBranchingActions()) {
|
||||
if (!sub.isApplicable(model, selected)) {
|
||||
continue;
|
||||
}
|
||||
m.getItems().add(toItem(sub, selected, s -> "... " + s));
|
||||
}
|
||||
var graphic = a.getIcon(model, selected);
|
||||
if (graphic != null) {
|
||||
m.setGraphic(graphic);
|
||||
}
|
||||
m.setDisable(!a.isActive(model, selected));
|
||||
getItems().add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
var copyName = new MenuItem("Copy name");
|
||||
copyName.setOnAction(event -> {
|
||||
var selection = new StringSelection(FileNames.getFileName(entry.getPath()));
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
private MenuItem toItem(LeafAction a, List<FileBrowserEntry> selected, UnaryOperator<String> nameFunc) {
|
||||
var mi = new MenuItem(nameFunc.apply(a.getName(model, selected)));
|
||||
mi.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BusyProperty.execute(model.getBusy(), () -> {
|
||||
a.execute(model, selected);
|
||||
});
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(copyName);
|
||||
|
||||
var copyPath = new MenuItem("Copy full path");
|
||||
copyPath.setOnAction(event -> {
|
||||
var selection = new StringSelection(entry.getPath());
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
event.consume();
|
||||
});
|
||||
getItems().add(copyPath);
|
||||
|
||||
var delete = new MenuItem("Delete");
|
||||
delete.setOnAction(event -> {
|
||||
model.deleteSelectionAsync();
|
||||
event.consume();
|
||||
});
|
||||
|
||||
var rename = new MenuItem("Rename");
|
||||
rename.setOnAction(event -> {
|
||||
event.consume();
|
||||
editing.setValue(entry);
|
||||
});
|
||||
|
||||
getItems().addAll(new SeparatorMenuItem(), rename, delete);
|
||||
if (a.getShortcut() != null) {
|
||||
mi.setAccelerator(a.getShortcut());
|
||||
}
|
||||
var graphic = a.getIcon(model, selected);
|
||||
if (graphic != null) {
|
||||
mi.setGraphic(graphic);
|
||||
}
|
||||
mi.setMnemonicParsing(false);
|
||||
mi.setDisable(!a.isActive(model, selected));
|
||||
return mi;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class FileFilterComp extends SimpleComp {
|
||||
|
||||
private final Property<String> filterString;
|
||||
|
||||
public FileFilterComp(Property<String> filterString) {
|
||||
this.filterString = filterString;
|
||||
}
|
||||
public class FileFilterComp extends Comp<FileFilterComp.Structure> {
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
public Structure createBase() {
|
||||
var expanded = new SimpleBooleanProperty();
|
||||
var text = new TextFieldComp(filterString, false).createRegion();
|
||||
var button = new Button();
|
||||
|
@ -57,7 +48,6 @@ public class FileFilterComp extends SimpleComp {
|
|||
|
||||
var fi = new FontIcon("mdi2m-magnify");
|
||||
GrowAugment.create(false, true).augment(new SimpleCompStructure<>(button));
|
||||
Shortcuts.addShortcut(button, new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN));
|
||||
button.setGraphic(fi);
|
||||
button.setOnAction(event -> {
|
||||
if (expanded.get()) {
|
||||
|
@ -75,7 +65,6 @@ public class FileFilterComp extends SimpleComp {
|
|||
text.setPrefWidth(0);
|
||||
button.getStyleClass().add(Styles.FLAT);
|
||||
expanded.addListener((observable, oldValue, val) -> {
|
||||
System.out.println(val);
|
||||
if (val) {
|
||||
text.setPrefWidth(250);
|
||||
button.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
@ -89,6 +78,20 @@ public class FileFilterComp extends SimpleComp {
|
|||
|
||||
var box = new HBox(text, button);
|
||||
box.setFillHeight(true);
|
||||
return box;
|
||||
return new Structure(box, (TextField) text, button);
|
||||
}
|
||||
|
||||
public record Structure(HBox box, TextField textField, Button toggleButton) implements CompStructure<HBox> {
|
||||
|
||||
@Override
|
||||
public HBox get() {
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
||||
private final Property<String> filterString;
|
||||
|
||||
public FileFilterComp(Property<String> filterString) {
|
||||
this.filterString = filterString;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,18 @@ package io.xpipe.app.browser;
|
|||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.SvgCacheComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.Containers;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
@ -30,7 +34,6 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.control.skin.TableViewSkin;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
@ -56,7 +59,7 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
public FileListComp(FileListModel fileList) {
|
||||
this.fileList = fileList;
|
||||
TableView<FileSystem.FileEntry> table = createTable();
|
||||
TableView<FileBrowserEntry> table = createTable();
|
||||
SimpleChangeListener.apply(table.comparatorProperty(), (newValue) -> {
|
||||
fileList.setComparator(newValue);
|
||||
});
|
||||
|
@ -67,64 +70,67 @@ final class FileListComp extends AnchorPane {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TableView<FileSystem.FileEntry> createTable() {
|
||||
var filenameCol = new TableColumn<FileSystem.FileEntry, String>("Name");
|
||||
private TableView<FileBrowserEntry> createTable() {
|
||||
var filenameCol = new TableColumn<FileBrowserEntry, String>("Name");
|
||||
filenameCol.setCellValueFactory(param -> new SimpleStringProperty(
|
||||
param.getValue() != null
|
||||
? FileNames.getFileName(param.getValue().getPath())
|
||||
? FileNames.getFileName(
|
||||
param.getValue().getRawFileEntry().getPath())
|
||||
: null));
|
||||
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
|
||||
filenameCol.setSortType(ASCENDING);
|
||||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
||||
|
||||
var sizeCol = new TableColumn<FileSystem.FileEntry, Number>("Size");
|
||||
sizeCol.setCellValueFactory(
|
||||
param -> new SimpleLongProperty(param.getValue().getSize()));
|
||||
var sizeCol = new TableColumn<FileBrowserEntry, Number>("Size");
|
||||
sizeCol.setCellValueFactory(param ->
|
||||
new SimpleLongProperty(param.getValue().getRawFileEntry().getSize()));
|
||||
sizeCol.setCellFactory(col -> new FileSizeCell());
|
||||
|
||||
var mtimeCol = new TableColumn<FileSystem.FileEntry, Instant>("Modified");
|
||||
mtimeCol.setCellValueFactory(
|
||||
param -> new SimpleObjectProperty<>(param.getValue().getDate()));
|
||||
var mtimeCol = new TableColumn<FileBrowserEntry, Instant>("Modified");
|
||||
mtimeCol.setCellValueFactory(param ->
|
||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().getDate()));
|
||||
mtimeCol.setCellFactory(col -> new FileTimeCell());
|
||||
mtimeCol.getStyleClass().add(Tweaks.ALIGN_RIGHT);
|
||||
|
||||
var modeCol = new TableColumn<FileSystem.FileEntry, String>("Attributes");
|
||||
modeCol.setCellValueFactory(
|
||||
param -> new SimpleObjectProperty<>(param.getValue().getMode()));
|
||||
var modeCol = new TableColumn<FileBrowserEntry, String>("Attributes");
|
||||
modeCol.setCellValueFactory(param ->
|
||||
new SimpleObjectProperty<>(param.getValue().getRawFileEntry().getMode()));
|
||||
modeCol.setCellFactory(col -> new FileModeCell());
|
||||
|
||||
var table = new TableView<FileSystem.FileEntry>();
|
||||
var table = new TableView<FileBrowserEntry>();
|
||||
table.setPlaceholder(new Region());
|
||||
table.getStyleClass().add(Styles.STRIPED);
|
||||
table.getColumns().setAll(filenameCol, sizeCol, modeCol, mtimeCol);
|
||||
table.getSortOrder().add(filenameCol);
|
||||
table.setSortPolicy(param -> {
|
||||
var comp = table.getComparator();
|
||||
if (comp == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var parentFirst = new Comparator<FileSystem.FileEntry>() {
|
||||
@Override
|
||||
public int compare(FileSystem.FileEntry o1, FileSystem.FileEntry o2) {
|
||||
var c = fileList.getFileSystemModel().getCurrentParentDirectory();
|
||||
if (c == null) {
|
||||
return 0;
|
||||
}
|
||||
var syntheticFirst = Comparator.<FileBrowserEntry, Boolean>comparing(path -> !path.isSynthetic());
|
||||
var dirsFirst = Comparator.<FileBrowserEntry, Boolean>comparing(
|
||||
path -> !path.getRawFileEntry().isDirectory());
|
||||
|
||||
return o1.getPath().equals(c.getPath()) ? -1 : (o2.getPath().equals(c.getPath()) ? 1 : 0);
|
||||
}
|
||||
};
|
||||
var dirsFirst = Comparator.<FileSystem.FileEntry, Boolean>comparing(path -> !path.isDirectory());
|
||||
|
||||
Comparator<? super FileSystem.FileEntry> us =
|
||||
parentFirst.thenComparing(dirsFirst).thenComparing(comp);
|
||||
FXCollections.sort(table.getItems(), us);
|
||||
Comparator<? super FileBrowserEntry> us =
|
||||
syntheticFirst.thenComparing(dirsFirst).thenComparing(comp);
|
||||
FXCollections.sort(param.getItems(), us);
|
||||
return true;
|
||||
});
|
||||
table.getSortOrder().add(filenameCol);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
filenameCol.minWidthProperty().bind(table.widthProperty().multiply(0.5));
|
||||
|
||||
table.setFixedCellSize(34.0);
|
||||
|
||||
prepareTableSelectionModel(table);
|
||||
prepareTableShortcuts(table);
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, mtimeCol, modeCol);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private void prepareTableSelectionModel(TableView<FileBrowserEntry> table) {
|
||||
if (fileList.getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)
|
||||
|| fileList.getMode().equals(FileBrowserModel.Mode.DIRECTORY_CHOOSER)) {
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
|
@ -132,59 +138,68 @@ final class FileListComp extends AnchorPane {
|
|||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
}
|
||||
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileSystem.FileEntry>)
|
||||
c -> {
|
||||
// Explicitly unselect synthetic entries since we can't use a custom selection model as that is bugged in JavaFX
|
||||
var toSelect = c.getList().stream()
|
||||
.filter(entry -> fileList.getFileSystemModel().getCurrentParentDirectory() == null
|
||||
|| !entry.getPath()
|
||||
.equals(fileList.getFileSystemModel()
|
||||
.getCurrentParentDirectory()
|
||||
.getPath()))
|
||||
.toList();
|
||||
fileList.getSelected().setAll(toSelect);
|
||||
fileList.getFileSystemModel()
|
||||
.getBrowserModel()
|
||||
.getSelectedFiles()
|
||||
.setAll(toSelect);
|
||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super FileBrowserEntry>) c -> {
|
||||
// Explicitly unselect synthetic entries since we can't use a custom selection model as that is bugged in
|
||||
// JavaFX
|
||||
var toSelect = c.getList().stream()
|
||||
.filter(entry -> fileList.getFileSystemModel().getCurrentParentDirectory() == null
|
||||
|| !entry.getRawFileEntry()
|
||||
.getPath()
|
||||
.equals(fileList.getFileSystemModel()
|
||||
.getCurrentParentDirectory()
|
||||
.getPath()))
|
||||
.toList();
|
||||
fileList.getSelected().setAll(toSelect);
|
||||
fileList.getFileSystemModel().getBrowserModel().getSelectedFiles().setAll(toSelect);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var toUnselect = table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(entry -> !toSelect.contains(entry))
|
||||
.toList();
|
||||
toUnselect.forEach(entry -> table.getSelectionModel()
|
||||
.clearSelection(table.getItems().indexOf(entry)));
|
||||
});
|
||||
});
|
||||
|
||||
table.setOnKeyPressed(event -> {
|
||||
if (event.isControlDown()
|
||||
&& event.getCode().equals(KeyCode.C)
|
||||
&& table.getSelectionModel().getSelectedItems().size() > 0) {
|
||||
FileBrowserClipboard.startCopy(
|
||||
fileList.getFileSystemModel().getCurrentDirectory(),
|
||||
table.getSelectionModel().getSelectedItems());
|
||||
event.consume();
|
||||
}
|
||||
|
||||
if (event.isControlDown() && event.getCode().equals(KeyCode.V)) {
|
||||
var clipboard = FileBrowserClipboard.retrieveCopy();
|
||||
if (clipboard != null) {
|
||||
var files = clipboard.getEntries();
|
||||
var target = fileList.getFileSystemModel().getCurrentDirectory();
|
||||
fileList.getFileSystemModel().dropFilesIntoAsync(target, files, true);
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
var toUnselect = table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(entry -> !toSelect.contains(entry))
|
||||
.toList();
|
||||
toUnselect.forEach(entry -> table.getSelectionModel()
|
||||
.clearSelection(table.getItems().indexOf(entry)));
|
||||
});
|
||||
});
|
||||
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, mtimeCol, modeCol);
|
||||
fileList.getSelected().addListener((ListChangeListener<? super FileBrowserEntry>) c -> {
|
||||
if (c.getList().equals(table.getSelectionModel().getSelectedItems())) {
|
||||
return;
|
||||
}
|
||||
|
||||
return table;
|
||||
Platform.runLater(() -> {
|
||||
if (c.getList().isEmpty()) {
|
||||
table.getSelectionModel().clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
var indices = c.getList().stream()
|
||||
.skip(1)
|
||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||
.toArray();
|
||||
table.getSelectionModel()
|
||||
.selectIndices(table.getItems().indexOf(c.getList().get(0)), indices);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareTableEntries(TableView<FileSystem.FileEntry> table) {
|
||||
private void prepareTableShortcuts(TableView<FileBrowserEntry> table) {
|
||||
table.setOnKeyPressed(event -> {
|
||||
var selected = fileList.getSelected();
|
||||
BrowserAction.getFlattened().stream()
|
||||
.filter(browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected)
|
||||
&& browserAction.isActive(fileList.getFileSystemModel(), selected))
|
||||
.filter(browserAction -> browserAction.getShortcut() != null)
|
||||
.filter(browserAction -> browserAction.getShortcut().match(event))
|
||||
.findAny()
|
||||
.ifPresent(browserAction -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
browserAction.execute(fileList.getFileSystemModel(), selected);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareTableEntries(TableView<FileBrowserEntry> table) {
|
||||
var emptyEntry = new FileListCompEntry(table, null, fileList);
|
||||
table.setOnDragOver(event -> {
|
||||
emptyEntry.onDragOver(event);
|
||||
|
@ -203,7 +218,15 @@ final class FileListComp extends AnchorPane {
|
|||
});
|
||||
|
||||
table.setRowFactory(param -> {
|
||||
TableRow<FileSystem.FileEntry> row = new TableRow<>();
|
||||
TableRow<FileBrowserEntry> row = new TableRow<>();
|
||||
new ContextMenuAugment<>(false, () -> {
|
||||
if (row.getItem() != null && row.getItem().isSynthetic()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FileContextMenu(fileList.getFileSystemModel(), row.getItem() == null);
|
||||
})
|
||||
.augment(new SimpleCompStructure<>(row));
|
||||
var listEntry = Bindings.createObjectBinding(
|
||||
() -> new FileListCompEntry(row, row.getItem(), fileList), row.itemProperty());
|
||||
|
||||
|
@ -214,8 +237,10 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
row.itemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
row.pseudoClassStateChanged(EMPTY, newValue == null);
|
||||
row.pseudoClassStateChanged(FILE, newValue != null && !newValue.isDirectory());
|
||||
row.pseudoClassStateChanged(FOLDER, newValue != null && newValue.isDirectory());
|
||||
row.pseudoClassStateChanged(
|
||||
FILE, newValue != null && !newValue.getRawFileEntry().isDirectory());
|
||||
row.pseudoClassStateChanged(
|
||||
FOLDER, newValue != null && newValue.getRawFileEntry().isDirectory());
|
||||
});
|
||||
|
||||
fileList.getDraggedOverDirectory().addListener((observable, oldValue, newValue) -> {
|
||||
|
@ -250,19 +275,19 @@ final class FileListComp extends AnchorPane {
|
|||
return row;
|
||||
});
|
||||
}
|
||||
private void prepareTableChanges(TableView<FileSystem.FileEntry> table, TableColumn<FileSystem.FileEntry, Instant> mtimeCol, TableColumn<FileSystem.FileEntry, String> modeCol) {
|
||||
|
||||
private void prepareTableChanges(
|
||||
TableView<FileBrowserEntry> table,
|
||||
TableColumn<FileBrowserEntry, Instant> mtimeCol,
|
||||
TableColumn<FileBrowserEntry, String> modeCol) {
|
||||
var lastDir = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||
Runnable updateHandler = () -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var newItems = new ArrayList<FileSystem.FileEntry>();
|
||||
var parentEntry = fileList.getFileSystemModel().getCurrentParentDirectory();
|
||||
if (parentEntry != null) {
|
||||
newItems.add(parentEntry);
|
||||
}
|
||||
newItems.addAll(fileList.getShown().getValue());
|
||||
var newItems = new ArrayList<>(fileList.getShown().getValue());
|
||||
|
||||
var hasModifiedDate =
|
||||
newItems.size() == 0 || newItems.stream().anyMatch(entry -> entry.getDate() != null);
|
||||
var hasModifiedDate = newItems.size() == 0
|
||||
|| newItems.stream()
|
||||
.anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
|
||||
if (!hasModifiedDate) {
|
||||
table.getColumns().remove(mtimeCol);
|
||||
} else {
|
||||
|
@ -340,7 +365,7 @@ final class FileListComp extends AnchorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private class FilenameCell extends TableCell<FileSystem.FileEntry, String> {
|
||||
private class FilenameCell extends TableCell<FileBrowserEntry, String> {
|
||||
|
||||
private final StringProperty img = new SimpleStringProperty();
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
|
@ -349,18 +374,17 @@ final class FileListComp extends AnchorPane {
|
|||
.createRegion();
|
||||
private final StackPane textField =
|
||||
new LazyTextFieldComp(text).createStructure().get();
|
||||
private final ChangeListener<String> listener;
|
||||
|
||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||
|
||||
public FilenameCell(Property<FileSystem.FileEntry> editing) {
|
||||
public FilenameCell(Property<FileBrowserEntry> editing) {
|
||||
editing.addListener((observable, oldValue, newValue) -> {
|
||||
if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) {
|
||||
textField.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
listener = (observable, oldValue, newValue) -> {
|
||||
ChangeListener<String> listener = (observable, oldValue, newValue) -> {
|
||||
if (updating.get()) {
|
||||
return;
|
||||
}
|
||||
|
@ -380,7 +404,7 @@ final class FileListComp extends AnchorPane {
|
|||
return;
|
||||
}
|
||||
|
||||
try (var b = new BusyProperty(updating)) {
|
||||
try (var ignored = new BusyProperty(updating)) {
|
||||
super.updateItem(fullPath, empty);
|
||||
setText(null);
|
||||
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
||||
|
@ -397,18 +421,20 @@ final class FileListComp extends AnchorPane {
|
|||
|
||||
var isParentLink = getTableRow()
|
||||
.getItem()
|
||||
.getRawFileEntry()
|
||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
||||
img.set(FileIconManager.getFileIcon(
|
||||
isParentLink
|
||||
? fileList.getFileSystemModel().getCurrentDirectory()
|
||||
: getTableRow().getItem(),
|
||||
: getTableRow().getItem().getRawFileEntry(),
|
||||
isParentLink));
|
||||
|
||||
var isDirectory = getTableRow().getItem().isDirectory();
|
||||
var isDirectory = getTableRow().getItem().getRawFileEntry().isDirectory();
|
||||
pseudoClassStateChanged(FOLDER, isDirectory);
|
||||
|
||||
var fileName = isParentLink ? ".." : FileNames.getFileName(fullPath);
|
||||
var hidden = !isParentLink && (getTableRow().getItem().isHidden() || fileName.startsWith("."));
|
||||
var hidden = !isParentLink
|
||||
&& (getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith("."));
|
||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||
text.set(fileName);
|
||||
}
|
||||
|
@ -416,7 +442,7 @@ final class FileListComp extends AnchorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private class FileSizeCell extends TableCell<FileSystem.FileEntry, Number> {
|
||||
private static class FileSizeCell extends TableCell<FileBrowserEntry, Number> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Number fileSize, boolean empty) {
|
||||
|
@ -425,7 +451,7 @@ final class FileListComp extends AnchorPane {
|
|||
setText(null);
|
||||
} else {
|
||||
var path = getTableRow().getItem();
|
||||
if (path.isDirectory()) {
|
||||
if (path.getRawFileEntry().isDirectory()) {
|
||||
setText("");
|
||||
} else {
|
||||
setText(byteCount(fileSize.longValue()));
|
||||
|
@ -434,7 +460,7 @@ final class FileListComp extends AnchorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private class FileModeCell extends TableCell<FileSystem.FileEntry, String> {
|
||||
private static class FileModeCell extends TableCell<FileBrowserEntry, String> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(String mode, boolean empty) {
|
||||
|
@ -447,7 +473,7 @@ final class FileListComp extends AnchorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private static class FileTimeCell extends TableCell<FileSystem.FileEntry, Instant> {
|
||||
private static class FileTimeCell extends TableCell<FileBrowserEntry, Instant> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Instant fileTime, boolean empty) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.TableView;
|
||||
|
@ -18,14 +17,13 @@ public class FileListCompEntry {
|
|||
public static final Timer DROP_TIMER = new Timer("dnd", true);
|
||||
|
||||
private final Node row;
|
||||
private final FileSystem.FileEntry item;
|
||||
private final FileBrowserEntry item;
|
||||
private final FileListModel model;
|
||||
|
||||
private Point2D lastOver = new Point2D(-1, -1);
|
||||
private TimerTask activeTask;
|
||||
private FileContextMenu currentContextMenu;
|
||||
|
||||
public FileListCompEntry(Node row, FileSystem.FileEntry item, FileListModel model) {
|
||||
public FileListCompEntry(Node row, FileBrowserEntry item, FileListModel model) {
|
||||
this.row = row;
|
||||
this.item = item;
|
||||
this.model = model;
|
||||
|
@ -34,6 +32,7 @@ public class FileListCompEntry {
|
|||
@SuppressWarnings("unchecked")
|
||||
public void onMouseClick(MouseEvent t) {
|
||||
if (item == null) {
|
||||
model.getSelected().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -48,7 +47,7 @@ public class FileListCompEntry {
|
|||
}
|
||||
|
||||
if (t.getButton() == MouseButton.PRIMARY && t.isShiftDown()) {
|
||||
var tv = ((TableView<FileSystem.FileEntry>) row.getParent().getParent().getParent().getParent());
|
||||
var tv = ((TableView<FileBrowserEntry>) row.getParent().getParent().getParent().getParent());
|
||||
var all = tv.getItems();
|
||||
var min = tv.getSelectionModel().getSelectedItems().stream().mapToInt(entry -> all.indexOf(entry)).min().orElse(1);
|
||||
var max = tv.getSelectionModel().getSelectedItems().stream().mapToInt(entry -> all.indexOf(entry)).max().orElse(all.size() - 1);
|
||||
|
@ -58,25 +57,10 @@ public class FileListCompEntry {
|
|||
t.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentContextMenu != null) {
|
||||
currentContextMenu.hide();
|
||||
currentContextMenu = null;
|
||||
t.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.getButton() == MouseButton.SECONDARY) {
|
||||
var cm = new FileContextMenu(model.getFileSystemModel(), item, model.getEditing());
|
||||
cm.show(row, t.getScreenX(), t.getScreenY());
|
||||
currentContextMenu = cm;
|
||||
t.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return item != null && item.equals(model.getFileSystemModel().getCurrentParentDirectory());
|
||||
return item != null && item.getRawFileEntry().equals(model.getFileSystemModel().getCurrentParentDirectory());
|
||||
}
|
||||
|
||||
private boolean acceptsDrop(DragEvent event) {
|
||||
|
@ -96,7 +80,7 @@ public class FileListCompEntry {
|
|||
// Prevent drag and drops of files into the current directory
|
||||
if (FileBrowserClipboard.currentDragClipboard
|
||||
.getBaseDirectory().getPath()
|
||||
.equals(model.getFileSystemModel().getCurrentDirectory().getPath()) && (item == null || !item.isDirectory())) {
|
||||
.equals(model.getFileSystemModel().getCurrentDirectory().getPath()) && (item == null || !item.getRawFileEntry().isDirectory())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -116,8 +100,8 @@ public class FileListCompEntry {
|
|||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
Dragboard db = event.getDragboard();
|
||||
var list = db.getFiles().stream().map(File::toPath).toList();
|
||||
var target = item != null && item.isDirectory()
|
||||
? item
|
||||
var target = item != null && item.getRawFileEntry().isDirectory()
|
||||
? item.getRawFileEntry()
|
||||
: model.getFileSystemModel().getCurrentDirectory();
|
||||
model.getFileSystemModel().dropLocalFilesIntoAsync(target, list);
|
||||
event.setDropCompleted(true);
|
||||
|
@ -127,8 +111,8 @@ public class FileListCompEntry {
|
|||
// Accept drops from inside the app window
|
||||
if (event.getGestureSource() != null) {
|
||||
var files = FileBrowserClipboard.retrieveDrag(event.getDragboard()).getEntries();
|
||||
var target = item != null && item.isDirectory()
|
||||
? item
|
||||
var target = item != null && item.getRawFileEntry().isDirectory()
|
||||
? item.getRawFileEntry()
|
||||
: model.getFileSystemModel().getCurrentDirectory();
|
||||
model.getFileSystemModel().dropFilesIntoAsync(target, files, false);
|
||||
event.setDropCompleted(true);
|
||||
|
@ -137,7 +121,7 @@ public class FileListCompEntry {
|
|||
}
|
||||
|
||||
public void onDragExited(DragEvent event) {
|
||||
if (item != null && item.isDirectory()) {
|
||||
if (item != null && item.getRawFileEntry().isDirectory()) {
|
||||
model.getDraggedOverDirectory().setValue(null);
|
||||
} else {
|
||||
model.getDraggedOverEmpty().setValue(false);
|
||||
|
@ -154,7 +138,7 @@ public class FileListCompEntry {
|
|||
return;
|
||||
}
|
||||
|
||||
var selected = model.getSelected();
|
||||
var selected = model.getSelectedRaw();
|
||||
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
|
||||
db.setContent(FileBrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected));
|
||||
|
||||
|
@ -166,13 +150,13 @@ public class FileListCompEntry {
|
|||
}
|
||||
|
||||
private void acceptDrag(DragEvent event) {
|
||||
model.getDraggedOverEmpty().setValue(item == null || !item.isDirectory());
|
||||
model.getDraggedOverEmpty().setValue(item == null || !item.getRawFileEntry().isDirectory());
|
||||
model.getDraggedOverDirectory().setValue(item);
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
|
||||
private void handleHoverTimer(DragEvent event) {
|
||||
if (item == null || !item.isDirectory()) {
|
||||
if (item == null || !item.getRawFileEntry().isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -192,7 +176,7 @@ public class FileListCompEntry {
|
|||
return;
|
||||
}
|
||||
|
||||
model.getFileSystemModel().cd(item.getPath());
|
||||
model.getFileSystemModel().cd(item.getRawFileEntry().getPath());
|
||||
}
|
||||
};
|
||||
DROP_TIMER.schedule(activeTask, 1000);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
|
@ -22,25 +23,27 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
final class FileListModel {
|
||||
public final class FileListModel {
|
||||
|
||||
static final Comparator<FileSystem.FileEntry> FILE_TYPE_COMPARATOR =
|
||||
Comparator.comparing(path -> !path.isDirectory());
|
||||
static final Predicate<FileSystem.FileEntry> PREDICATE_ANY = path -> true;
|
||||
static final Predicate<FileSystem.FileEntry> PREDICATE_NOT_HIDDEN = path -> true;
|
||||
static final Comparator<FileBrowserEntry> FILE_TYPE_COMPARATOR =
|
||||
Comparator.comparing(path -> !path.getRawFileEntry().isDirectory());
|
||||
static final Predicate<FileBrowserEntry> PREDICATE_ANY = path -> true;
|
||||
static final Predicate<FileBrowserEntry> PREDICATE_NOT_HIDDEN = path -> true;
|
||||
|
||||
private final OpenFileSystemModel fileSystemModel;
|
||||
private final Property<Comparator<FileSystem.FileEntry>> comparatorProperty =
|
||||
private final Property<Comparator<FileBrowserEntry>> comparatorProperty =
|
||||
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
|
||||
private final Property<List<FileSystem.FileEntry>> all = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final Property<List<FileSystem.FileEntry>> shown = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final ObjectProperty<Predicate<FileSystem.FileEntry>> predicateProperty =
|
||||
private final Property<List<FileBrowserEntry>> all = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final Property<List<FileBrowserEntry>> shown = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final ObjectProperty<Predicate<FileBrowserEntry>> predicateProperty =
|
||||
new SimpleObjectProperty<>(path -> true);
|
||||
private final ObservableList<FileSystem.FileEntry> selected = FXCollections.observableArrayList();
|
||||
private final ObservableList<FileBrowserEntry> selected = FXCollections.observableArrayList();
|
||||
private final ObservableList<FileSystem.FileEntry> selectedRaw =
|
||||
BindingsHelper.mappedContentBinding(selected, entry -> entry.getRawFileEntry());
|
||||
|
||||
private final Property<FileSystem.FileEntry> draggedOverDirectory = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||
private final Property<FileBrowserEntry> draggedOverDirectory = new SimpleObjectProperty<FileBrowserEntry>();
|
||||
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();
|
||||
private final Property<FileSystem.FileEntry> editing = new SimpleObjectProperty<>();
|
||||
private final Property<FileBrowserEntry> editing = new SimpleObjectProperty<>();
|
||||
|
||||
public FileListModel(OpenFileSystemModel fileSystemModel) {
|
||||
this.fileSystemModel = fileSystemModel;
|
||||
|
@ -54,35 +57,42 @@ final class FileListModel {
|
|||
return fileSystemModel.getBrowserModel().getMode();
|
||||
}
|
||||
|
||||
public void setAll(List<FileSystem.FileEntry> newFiles) {
|
||||
all.setValue(newFiles);
|
||||
refreshShown();
|
||||
}
|
||||
|
||||
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
|
||||
try (var s = newFiles) {
|
||||
var l = s.filter(entry -> entry != null).limit(5000).toList();
|
||||
var parent = fileSystemModel.getCurrentParentDirectory();
|
||||
var l = Stream.concat(
|
||||
parent != null ? Stream.of(new FileBrowserEntry(parent, this, true)) : Stream.of(),
|
||||
s.filter(entry -> entry != null)
|
||||
.limit(5000)
|
||||
.map(entry -> new FileBrowserEntry(entry, this, false)))
|
||||
.toList();
|
||||
all.setValue(l);
|
||||
refreshShown();
|
||||
}
|
||||
}
|
||||
|
||||
public void setComparator(Comparator<FileSystem.FileEntry> comparator) {
|
||||
public void setComparator(Comparator<FileBrowserEntry> comparator) {
|
||||
comparatorProperty.setValue(comparator);
|
||||
refreshShown();
|
||||
}
|
||||
|
||||
private void refreshShown() {
|
||||
List<FileSystem.FileEntry> filtered = fileSystemModel.getFilter().getValue() != null ? all.getValue().stream().filter(entry -> {
|
||||
var name = FileNames.getFileName(entry.getPath()).toLowerCase(Locale.ROOT);
|
||||
var filterString = fileSystemModel.getFilter().getValue().toLowerCase(Locale.ROOT);
|
||||
return name.contains(filterString);
|
||||
}).toList() : all.getValue();
|
||||
List<FileBrowserEntry> filtered = fileSystemModel.getFilter().getValue() != null
|
||||
? all.getValue().stream()
|
||||
.filter(entry -> {
|
||||
var name = FileNames.getFileName(
|
||||
entry.getRawFileEntry().getPath())
|
||||
.toLowerCase(Locale.ROOT);
|
||||
var filterString =
|
||||
fileSystemModel.getFilter().getValue().toLowerCase(Locale.ROOT);
|
||||
return name.contains(filterString);
|
||||
})
|
||||
.toList()
|
||||
: all.getValue();
|
||||
|
||||
Comparator<FileSystem.FileEntry> tableComparator = comparatorProperty.getValue();
|
||||
var comparator = tableComparator != null
|
||||
? FILE_TYPE_COMPARATOR.thenComparing(tableComparator)
|
||||
: FILE_TYPE_COMPARATOR;
|
||||
Comparator<FileBrowserEntry> tableComparator = comparatorProperty.getValue();
|
||||
var comparator =
|
||||
tableComparator != null ? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) : FILE_TYPE_COMPARATOR;
|
||||
var listCopy = new ArrayList<>(filtered);
|
||||
listCopy.sort(comparator);
|
||||
shown.setValue(listCopy);
|
||||
|
@ -101,23 +111,23 @@ final class FileListModel {
|
|||
}
|
||||
}
|
||||
|
||||
public void onDoubleClick(FileSystem.FileEntry entry) {
|
||||
if (!entry.isDirectory() && getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)) {
|
||||
public void onDoubleClick(FileBrowserEntry entry) {
|
||||
if (!entry.getRawFileEntry().isDirectory() && getMode().equals(FileBrowserModel.Mode.SINGLE_FILE_CHOOSER)) {
|
||||
getFileSystemModel().getBrowserModel().finishChooser();
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
var dir = fileSystemModel.cd(entry.getPath());
|
||||
if (entry.getRawFileEntry().isDirectory()) {
|
||||
var dir = fileSystemModel.cd(entry.getRawFileEntry().getPath());
|
||||
if (dir.isPresent()) {
|
||||
fileSystemModel.cd(dir.get());
|
||||
}
|
||||
} else {
|
||||
FileOpener.openInTextEditor(entry);
|
||||
FileOpener.openInTextEditor(entry.getRawFileEntry());
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectProperty<Predicate<FileSystem.FileEntry>> predicateProperty() {
|
||||
public ObjectProperty<Predicate<FileBrowserEntry>> predicateProperty() {
|
||||
return predicateProperty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class OpenFileSystemCache {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final Map<String, Boolean> installedApplications = new HashMap<>();
|
||||
|
||||
public OpenFileSystemCache(OpenFileSystemModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public boolean isApplicationInPath(String app) {
|
||||
if (!installedApplications.containsKey(app)) {
|
||||
try {
|
||||
var b = ApplicationHelper.isInPath(model.getFileSystem().getShell().orElseThrow(), app);
|
||||
installedApplications.put(app, b);
|
||||
} catch (Exception e) {
|
||||
installedApplications.put(app, false);
|
||||
}
|
||||
}
|
||||
|
||||
return installedApplications.get(app);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import javafx.geometry.Pos;
|
|||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.layout.*;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
@ -66,7 +67,8 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
addBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory()));
|
||||
Shortcuts.addShortcut(addBtn, new KeyCodeCombination(KeyCode.PLUS));
|
||||
|
||||
var filter = new FileFilterComp(model.getFilter()).createRegion();
|
||||
var filter = new FileFilterComp(model.getFilter()).createStructure();
|
||||
Shortcuts.addShortcut(filter.toggleButton(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN));
|
||||
|
||||
var topBar = new ToolBar();
|
||||
topBar.getItems().setAll(
|
||||
|
@ -74,7 +76,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
forthBtn,
|
||||
new Spacer(10),
|
||||
pathBar,
|
||||
filter,
|
||||
filter.get(),
|
||||
refreshBtn,
|
||||
terminalBtn,
|
||||
addBtn
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
|
@ -15,6 +18,7 @@ import io.xpipe.core.store.ShellStore;
|
|||
import javafx.beans.property.*;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.function.FailableConsumer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
@ -22,9 +26,11 @@ import java.time.Instant;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
final class OpenFileSystemModel {
|
||||
public final class OpenFileSystemModel {
|
||||
|
||||
private Property<FileSystemStore> store = new SimpleObjectProperty<>();
|
||||
private FileSystem fileSystem;
|
||||
|
@ -35,21 +41,56 @@ final class OpenFileSystemModel {
|
|||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
private final FileBrowserModel browserModel;
|
||||
private final BooleanProperty noDirectory = new SimpleBooleanProperty();
|
||||
private final Property<OpenFileSystemSavedState> savedState = new SimpleObjectProperty<>();
|
||||
private final OpenFileSystemCache cache = new OpenFileSystemCache(this);
|
||||
|
||||
public OpenFileSystemModel(FileBrowserModel browserModel) {
|
||||
this.browserModel = browserModel;
|
||||
fileList = new FileListModel(this);
|
||||
addListeners();
|
||||
}
|
||||
|
||||
public void withShell(FailableConsumer<ShellControl, Exception> c, boolean refresh) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (fileSystem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BusyProperty.execute(busy, () -> {
|
||||
if (store.getValue() instanceof ShellStore s) {
|
||||
c.accept(fileSystem.getShell().orElseThrow());
|
||||
if (refresh) {
|
||||
refreshSync();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
savedState.addListener((observable, oldValue, newValue) -> {
|
||||
if (store.getValue() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(store.getValue());
|
||||
storageEntry.ifPresent(entry -> AppCache.update("browser-state-" + entry.getUuid(), newValue));
|
||||
});
|
||||
|
||||
currentPath.addListener((observable, oldValue, newValue) -> {
|
||||
savedState.setValue(savedState.getValue().withLastDirectory(newValue));
|
||||
});
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void refresh() {
|
||||
BusyProperty.execute(busy, () -> {
|
||||
cdSync(currentPath.get());
|
||||
cdSyncWithoutCheck(currentPath.get());
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshInternal() throws Exception {
|
||||
cdSync(currentPath.get());
|
||||
public void refreshSync() throws Exception {
|
||||
cdSyncWithoutCheck(currentPath.get());
|
||||
}
|
||||
|
||||
public FileSystem.FileEntry getCurrentParentDirectory() {
|
||||
|
@ -93,13 +134,13 @@ final class OpenFileSystemModel {
|
|||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
try (var ignored = new BusyProperty(busy)) {
|
||||
cdSync(path);
|
||||
cdSyncWithoutCheck(path);
|
||||
}
|
||||
});
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void cdSync(String path) throws Exception {
|
||||
private void cdSyncWithoutCheck(String path) throws Exception {
|
||||
if (fileSystem == null) {
|
||||
var fs = store.getValue().createFileSystem();
|
||||
fs.open();
|
||||
|
@ -111,6 +152,7 @@ final class OpenFileSystemModel {
|
|||
|
||||
filter.setValue(null);
|
||||
currentPath.set(path);
|
||||
savedState.setValue(savedState.getValue().withLastDirectory(path));
|
||||
history.updateCurrent(path);
|
||||
loadFilesSync(path);
|
||||
}
|
||||
|
@ -130,7 +172,7 @@ final class OpenFileSystemModel {
|
|||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
fileList.setAll(List.of());
|
||||
fileList.setAll(Stream.of());
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
|
@ -144,7 +186,7 @@ final class OpenFileSystemModel {
|
|||
}
|
||||
|
||||
FileSystemHelper.dropLocalFilesInto(entry, files);
|
||||
refreshInternal();
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -165,7 +207,7 @@ final class OpenFileSystemModel {
|
|||
}
|
||||
|
||||
FileSystemHelper.dropFilesInto(target, files, explicitCopy);
|
||||
refreshInternal();
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -191,7 +233,7 @@ final class OpenFileSystemModel {
|
|||
}
|
||||
|
||||
fileSystem.mkdirs(abs);
|
||||
refreshInternal();
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -213,7 +255,7 @@ final class OpenFileSystemModel {
|
|||
|
||||
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
|
||||
fileSystem.touch(abs);
|
||||
refreshInternal();
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -225,12 +267,12 @@ final class OpenFileSystemModel {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!FileBrowserAlerts.showDeleteAlert(fileList.getSelected())) {
|
||||
if (!FileBrowserAlerts.showDeleteAlert(fileList.getSelectedRaw())) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileSystemHelper.delete(fileList.getSelected());
|
||||
refreshInternal();
|
||||
FileSystemHelper.delete(fileList.getSelectedRaw());
|
||||
refreshSync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -257,8 +299,22 @@ final class OpenFileSystemModel {
|
|||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
|
||||
var current = FileSystemHelper.getStartDirectory(this);
|
||||
cdSync(current);
|
||||
var storageEntry = DataStorage.get()
|
||||
.getStoreEntryIfPresent(fileSystem)
|
||||
.map(entry -> entry.getUuid())
|
||||
.orElse(UUID.randomUUID());
|
||||
this.savedState.setValue(
|
||||
AppCache.get("browser-state-" + storageEntry, OpenFileSystemSavedState.class, () -> {
|
||||
try {
|
||||
return OpenFileSystemSavedState.builder()
|
||||
.lastDirectory(FileSystemHelper.getStartDirectory(this))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
cdSyncWithoutCheck(this.savedState.getValue().getLastDirectory());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
@Value
|
||||
@With
|
||||
@Jacksonized
|
||||
@Builder
|
||||
public class OpenFileSystemSavedState {
|
||||
|
||||
String lastDirectory;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApplicationPathAction extends BrowserAction {
|
||||
|
||||
public abstract String getExecutable();
|
||||
|
||||
@Override
|
||||
public default boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
if (entries.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return entries.stream().allMatch(entry -> isApplicable(model, entry));
|
||||
}
|
||||
|
||||
boolean isApplicable(OpenFileSystemModel model, FileBrowserEntry entry);
|
||||
|
||||
@Override
|
||||
public default boolean isActive(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return model.getCache().isApplicationInPath(getExecutable());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BranchAction extends BrowserAction {
|
||||
|
||||
List<LeafAction> getBranchingActions();
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public interface BrowserAction {
|
||||
|
||||
static enum Category {
|
||||
CUSTOM,
|
||||
OPEN,
|
||||
COPY_PASTE,
|
||||
MUTATION
|
||||
}
|
||||
|
||||
static List<BrowserAction> ALL = new ArrayList<>();
|
||||
|
||||
public static List<LeafAction> getFlattened() {
|
||||
return ALL.stream()
|
||||
.map(browserAction -> browserAction instanceof LeafAction
|
||||
? List.of((LeafAction) browserAction)
|
||||
: ((BranchAction) browserAction).getBranchingActions())
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
default Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Category getCategory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default KeyCombination getShortcut() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean acceptsEmptySelection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries);
|
||||
|
||||
public default boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public default boolean isActive(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Loader implements ModuleLayerLoader {
|
||||
|
||||
@Override
|
||||
public void init(ModuleLayer layer) {
|
||||
ALL.addAll(ServiceLoader.load(layer, BrowserAction.class).stream()
|
||||
.map(actionProviderProvider -> actionProviderProvider.get())
|
||||
.filter(provider -> {
|
||||
try {
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresFullDaemon() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prioritizeLoading() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ExecuteApplicationAction implements LeafAction, ApplicationPathAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
for (FileBrowserEntry entry : entries) {
|
||||
var command = detach() ? ScriptHelper.createDetachCommand(sc, createCommand(model, entry)) : createCommand(model, entry);
|
||||
try (var cc = sc.command(command).workingDirectory(model.getCurrentDirectory().getPath()).start()) {
|
||||
cc.discardOrThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract String createCommand(OpenFileSystemModel model, FileBrowserEntry entry);
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LeafAction extends BrowserAction {
|
||||
|
||||
public abstract void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception;
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class MultiExecuteAction implements BranchAction {
|
||||
|
||||
protected String filesArgument(List<FileBrowserEntry> entries) {
|
||||
return entries.size() == 1 ? entries.get(0).getOptionallyQuotedFileName() : "(" + entries.size() + ")";
|
||||
}
|
||||
|
||||
protected abstract String createCommand(ShellControl sc, OpenFileSystemModel model, FileBrowserEntry entry);
|
||||
|
||||
@Override
|
||||
public List<LeafAction> getBranchingActions() {
|
||||
return List.of(
|
||||
new LeafAction() {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
model.withShell(
|
||||
pc -> {
|
||||
for (FileBrowserEntry entry : entries) {
|
||||
var cmd = pc.command(createCommand(pc, model, entry))
|
||||
.workingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
.prepareTerminalOpen(FileNames.getFileName(
|
||||
entry.getRawFileEntry().getPath()));
|
||||
TerminalHelper.open(
|
||||
FilenameUtils.getBaseName(
|
||||
entry.getRawFileEntry().getPath()),
|
||||
cmd);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "in " + AppPrefs.get().terminalType().getValue().toTranslatedString();
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
model.withShell(
|
||||
pc -> {
|
||||
for (FileBrowserEntry entry : entries) {
|
||||
var cmd = ScriptHelper.createDetachCommand(
|
||||
pc, createCommand(pc, model, entry));
|
||||
pc.command(cmd)
|
||||
.workingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
.execute();
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "in background";
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
model.withShell(
|
||||
pc -> {
|
||||
for (FileBrowserEntry entry : entries) {
|
||||
pc.command(createCommand(pc, model, entry))
|
||||
.workingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
.execute();
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "wait for completion";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
101
app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java
Normal file
101
app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public interface DirectoryType {
|
||||
|
||||
List<DirectoryType> ALL = new ArrayList<>();
|
||||
|
||||
static DirectoryType byId(String id) {
|
||||
return ALL.stream().filter(fileType -> fileType.getId().equals(id)).findAny().orElseThrow();
|
||||
}
|
||||
|
||||
public static void loadDefinitions() {
|
||||
ALL.add(new Simple(
|
||||
"default", new IconVariant("default_root_folder.svg"), new IconVariant("default_root_folder_opened.svg"), ""));
|
||||
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "folder_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
|
||||
var closedIcon = split[2].trim();
|
||||
var openIcon = split[3].trim();
|
||||
|
||||
var lightClosedIcon = split.length > 4 ? split[4].trim() : closedIcon;
|
||||
var lightOpenIcon = split.length > 4 ? split[5].trim() : openIcon;
|
||||
|
||||
ALL.add(new Simple(
|
||||
id, new IconVariant(lightClosedIcon, closedIcon),
|
||||
new IconVariant(lightOpenIcon, openIcon),
|
||||
filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Simple implements DirectoryType {
|
||||
|
||||
@Getter
|
||||
private final String id;
|
||||
private final IconVariant closed;
|
||||
private final IconVariant open;
|
||||
private final String[] names;
|
||||
|
||||
public Simple(String id, IconVariant closed, IconVariant open, String... names) {
|
||||
this.id = id;
|
||||
this.closed = closed;
|
||||
this.open = open;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FileSystem.FileEntry entry) {
|
||||
if (!entry.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.stream(names).anyMatch(name -> FileNames.getFileName(entry.getPath())
|
||||
.equalsIgnoreCase(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
return open ? this.open.getIcon() : this.closed.getIcon();
|
||||
}
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
boolean matches(FileSystem.FileEntry entry);
|
||||
|
||||
String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface FileIconFactory {
|
||||
|
||||
class SimpleFile extends IconVariant implements FileIconFactory {
|
||||
|
||||
private final String[] endings;
|
||||
|
||||
public SimpleFile(String lightIcon, String darkIcon, String... endings) {
|
||||
super(lightIcon, darkIcon);
|
||||
this.endings = endings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arrays.stream(endings).anyMatch(ending -> entry.getPath().toLowerCase().endsWith(ending.toLowerCase())) ? getIcon() : null;
|
||||
}
|
||||
}
|
||||
|
||||
String getIcon(FileSystem.FileEntry entry);
|
||||
}
|
|
@ -7,89 +7,14 @@ import io.xpipe.core.store.FileSystem;
|
|||
import javafx.scene.image.Image;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
|
||||
public class FileIconManager {
|
||||
|
||||
private static final List<FileIconFactory> factories = new ArrayList<>();
|
||||
private static final List<FolderIconFactory> folderFactories = new ArrayList<>();
|
||||
@Getter
|
||||
private static SvgCache svgCache = createCache();
|
||||
private static boolean loaded;
|
||||
|
||||
private static void loadDefinitions() {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "browser_icons/file_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
var darkIcon = split[2].trim();
|
||||
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
|
||||
factories.add(new FileIconFactory.SimpleFile(lightIcon, darkIcon, filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
folderFactories.addAll(List.of(new FolderIconFactory.SimpleDirectory(
|
||||
new IconVariant("default_root_folder.svg"), new IconVariant("default_root_folder_opened.svg"), "")));
|
||||
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "browser_icons/folder_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
|
||||
var closedIcon = split[2].trim();
|
||||
var openIcon = split[3].trim();
|
||||
|
||||
var lightClosedIcon = split.length > 4 ? split[4].trim() : closedIcon;
|
||||
var lightOpenIcon = split.length > 4 ? split[5].trim() : openIcon;
|
||||
|
||||
folderFactories.add(new FolderIconFactory.SimpleDirectory(
|
||||
new IconVariant(lightClosedIcon, closedIcon),
|
||||
new IconVariant(lightOpenIcon, openIcon),
|
||||
filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static SvgCache createCache() {
|
||||
return new SvgCache() {
|
||||
|
||||
|
@ -109,7 +34,6 @@ public class FileIconManager {
|
|||
|
||||
public static synchronized void loadIfNecessary() {
|
||||
if (!loaded) {
|
||||
loadDefinitions();
|
||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "browser_icons");
|
||||
loaded = true;
|
||||
}
|
||||
|
@ -123,17 +47,15 @@ public class FileIconManager {
|
|||
loadIfNecessary();
|
||||
|
||||
if (!entry.isDirectory()) {
|
||||
for (var f : factories) {
|
||||
var icon = f.getIcon(entry);
|
||||
if (icon != null) {
|
||||
return getIconPath(icon);
|
||||
for (var f : FileType.ALL) {
|
||||
if (f.matches(entry)) {
|
||||
return getIconPath(f.getIcon());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var f : folderFactories) {
|
||||
var icon = f.getIcon(entry, open);
|
||||
if (icon != null) {
|
||||
return getIconPath(icon);
|
||||
for (var f : DirectoryType.ALL) {
|
||||
if (f.matches(entry)) {
|
||||
return getIconPath(f.getIcon(entry, open));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
|
||||
public class FileIcons {
|
||||
|
||||
public static PrettyImageComp createIcon(FileType type) {
|
||||
return new PrettyImageComp(new SimpleStringProperty(type.getIcon()), 22, 22);
|
||||
}
|
||||
|
||||
public static PrettyImageComp createIcon(FileSystem.FileEntry entry) {
|
||||
return new PrettyImageComp(new SimpleStringProperty(FileIconManager.getFileIcon(entry, false)), 22, 22);
|
||||
}
|
||||
|
|
87
app/src/main/java/io/xpipe/app/browser/icon/FileType.java
Normal file
87
app/src/main/java/io/xpipe/app/browser/icon/FileType.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public interface FileType {
|
||||
|
||||
List<FileType> ALL = new ArrayList<>();
|
||||
|
||||
static FileType byId(String id) {
|
||||
return ALL.stream().filter(fileType -> fileType.getId().equals(id)).findAny().orElseThrow();
|
||||
}
|
||||
|
||||
public static void loadDefinitions() {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "file_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
var darkIcon = split[2].trim();
|
||||
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
|
||||
ALL.add(new FileType.Simple(id, lightIcon, darkIcon, filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Getter
|
||||
class Simple implements FileType {
|
||||
|
||||
private final String id;
|
||||
private final IconVariant icon;
|
||||
private final String[] endings;
|
||||
|
||||
public Simple(String id, String lightIcon, String darkIcon, String... endings) {
|
||||
this.icon = new IconVariant(lightIcon, darkIcon);
|
||||
this.id = id;
|
||||
this.endings = endings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FileSystem.FileEntry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.stream(endings)
|
||||
.anyMatch(ending -> entry.getPath().toLowerCase().endsWith(ending.toLowerCase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return icon.getIcon();
|
||||
}
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
boolean matches(FileSystem.FileEntry entry);
|
||||
|
||||
String getIcon();
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface FolderIconFactory {
|
||||
|
||||
class SimpleDirectory implements FolderIconFactory {
|
||||
|
||||
private final IconVariant closed;
|
||||
private final IconVariant open;
|
||||
private final String[] names;
|
||||
|
||||
public SimpleDirectory(IconVariant closed, IconVariant open, String... names) {
|
||||
this.closed = closed;
|
||||
this.open = open;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
if (!entry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arrays.stream(names).anyMatch(name -> FileNames.getFileName(entry.getPath())
|
||||
.equalsIgnoreCase(name))
|
||||
? (open ? this.open.getIcon() : this.closed.getIcon())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||
}
|
|
@ -78,7 +78,7 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
|
|||
|
||||
var pane = new BorderPane();
|
||||
var sidebar = new SideMenuBarComp(selected, entries);
|
||||
pane.setCenter(selected.getValue().comp().createRegion());
|
||||
pane.setCenter(map.get(selected.getValue()));
|
||||
pane.setRight(sidebar.createRegion());
|
||||
selected.addListener((c, o, n) -> {
|
||||
if (o != null && o.equals(entries.get(2))) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.app.comp.storage.collection;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.PopupMenuAugment;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import javafx.scene.control.Alert;
|
||||
|
@ -13,19 +13,14 @@ import javafx.scene.control.SeparatorMenuItem;
|
|||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class SourceCollectionContextMenu<S extends CompStructure<?>> extends PopupMenuAugment<S> {
|
||||
|
||||
private final SourceCollectionWrapper group;
|
||||
private final Region renameTextField;
|
||||
public class SourceCollectionContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
|
||||
|
||||
public SourceCollectionContextMenu(
|
||||
boolean showOnPrimaryButton, SourceCollectionWrapper group, Region renameTextField) {
|
||||
super(showOnPrimaryButton);
|
||||
this.group = group;
|
||||
this.renameTextField = renameTextField;
|
||||
super(showOnPrimaryButton, () -> createContextMenu(group, renameTextField));
|
||||
}
|
||||
|
||||
private void onDelete() {
|
||||
private static void onDelete(SourceCollectionWrapper group) {
|
||||
if (group.getEntries().size() > 0) {
|
||||
AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
|
||||
|
@ -44,7 +39,7 @@ public class SourceCollectionContextMenu<S extends CompStructure<?>> extends Pop
|
|||
}
|
||||
}
|
||||
|
||||
private void onClean() {
|
||||
private static void onClean(SourceCollectionWrapper group) {
|
||||
if (group.getEntries().size() > 0) {
|
||||
AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
|
||||
|
@ -63,8 +58,7 @@ public class SourceCollectionContextMenu<S extends CompStructure<?>> extends Pop
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextMenu createContextMenu() {
|
||||
protected static ContextMenu createContextMenu(SourceCollectionWrapper group, Region renameTextField) {
|
||||
var cm = new ContextMenu();
|
||||
var name = new MenuItem(group.getName());
|
||||
name.setDisable(true);
|
||||
|
@ -96,13 +90,13 @@ public class SourceCollectionContextMenu<S extends CompStructure<?>> extends Pop
|
|||
if (group.isDeleteable()) {
|
||||
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
|
||||
del.setOnAction(e -> {
|
||||
onDelete();
|
||||
onDelete(group);
|
||||
});
|
||||
cm.getItems().add(del);
|
||||
} else {
|
||||
var del = new MenuItem(AppI18n.get("clean"), new FontIcon("mdal-delete_outline"));
|
||||
del.setOnAction(e -> {
|
||||
onClean();
|
||||
onClean(group);
|
||||
});
|
||||
cm.getItems().add(del);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.app.comp.storage.source;
|
|||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.PopupMenuAugment;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataSourceEntry;
|
||||
|
@ -16,19 +16,14 @@ import javafx.scene.control.SeparatorMenuItem;
|
|||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class SourceEntryContextMenu<S extends CompStructure<?>> extends PopupMenuAugment<S> {
|
||||
public class SourceEntryContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
|
||||
|
||||
private final SourceEntryWrapper entry;
|
||||
private final Region renameTextField;
|
||||
|
||||
public SourceEntryContextMenu(boolean showOnPrimaryButton, SourceEntryWrapper entry, Region renameTextField) {
|
||||
super(showOnPrimaryButton);
|
||||
this.entry = entry;
|
||||
this.renameTextField = renameTextField;
|
||||
super(showOnPrimaryButton, () -> createContextMenu(entry, renameTextField));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextMenu createContextMenu() {
|
||||
protected static ContextMenu createContextMenu(SourceEntryWrapper entry, Region renameTextField) {
|
||||
var cm = new ContextMenu();
|
||||
AppFont.normal(cm.getStyleableNode());
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.augment.PopupMenuAugment;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.impl.*;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
|
@ -164,12 +164,7 @@ public class StoreEntryComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
new PopupMenuAugment<>(false) {
|
||||
@Override
|
||||
protected ContextMenu createContextMenu() {
|
||||
return StoreEntryComp.this.createContextMenu();
|
||||
}
|
||||
}.augment(new SimpleCompStructure<>(button));
|
||||
new ContextMenuAugment<>(false, () -> StoreEntryComp.this.createContextMenu()).augment(new SimpleCompStructure<>(button));
|
||||
|
||||
return button;
|
||||
}
|
||||
|
@ -218,12 +213,7 @@ public class StoreEntryComp extends SimpleComp {
|
|||
private Comp<?> createSettingsButton() {
|
||||
var settingsButton = new IconButtonComp("mdomz-settings");
|
||||
settingsButton.styleClass("settings");
|
||||
settingsButton.apply(new PopupMenuAugment<>(true) {
|
||||
@Override
|
||||
protected ContextMenu createContextMenu() {
|
||||
return StoreEntryComp.this.createContextMenu();
|
||||
}
|
||||
});
|
||||
settingsButton.apply(new ContextMenuAugment<>(true, () -> StoreEntryComp.this.createContextMenu()));
|
||||
settingsButton.apply(GrowAugment.create(false, true));
|
||||
settingsButton.apply(s -> {
|
||||
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package io.xpipe.app.fxcomps.augment;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||
|
||||
private final boolean showOnPrimaryButton;
|
||||
private final Supplier<ContextMenu> contextMenu;
|
||||
|
||||
public ContextMenuAugment(boolean showOnPrimaryButton, Supplier<ContextMenu> contextMenu) {
|
||||
this.showOnPrimaryButton = showOnPrimaryButton;
|
||||
this.contextMenu = contextMenu;
|
||||
}
|
||||
|
||||
private static ContextMenu currentContextMenu;
|
||||
|
||||
@Override
|
||||
public void augment(S struc) {
|
||||
var r = struc.get();
|
||||
r.setOnMousePressed(event -> {
|
||||
if ((showOnPrimaryButton && event.getButton() == MouseButton.PRIMARY)
|
||||
|| (!showOnPrimaryButton && event.getButton() == MouseButton.SECONDARY)) {
|
||||
if (currentContextMenu != null && currentContextMenu.isShowing()) {
|
||||
currentContextMenu.hide();
|
||||
currentContextMenu = null;
|
||||
}
|
||||
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
var cm = contextMenu.get();
|
||||
if (cm != null) {
|
||||
cm.setAutoHide(true);
|
||||
cm.show(r, event.getScreenX(), event.getScreenY());
|
||||
currentContextMenu = cm;
|
||||
}
|
||||
}
|
||||
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package io.xpipe.app.fxcomps.augment;
|
||||
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
|
||||
public abstract class PopupMenuAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||
|
||||
private final boolean showOnPrimaryButton;
|
||||
|
||||
protected PopupMenuAugment(boolean showOnPrimaryButton) {
|
||||
this.showOnPrimaryButton = showOnPrimaryButton;
|
||||
}
|
||||
|
||||
protected abstract ContextMenu createContextMenu();
|
||||
|
||||
@Override
|
||||
public void augment(S struc) {
|
||||
var cm = createContextMenu();
|
||||
var r = struc.get();
|
||||
r.setOnMousePressed(event -> {
|
||||
if ((showOnPrimaryButton && event.getButton() == MouseButton.PRIMARY)
|
||||
|| (!showOnPrimaryButton && event.getButton() == MouseButton.SECONDARY)) {
|
||||
cm.show(r, event.getScreenX(), event.getScreenY());
|
||||
event.consume();
|
||||
} else {
|
||||
cm.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import javafx.scene.layout.Region;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -21,7 +22,6 @@ public class Shortcuts {
|
|||
}
|
||||
|
||||
public static <T extends Region> void addShortcut(T region, KeyCombination comb, Consumer<T> exec) {
|
||||
AtomicReference<Scene> scene = new AtomicReference<>(region.getScene());
|
||||
var filter = new EventHandler<KeyEvent>() {
|
||||
public void handle(KeyEvent ke) {
|
||||
if (comb.match(ke)) {
|
||||
|
@ -30,21 +30,23 @@ public class Shortcuts {
|
|||
}
|
||||
}
|
||||
};
|
||||
SHORTCUTS.put(region, comb);
|
||||
|
||||
AtomicReference<Scene> scene = new AtomicReference<>();
|
||||
SimpleChangeListener.apply(region.sceneProperty(), s -> {
|
||||
if (Objects.equals(s, scene.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene.get() != null) {
|
||||
scene.get().removeEventHandler(KeyEvent.KEY_PRESSED, filter);
|
||||
SHORTCUTS.remove(region);
|
||||
scene.set(null);
|
||||
}
|
||||
|
||||
if (s != null) {
|
||||
scene.set(s);
|
||||
s.addEventHandler(KeyEvent.KEY_PRESSED, filter);
|
||||
SHORTCUTS.put(region, comb);
|
||||
} else {
|
||||
if (scene.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scene.get().removeEventHandler(KeyEvent.KEY_PRESSED, filter);
|
||||
SHORTCUTS.remove(region);
|
||||
scene.set(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,6 +23,11 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
public static final ExternalEditorType VSCODE_WINDOWS = new WindowsFullPathType("app.vscode") {
|
||||
|
||||
@Override
|
||||
public boolean canOpenDirectory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determinePath() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
|
@ -48,7 +53,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
public static final LinuxPathType VSCODE_LINUX = new LinuxPathType("app.vscode", "code");
|
||||
public static final LinuxPathType VSCODE_LINUX = new LinuxPathType("app.vscode", "code") {
|
||||
|
||||
@Override
|
||||
public boolean canOpenDirectory() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public static final LinuxPathType KATE = new LinuxPathType("app.kate", "kate");
|
||||
|
||||
|
@ -81,7 +92,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
public static final ExternalEditorType SUBLIME_MACOS = new MacOsEditor("app.sublime", "Sublime Text");
|
||||
|
||||
public static final ExternalEditorType VSCODE_MACOS = new MacOsEditor("app.vscode", "Visual Studio Code");
|
||||
public static final ExternalEditorType VSCODE_MACOS = new MacOsEditor("app.vscode", "Visual Studio Code") {
|
||||
|
||||
@Override
|
||||
public boolean canOpenDirectory() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public static final ExternalEditorType CUSTOM = new ExternalEditorType() {
|
||||
|
||||
|
@ -110,6 +127,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
public void launch(Path file) throws Exception;
|
||||
|
||||
default boolean canOpenDirectory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class LinuxPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
|
||||
|
||||
public LinuxPathType(String id, String command) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import java.util.stream.Stream;
|
|||
|
||||
public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
|
||||
public static final ExternalTerminalType CMD = new SimpleType("cmd", "cmd.exe", "cmd.exe") {
|
||||
public static final ExternalTerminalType CMD = new SimpleType("app.cmd", "cmd.exe", "cmd.exe") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -31,7 +31,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
};
|
||||
|
||||
public static final ExternalTerminalType POWERSHELL_WINDOWS =
|
||||
new SimpleType("powershell", "powershell", "PowerShell") {
|
||||
new SimpleType("app.powershell", "powershell", "PowerShell") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -44,7 +44,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
public static final ExternalTerminalType PWSH_WINDOWS = new SimpleType("pwsh", "pwsh", "PowerShell Core") {
|
||||
public static final ExternalTerminalType PWSH_WINDOWS = new SimpleType("app.pwsh", "pwsh", "PowerShell Core") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -60,7 +60,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
};
|
||||
|
||||
public static final ExternalTerminalType WINDOWS_TERMINAL =
|
||||
new SimpleType("windowsTerminal", "wt.exe", "Windows Terminal") {
|
||||
new SimpleType("app.windowsTerminal", "wt.exe", "Windows Terminal") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -78,7 +78,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
};
|
||||
|
||||
public static final ExternalTerminalType GNOME_TERMINAL =
|
||||
new SimpleType("gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
|
||||
new SimpleType("app.gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
|
||||
|
||||
@Override
|
||||
public void launch(String name, String file, boolean elevated) throws Exception {
|
||||
|
@ -105,7 +105,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
public static final ExternalTerminalType KONSOLE = new SimpleType("konsole", "konsole", "Konsole") {
|
||||
public static final ExternalTerminalType KONSOLE = new SimpleType("app.konsole", "konsole", "Konsole") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -120,7 +120,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
public static final ExternalTerminalType XFCE = new SimpleType("xfce", "xfce4-terminal", "Xfce") {
|
||||
public static final ExternalTerminalType XFCE = new SimpleType("app.xfce", "xfce4-terminal", "Xfce") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
|
@ -169,7 +169,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
static class MacOsTerminalType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
|
||||
|
||||
public MacOsTerminalType() {
|
||||
super("macosTerminal", "Terminal");
|
||||
super("app.macosTerminal", "Terminal");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -193,7 +193,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
static class CustomType extends ExternalApplicationType implements ExternalTerminalType {
|
||||
|
||||
public CustomType() {
|
||||
super("custom");
|
||||
super("app.custom");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -229,7 +229,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
static class ITerm2Type extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
|
||||
|
||||
public ITerm2Type() {
|
||||
super("iterm2", "iTerm");
|
||||
super("app.iterm2", "iTerm");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -263,7 +263,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
static class WarpType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
|
||||
|
||||
public WarpType() {
|
||||
super("warp", "Warp");
|
||||
super("app.warp", "Warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,7 +18,7 @@ public class ScriptHelper {
|
|||
|
||||
public static String createDetachCommand(ShellControl pc, String command) {
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
return "start \"\" " + command;
|
||||
return "start \"\" /MIN " + command;
|
||||
} else {
|
||||
return "nohup " + command + " </dev/null &>/dev/null & disown";
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.core.AppLogs;
|
||||
import io.xpipe.app.exchange.*;
|
||||
import io.xpipe.app.exchange.api.*;
|
||||
|
@ -33,6 +34,7 @@ open module io.xpipe.app {
|
|||
exports io.xpipe.app.fxcomps.util;
|
||||
exports io.xpipe.app.fxcomps.augment;
|
||||
exports io.xpipe.app.test;
|
||||
exports io.xpipe.app.browser.action;
|
||||
exports io.xpipe.app.browser;
|
||||
exports io.xpipe.app.browser.icon;
|
||||
|
||||
|
@ -119,11 +121,13 @@ open module io.xpipe.app {
|
|||
uses ProxyFunction;
|
||||
uses ModuleLayerLoader;
|
||||
uses ScanProvider;
|
||||
uses BrowserAction;
|
||||
|
||||
provides ModuleLayerLoader with
|
||||
DataSourceTarget.Loader,
|
||||
ActionProvider.Loader,
|
||||
PrefsProvider.Loader,
|
||||
BrowserAction.Loader,
|
||||
ScanProvider.Loader;
|
||||
provides DataStateProvider with
|
||||
DataStateProviderImpl;
|
||||
|
|
|
@ -66,6 +66,35 @@
|
|||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.browser .context-menu > * > * {
|
||||
-fx-padding: 3px 10px 3px 10px;
|
||||
-fx-background-radius: 1px;
|
||||
-fx-spacing: 20px;
|
||||
}
|
||||
|
||||
.browser .context-menu .separator {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.browser .context-menu .separator .line {
|
||||
-fx-padding: 0;
|
||||
-fx-border-insets: 0px;
|
||||
}
|
||||
|
||||
.browser .context-menu .accelerator-text {
|
||||
-fx-padding: 3px 0px 3px 50px;
|
||||
}
|
||||
|
||||
.browser .context-menu > * {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.browser .context-menu {
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 1px;
|
||||
-fx-border-color: -color-neutral-muted;
|
||||
}
|
||||
|
||||
.chooser-bar {
|
||||
-fx-border-color: -color-neutral-emphasis;
|
||||
-fx-border-width: 0.1em 0 0 0;
|
||||
|
|
|
@ -5,6 +5,10 @@ import java.util.List;
|
|||
|
||||
public class FileNames {
|
||||
|
||||
public static String quoteIfNecessary(String n) {
|
||||
return n.contains(" ") ? "\"" + n + "\"" : n;
|
||||
}
|
||||
|
||||
public static String toDirectory(String path) {
|
||||
if (path.endsWith("/") || path.endsWith("\\")) {
|
||||
return path;
|
||||
|
@ -45,6 +49,19 @@ public class FileNames {
|
|||
return components.get(components.size() - 1);
|
||||
}
|
||||
|
||||
public static String getBaseName(String file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = FileNames.getFileName(file);
|
||||
var split = file.lastIndexOf("\\.");
|
||||
if (split == -1) {
|
||||
return name;
|
||||
}
|
||||
return name.substring(0, split);
|
||||
}
|
||||
|
||||
public static String getExtension(String file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
|
|
|
@ -4,7 +4,7 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface OsType {
|
||||
public sealed interface OsType permits OsType.Windows, OsType.Linux, OsType.MacOs {
|
||||
|
||||
Windows WINDOWS = new Windows();
|
||||
Linux LINUX = new Linux();
|
||||
|
@ -33,7 +33,7 @@ public interface OsType {
|
|||
|
||||
String determineOperatingSystemName(ShellControl pc) throws Exception;
|
||||
|
||||
static class Windows implements OsType {
|
||||
static final class Windows implements OsType {
|
||||
|
||||
@Override
|
||||
public String getHomeDirectory(ShellControl pc) throws Exception {
|
||||
|
@ -80,7 +80,7 @@ public interface OsType {
|
|||
}
|
||||
}
|
||||
|
||||
static class Linux implements OsType {
|
||||
static final class Linux implements OsType {
|
||||
|
||||
@Override
|
||||
public String getHomeDirectory(ShellControl pc) throws Exception {
|
||||
|
@ -138,7 +138,7 @@ public interface OsType {
|
|||
}
|
||||
}
|
||||
|
||||
static class MacOs implements OsType {
|
||||
static final class MacOs implements OsType {
|
||||
|
||||
@Override
|
||||
public String getHomeDirectory(ShellControl pc) throws Exception {
|
||||
|
|
|
@ -8,6 +8,12 @@ apply from: "$rootDir/gradle/gradle_scripts/commons.gradle"
|
|||
apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle"
|
||||
apply from: "$rootDir/gradle/gradle_scripts/extension.gradle"
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||
compileOnly 'net.java.dev.jna:jna-jpms:5.12.1'
|
||||
compileOnly 'net.java.dev.jna:jna-platform-jpms:5.12.1'
|
||||
}
|
||||
|
||||
compileJava {
|
||||
doFirst {
|
||||
options.compilerArgs += [
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserClipboard;
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CopyAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
FileBrowserClipboard.startCopy(
|
||||
model.getCurrentDirectory(), entries.stream().map(entry -> entry.getRawFileEntry()).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2c-content-copy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.COPY_PASTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Copy";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.BranchAction;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CopyPathAction implements BrowserAction, BranchAction {
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Copy location";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.COPY_PASTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeafAction> getBranchingActions() {
|
||||
return List.of(
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Absolute Path";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var s = entries.stream()
|
||||
.map(entry -> entry.getRawFileEntry().getPath())
|
||||
.collect(Collectors.joining("\n"));
|
||||
var selection = new StringSelection(s);
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Absolute Path (Quoted)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().anyMatch(entry -> entry.getRawFileEntry().getPath().contains(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var s = entries.stream()
|
||||
.map(entry -> "\"" + entry.getRawFileEntry().getPath() + "\"")
|
||||
.collect(Collectors.joining("\n"));
|
||||
var selection = new StringSelection(s);
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "File Name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var s = entries.stream()
|
||||
.map(entry ->
|
||||
FileNames.getFileName(entry.getRawFileEntry().getPath()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
var selection = new StringSelection(s);
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "File Name (Quoted)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().anyMatch(entry -> FileNames.getFileName(entry.getRawFileEntry().getPath()).contains(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var s = entries.stream()
|
||||
.map(entry ->
|
||||
"\"" + FileNames.getFileName(entry.getRawFileEntry().getPath()) + "\"")
|
||||
.collect(Collectors.joining("\n"));
|
||||
var selection = new StringSelection(s);
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserAlerts;
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.FileSystemHelper;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeleteAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var toDelete = entries.stream().map(entry -> entry.getRawFileEntry()).toList();
|
||||
if (!FileBrowserAlerts.showDeleteAlert(toDelete)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileSystemHelper.delete(toDelete);
|
||||
model.refreshSync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.MUTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2d-delete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.DELETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Delete";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import javafx.scene.Node;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditFileAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
for (FileBrowserEntry entry : entries) {
|
||||
FileOpener.openInTextEditor(entry.getRawFileEntry());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2p-pencil");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().noneMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Edit with " + AppPrefs.get().externalEditor().getValue().toTranslatedString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.icon.FileIcons;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import javafx.scene.Node;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface FileTypeAction extends BrowserAction {
|
||||
|
||||
@Override
|
||||
default boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
var t = getType();
|
||||
return entries.stream().allMatch(entry -> t.matches(entry.getRawFileEntry()));
|
||||
}
|
||||
|
||||
@Override
|
||||
default Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return FileIcons.createIcon(getType()).createRegion();
|
||||
}
|
||||
|
||||
FileType getType();
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JarAction extends JavaAction implements FileTypeAction {
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.CUSTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return super.isApplicable(model, entries) && FileTypeAction.super.isApplicable(model, entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, FileBrowserEntry entry) {
|
||||
return entry.getFileName().endsWith(".jar");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createCommand(ShellControl sc, OpenFileSystemModel model, FileBrowserEntry entry) {
|
||||
return "java -jar " + entry.getOptionallyQuotedFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "java -jar " + filesArgument(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileType getType() {
|
||||
return FileType.byId("jar");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.ApplicationPathAction;
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class JavaAction extends MultiExecuteAction implements ApplicationPathAction {
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Java";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "java";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.BranchAction;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NewItemAction implements BrowserAction, BranchAction {
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2p-plus-box-outline");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Create new";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsEmptySelection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.size() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.MUTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeafAction> getBranchingActions() {
|
||||
return List.of(
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "directory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenDirectoryAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
model.cd(entries.get(0).getRawFileEntry().getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2f-folder-open");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.ENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Open";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.FileBrowserModel;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenDirectoryInNewTabAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
model.getBrowserModel().openFileSystemSync(model.getStore().getValue().asNeeded(), entries.get(0).getRawFileEntry().getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2f-folder-open-outline");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory()) && model.getBrowserModel().getMode() == FileBrowserModel.Mode.BROWSER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.ENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Open in new tab";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenFileDefaultAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
for (var entry : entries) {
|
||||
FileOpener.openInDefaultApplication(entry.getRawFileEntry());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2b-book-open-variant");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().noneMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.ENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Open";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import com.sun.jna.platform.win32.Shell32;
|
||||
import com.sun.jna.platform.win32.WinUser;
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenFileWithAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> {
|
||||
Shell32.INSTANCE.ShellExecute(
|
||||
null,
|
||||
"open",
|
||||
"rundll32.exe",
|
||||
"shell32.dll,OpenAs_RunDLL " + entries.get(0).getRawFileEntry().getPath(),
|
||||
null,
|
||||
WinUser.SW_SHOWNORMAL);
|
||||
}
|
||||
case OsType.Linux linux -> {
|
||||
}
|
||||
case OsType.MacOs macOs -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2b-book-open-page-variant-outline");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.size() == 1 && entries.stream().noneMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.ENTER, KeyCombination.SHIFT_DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Open with ...";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpenTerminalAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
for (var entry : entries) {
|
||||
model.openTerminalAsync(entry.getRawFileEntry().getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2c-console");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Open in " + AppPrefs.get().terminalType().getValue().toTranslatedString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserClipboard;
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PasteAction implements LeafAction {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<FileBrowserEntry> entries) throws Exception {
|
||||
var clipboard = FileBrowserClipboard.retrieveCopy();
|
||||
if (clipboard == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var target = entries.size() == 1 && entries.get(0).getRawFileEntry().isDirectory() ? entries.get(0).getRawFileEntry() : model.getCurrentDirectory();
|
||||
var files = clipboard.getEntries();
|
||||
model.dropFilesIntoAsync(target, files, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.COPY_PASTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2c-content-paste");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.size() < 2 && entries.stream().allMatch(entry -> entry.getRawFileEntry().isDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return FileBrowserClipboard.retrieveCopy() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCombination getShortcut() {
|
||||
return new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsEmptySelection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Paste";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.scene.Node;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RunAction extends MultiExecuteAction {
|
||||
|
||||
private boolean isExecutable(FileSystem.FileEntry e) {
|
||||
if (e.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.getExecutable() != null && e.getExecutable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var shell = e.getFileSystem().getShell();
|
||||
if (shell.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var os = shell.get().getOsType();
|
||||
if (os.equals(OsType.WINDOWS) && List.of("exe", "bat", "ps1", "cmd").stream().anyMatch(s -> e.getPath().endsWith(s))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (List.of("sh", "command").stream().anyMatch(s -> e.getPath().endsWith(s))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.CUSTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getIcon(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return new FontIcon("mdi2p-play");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "Run";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return entries.stream().allMatch(entry -> isExecutable(entry.getRawFileEntry()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createCommand(ShellControl sc, OpenFileSystemModel model, FileBrowserEntry entry) {
|
||||
return sc.getShellDialect().runScript(entry.getFileName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.FileBrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.ExecuteApplicationAction;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UnzipAction extends ExecuteApplicationAction {
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "unzip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(OpenFileSystemModel model, FileBrowserEntry entry) {
|
||||
return entry.getRawFileEntry().getPath().endsWith(".zip") && !OsType.getLocal().equals(OsType.WINDOWS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createCommand(OpenFileSystemModel model, FileBrowserEntry entry) {
|
||||
return "unzip -o " + entry.getOptionallyQuotedFileName() + " -d " + FileNames.quoteIfNecessary(FileNames.getBaseName(entry.getFileName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.CUSTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<FileBrowserEntry> entries) {
|
||||
return "unzip [...]";
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.ext.DataSourceProvider;
|
||||
import io.xpipe.app.ext.DataSourceTarget;
|
||||
|
@ -5,6 +6,7 @@ import io.xpipe.app.ext.DataStoreProvider;
|
|||
import io.xpipe.ext.base.*;
|
||||
import io.xpipe.ext.base.actions.*;
|
||||
import io.xpipe.ext.base.apps.*;
|
||||
import io.xpipe.ext.base.browser.*;
|
||||
|
||||
open module io.xpipe.ext.base {
|
||||
exports io.xpipe.ext.base;
|
||||
|
@ -20,7 +22,25 @@ open module io.xpipe.ext.base {
|
|||
requires static net.synedra.validatorfx;
|
||||
requires static io.xpipe.app;
|
||||
requires org.apache.commons.lang3;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires com.sun.jna;
|
||||
requires com.sun.jna.platform;
|
||||
|
||||
provides BrowserAction with
|
||||
OpenFileDefaultAction,
|
||||
OpenFileWithAction,
|
||||
OpenDirectoryAction,
|
||||
OpenDirectoryInNewTabAction,
|
||||
OpenTerminalAction,
|
||||
EditFileAction,
|
||||
RunAction,
|
||||
CopyAction,
|
||||
CopyPathAction,
|
||||
PasteAction,
|
||||
NewItemAction,
|
||||
DeleteAction,
|
||||
UnzipAction,
|
||||
JarAction;
|
||||
provides ActionProvider with
|
||||
DeleteStoreChildrenAction,
|
||||
AddStoreAction,
|
||||
|
|
Loading…
Reference in a new issue