Small fixes and improvements [release]

This commit is contained in:
crschnick 2023-03-22 11:42:47 +00:00
parent b881e548fc
commit 71e9539efd
16 changed files with 177 additions and 44 deletions

View file

@ -0,0 +1,51 @@
package io.xpipe.app.browser;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.core.store.FileSystem;
import javafx.scene.control.Alert;
import java.util.List;
import java.util.stream.Collectors;
public class FileBrowserAlerts {
public static boolean showMoveAlert(List<FileSystem.FileEntry> source, FileSystem.FileEntry target) {
if (source.stream().noneMatch(entry -> entry.isDirectory())) {
return true;
}
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("moveAlertTitle"));
alert.setHeaderText(AppI18n.get("moveAlertHeader", source.size(), target.getPath()));
alert.getDialogPane().setContent(AppWindowHelper.alertContentText(getSelectedElementsString(source)));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.map(b -> b.getButtonData().isDefaultButton())
.orElse(false);
}
public static boolean showDeleteAlert(List<FileSystem.FileEntry> source) {
if (source.stream().noneMatch(entry -> entry.isDirectory())) {
return true;
}
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("deleteAlertTitle"));
alert.setHeaderText(AppI18n.get("deleteAlertHeader", source.size()));
alert.getDialogPane().setContent(AppWindowHelper.alertContentText(getSelectedElementsString(source)));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.map(b -> b.getButtonData().isDefaultButton())
.orElse(false);
}
private static String getSelectedElementsString(List<FileSystem.FileEntry> source) {
var namesHeader = AppI18n.get("selectedElements");
var names = namesHeader + "\n" + source.stream().limit(10).map(entry -> "- " + entry.getPath()).collect(Collectors.joining("\n"));
if (source.size() > 10) {
names += "\n+ " + (source.size() - 10) + " ...";
}
return names;
}
}

View file

@ -147,8 +147,8 @@ final class FileContextMenu extends ContextMenu {
var delete = new MenuItem("Delete");
delete.setOnAction(event -> {
model.deleteSelectionAsync();
event.consume();
model.deleteAsync(entry.getPath());
});
var rename = new MenuItem("Rename");

View file

@ -23,6 +23,7 @@ public class FileListCompEntry {
private Point2D lastOver = new Point2D(-1, -1);
private TimerTask activeTask;
private FileContextMenu currentContextMenu;
public FileListCompEntry(Node row, FileSystem.FileEntry item, FileListModel model) {
this.row = row;
@ -36,9 +37,15 @@ public class FileListCompEntry {
return;
}
var cm = new FileContextMenu(model.getFileSystemModel(), item, model.getEditing());
if (currentContextMenu != null) {
currentContextMenu.hide();
currentContextMenu = null;
}
if (t.getButton() == MouseButton.SECONDARY) {
var cm = new FileContextMenu(model.getFileSystemModel(), item, model.getEditing());
cm.show(row, t.getScreenX(), t.getScreenY());
currentContextMenu = cm;
}
}

View file

@ -24,15 +24,17 @@ public class FileSystemHelper {
var current = !(model.getStore().getValue() instanceof LocalStore)
? fileSystem
.getShellControl()
.executeStringSimpleCommand(fileSystem
.getShellControl()
.getShellDialect()
.getPrintWorkingDirectoryCommand())
: fileSystem.getShell().get().getOsType().getHomeDirectory(fileSystem.getShell().get());
return FileSystemHelper.normalizeDirectoryPath(model, current);
.executeStringSimpleCommand(
fileSystem.getShellControl().getShellDialect().getPrintWorkingDirectoryCommand())
: fileSystem
.getShell()
.get()
.getOsType()
.getHomeDirectory(fileSystem.getShell().get());
return FileSystemHelper.resolveDirectoryPath(model, current);
}
public static String normalizeDirectoryPath(OpenFileSystemModel model, String path) throws Exception {
public static String resolveDirectoryPath(OpenFileSystemModel model, String path) throws Exception {
if (path == null) {
return null;
}
@ -65,6 +67,8 @@ public class FileSystemHelper {
throw new IllegalArgumentException(String.format("Directory %s does not exist", normalized));
}
model.getFileSystem().directoryAccessible(normalized);
return FileNames.toDirectory(normalized);
}
@ -96,6 +100,20 @@ public class FileSystemHelper {
}
}
public static void delete(List<FileSystem.FileEntry> files) throws Exception {
if (files.size() == 0) {
return;
}
for (var file : files) {
try {
file.getFileSystem().delete(file.getPath());
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).handle();
}
}
}
public static void dropFilesInto(
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) throws Exception {
if (files.size() == 0) {

View file

@ -2,7 +2,9 @@ package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.impl.FileNames;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -52,11 +54,13 @@ public class OpenFileSystemComp extends SimpleComp {
var terminalBtn = new Button(null, new FontIcon("mdi2c-code-greater-than"));
terminalBtn.setOnAction(e -> model.openTerminalAsync(model.getCurrentPath().get()));
terminalBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory()));
var addBtn = new Button(null, new FontIcon("mdmz-plus"));
addBtn.setOnAction(e -> {
creatingProperty.set(true);
});
addBtn.disableProperty().bind(PlatformThread.sync(model.getNoDirectory()));
var filter = new FileFilterComp(model.getFilter()).createRegion();
@ -85,14 +89,14 @@ public class OpenFileSystemComp extends SimpleComp {
pane.getChildren().add(root);
var creation = createCreationWindow(creatingProperty);
var creationPain = new StackPane(creation);
creationPain.setAlignment(Pos.CENTER);
creationPain.setOnMouseClicked(event -> {
var creationPane = new StackPane(creation);
creationPane.setAlignment(Pos.CENTER);
creationPane.setOnMouseClicked(event -> {
creatingProperty.set(false);
});
pane.getChildren().add(creationPain);
creationPain.visibleProperty().bind(creatingProperty);
creationPain.managedProperty().bind(creatingProperty);
pane.getChildren().add(creationPane);
creationPane.visibleProperty().bind(creatingProperty);
creationPane.managedProperty().bind(creatingProperty);
return pane;
}
@ -104,25 +108,32 @@ public class OpenFileSystemComp extends SimpleComp {
creationName.setText("");
}
});
var createFileButton = new Button("Create file");
var createFileButton = new Button("File", new PrettyImageComp(new SimpleStringProperty("file_drag_icon.png"), 20, 20).createRegion());
createFileButton.setOnAction(event -> {
model.createFileAsync(FileNames.join(model.getCurrentPath().get(), creationName.getText()));
model.createFileAsync(creationName.getText());
creating.set(false);
});
var createDirectoryButton = new Button("Create directory");
var createDirectoryButton = new Button("Directory", new PrettyImageComp(new SimpleStringProperty("folder_closed.svg"), 20, 20).createRegion());
createDirectoryButton.setOnAction(event -> {
model.createDirectoryAsync(FileNames.join(model.getCurrentPath().get(), creationName.getText()));
model.createDirectoryAsync(creationName.getText());
creating.set(false);
});
var buttonBar = new ButtonBar();
buttonBar.getButtons().addAll(createFileButton, createDirectoryButton);
var creationContent = new VBox(creationName, buttonBar);
creationContent.setSpacing(15);
var creation = new TitledPane("New", creationContent);
var creation = new TitledPane("New ...", creationContent);
creation.setMaxWidth(400);
creation.setCollapsible(false);
creationContent.setPadding(new Insets(15));
creation.getStyleClass().add("elevated-3");
creating.addListener((observable, oldValue, newValue) -> {
if (newValue) {
creationName.requestFocus();
}
});
return creation;
}
}

View file

@ -35,6 +35,7 @@ final class OpenFileSystemModel {
private final FileBrowserNavigationHistory history = new FileBrowserNavigationHistory();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final FileBrowserModel browserModel;
private final BooleanProperty noDirectory = new SimpleBooleanProperty();
public OpenFileSystemModel(FileBrowserModel browserModel) {
this.browserModel = browserModel;
@ -77,7 +78,7 @@ final class OpenFileSystemModel {
public Optional<String> cd(String path) {
String newPath = null;
try {
newPath = FileSystemHelper.normalizeDirectoryPath(this, path);
newPath = FileSystemHelper.resolveDirectoryPath(this, path);
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
return Optional.of(currentPath.get());
@ -116,10 +117,12 @@ final class OpenFileSystemModel {
List<FileSystem.FileEntry> newList;
if (dir != null) {
newList = getFileSystem().listFiles(dir).collect(Collectors.toCollection(ArrayList::new));
noDirectory.set(false);
} else {
newList = getFileSystem().listRoots().stream()
.map(s -> new FileSystem.FileEntry(getFileSystem(), s, Instant.now(), true, false, false, 0))
.collect(Collectors.toCollection(ArrayList::new));
noDirectory.set(true);
}
fileList.setAll(newList);
return true;
@ -151,14 +154,25 @@ final class OpenFileSystemModel {
return;
}
var same = files.get(0).getFileSystem().equals(target.getFileSystem());
if (same) {
if (!FileBrowserAlerts.showMoveAlert(files, target)) {
return;
}
}
FileSystemHelper.dropFilesInto(target, files, explicitCopy);
refreshInternal();
});
});
}
public void createDirectoryAsync(String path) {
if (path.isBlank()) {
public void createDirectoryAsync(String name) {
if (name.isBlank()) {
return;
}
if (getCurrentDirectory() == null) {
return;
}
@ -168,14 +182,23 @@ final class OpenFileSystemModel {
return;
}
fileSystem.mkdirs(path);
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
if (fileSystem.directoryExists(abs)) {
throw new IllegalStateException(String.format("Directory %s already exists", abs));
}
fileSystem.mkdirs(abs);
refreshInternal();
});
});
}
public void createFileAsync(String path) {
if (path.isBlank()) {
public void createFileAsync(String name) {
if (name.isBlank()) {
return;
}
if (getCurrentDirectory() == null) {
return;
}
@ -185,20 +208,25 @@ final class OpenFileSystemModel {
return;
}
fileSystem.touch(path);
var abs = FileNames.join(getCurrentDirectory().getPath(), name);
fileSystem.touch(abs);
refreshInternal();
});
});
}
public void deleteAsync(String path) {
public void deleteSelectionAsync() {
ThreadHelper.runFailableAsync(() -> {
BusyProperty.execute(busy, () -> {
if (fileSystem == null) {
return;
}
fileSystem.delete(path);
if (!FileBrowserAlerts.showDeleteAlert(fileList.getSelected())) {
return;
}
FileSystemHelper.delete(fileList.getSelected());
refreshInternal();
});
});
@ -249,7 +277,7 @@ final class OpenFileSystemModel {
var command = s.create()
.initWith(List.of(connection.getShellDialect().getCdCommand(directory)))
.prepareTerminalOpen();
TerminalHelper.open("", command);
TerminalHelper.open(directory, command);
}
});
});

View file

@ -48,7 +48,7 @@ public class StoreCreationBarComp extends SimpleComp {
.shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addDatabase"));
var box = new VerticalComp(List.of(newShellStore, newDbStore, newStreamStore, newOtherStore));
var box = new VerticalComp(List.of(newShellStore, newDbStore, newStreamStore));
box.apply(s -> AppFont.medium(s.get()));
var bar = box.createRegion();
bar.getStyleClass().add("bar");

View file

@ -7,6 +7,7 @@ import io.xpipe.app.util.ThreadHelper;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
@ -35,7 +36,9 @@ public class AppWindowHelper {
var text = new Text(s);
text.setWrappingWidth(450);
AppFont.medium(text);
return new StackPane(text);
var sp = new StackPane(text);
sp.setPadding(new Insets(5));
return sp;
}
public static Stage sideWindow(

View file

@ -1,6 +1,7 @@
package io.xpipe.app.issue;
import io.xpipe.app.core.AppI18n;
import io.xpipe.core.process.ProcessOutputException;
import java.io.FileNotFoundException;
@ -14,6 +15,7 @@ public class ExceptionConverter {
}
return switch (ex) {
case ProcessOutputException e -> e.getOutput();
case StackOverflowError e -> AppI18n.get("app.stackOverflow");
case OutOfMemoryError e -> AppI18n.get("app.outOfMemory");
case FileNotFoundException e -> AppI18n.get("app.fileNotFound", msg);

View file

@ -4,6 +4,7 @@ import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.MacOsPermissions;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
@ -45,7 +46,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
protected String toCommand(String name, String command) {
return "-w 1 nt --title \"" + name + "\" " + command;
// A weird behavior in Windows Terminal causes the trailing
// backslash of a filepath to escape the closing quote in the title argument
// So just remove that slash
var fixedName = FileNames.removeTrailingSlash(name);
return "-w 1 nt --title \"" + fixedName + "\" " + command;
}
@Override

View file

@ -11,7 +11,7 @@ public class TerminalHelper {
}
public static void open(String title, String command) throws Exception {
if (command.contains("\n") || !command.strip().equals(command)) {
if (command.contains("\n") || command.contains(" ")) {
command = ScriptHelper.createLocalExecScript(command);
}

View file

@ -7,6 +7,11 @@ common=Common
other=Other
askpassAlertTitle=Askpass
nullPointer=Null Pointer
moveAlertTitle=Confirm move
moveAlertHeader=Do you want to move the ($COUNT$) selected elements into $TARGET$?
deleteAlertTitle=Confirm deletion
deleteAlertHeader=Do you want to delete the ($COUNT$) selected elements?
selectedElements=Selected elements:
mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$

View file

@ -10,7 +10,6 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
@ -77,10 +76,7 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override
public OutputStream openOutput() throws Exception {
if (!fileSystem.createFileSystem().open().mkdirs(getParent())) {
throw new IOException("Unable to create directory: " + getParent());
}
fileSystem.createFileSystem().open().mkdirs(getParent());
return fileSystem.createFileSystem().open().openOutput(path);
}

View file

@ -35,6 +35,11 @@ public class ConnectionFileSystem implements FileSystem {
return shellControl.getShellDialect().directoryExists(shellControl, file).executeAndCheck();
}
@Override
public void directoryAccessible(String file) throws Exception {
shellControl.executeSimpleCommand(shellControl.getShellDialect().getCdCommand(file));
}
@Override
public Stream<FileEntry> listFiles(String file) throws Exception {
return shellControl.getShellDialect().listFiles(this, shellControl, file);
@ -107,11 +112,11 @@ public class ConnectionFileSystem implements FileSystem {
}
@Override
public boolean mkdirs(String file) throws Exception {
public void mkdirs(String file) throws Exception {
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getMkdirsCommand(file)).complex()
.start()) {
return pc.discardAndCheckExit();
pc.discardOrThrow();
}
}

View file

@ -59,12 +59,14 @@ public interface FileSystem extends Closeable, AutoCloseable {
void move(String file, String newFile) throws Exception;
boolean mkdirs(String file) throws Exception;
void mkdirs(String file) throws Exception;
void touch(String file) throws Exception;
boolean directoryExists(String file) throws Exception;
void directoryAccessible(String file) throws Exception;
Stream<FileEntry> listFiles(String file) throws Exception;
default Stream<FileEntry> listFilesRecursively(String file) throws Exception {

View file

@ -1 +1 @@
0.5.17
0.5.18