Browser fixes

This commit is contained in:
crschnick 2024-08-18 10:23:44 +00:00
parent ef5427f046
commit b5471e52d1
23 changed files with 215 additions and 154 deletions

View file

@ -81,6 +81,7 @@ public final class BrowserFileListComp extends SimpleComp {
filenameCol.setSortType(ASCENDING);
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing(), col.getTableView()));
filenameCol.setReorderable(false);
filenameCol.setResizable(false);
var sizeCol = new TableColumn<BrowserEntry, Number>();
sizeCol.textProperty().bind(AppI18n.observable("size"));
@ -118,36 +119,53 @@ public final class BrowserFileListComp extends SimpleComp {
ownerCol.setCellFactory(col -> new FileOwnerCell());
ownerCol.setSortable(false);
ownerCol.setReorderable(false);
ownerCol.prefWidthProperty().bind(ownerWidth);
ownerCol.setResizable(false);
var table = new TableView<BrowserEntry>();
table.setSkin(new TableViewSkin<>(table));
table.setAccessibleText("Directory contents");
table.setPlaceholder(new Region());
table.getStyleClass().add(Styles.STRIPED);
table.getColumns().setAll(filenameCol, sizeCol, modeCol, mtimeCol);
table.getColumns().setAll(filenameCol, sizeCol, modeCol, ownerCol, mtimeCol);
table.getSortOrder().add(filenameCol);
table.setFocusTraversable(true);
table.setSortPolicy(param -> {
fileList.setComparator(table.getComparator());
return true;
});
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN);
table.setFixedCellSize(32.0);
var os = fileList.getFileSystemModel().getFileSystem().getShell().orElseThrow().getOsType();
table.widthProperty().subscribe((newValue) -> {
if (os != OsType.WINDOWS) {
ownerCol.setVisible(newValue.doubleValue() > 1000);
}
var width = getFilenameWidth(table);
filenameCol.setPrefWidth(width);
});
table.lookupAll(".scroll-bar").stream().filter(node -> node.getPseudoClassStates().contains(PseudoClass.getPseudoClass("horizontal"))).findFirst().ifPresent(node -> {
Region region = (Region) node;
region.setMinHeight(0);
region.setPrefHeight(0);
region.setMaxHeight(0);
});
prepareTableSelectionModel(table);
prepareTableShortcuts(table);
prepareTableEntries(table);
prepareTableChanges(table, mtimeCol, modeCol, ownerCol);
prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol);
prepareTypedSelectionModel(table);
return table;
}
private double getFilenameWidth(TableView<?> tableView) {
var sum = tableView.getColumns().stream().filter(tableColumn -> tableColumn.isVisible() &&
tableView.getColumns().indexOf(tableColumn) != 0)
.mapToDouble(value -> value.getPrefWidth()).sum() + 7;
return tableView.getWidth() - sum;
}
private String formatOwner(BrowserEntry param) {
FileInfo.Unix unix = param.getRawFileEntry().resolved().getInfo() instanceof FileInfo.Unix u ? u : null;
if (unix == null) {
@ -400,72 +418,60 @@ public final class BrowserFileListComp extends SimpleComp {
private void prepareTableChanges(
TableView<BrowserEntry> table,
TableColumn<BrowserEntry, String> filenameCol,
TableColumn<BrowserEntry, Instant> mtimeCol,
TableColumn<BrowserEntry, String> modeCol,
TableColumn<BrowserEntry, String> ownerCol) {
var lastDir = new SimpleObjectProperty<FileEntry>();
Runnable updateHandler = () -> {
Platform.runLater(() -> {
PlatformThread.runLaterIfNeeded(() -> {
var newItems = new ArrayList<>(fileList.getShown().getValue());
table.getItems().clear();
var hasModifiedDate = newItems.size() == 0
|| newItems.stream()
.anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
var hasModifiedDate = newItems.size() == 0 || newItems.stream().anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
if (!hasModifiedDate) {
table.getColumns().remove(mtimeCol);
mtimeCol.setVisible(false);
} else {
if (!table.getColumns().contains(mtimeCol)) {
table.getColumns().add(mtimeCol);
}
mtimeCol.setVisible(true);
}
ownerWidth.set(fileList.getAll().getValue().stream()
.map(browserEntry -> formatOwner(browserEntry))
.map(s -> s != null ? s.length() * 10 : 0)
.max(Comparator.naturalOrder()).orElse(150));
ownerWidth.set(fileList.getAll().getValue().stream().map(browserEntry -> formatOwner(browserEntry)).map(
s -> s != null ? s.length() * 9 : 0).max(Comparator.naturalOrder()).orElse(150));
ownerCol.setPrefWidth(ownerWidth.get());
if (fileList.getFileSystemModel().getFileSystem() != null) {
var shell = fileList.getFileSystemModel()
.getFileSystem()
.getShell()
.orElseThrow();
var notWindows = !OsType.WINDOWS.equals(shell.getOsType());
if (!notWindows) {
table.getColumns().remove(modeCol);
table.getColumns().remove(ownerCol);
var shell = fileList.getFileSystemModel().getFileSystem().getShell().orElseThrow();
if (OsType.WINDOWS.equals(shell.getOsType())) {
modeCol.setVisible(false);
ownerCol.setVisible(false);
} else {
if (!table.getColumns().contains(modeCol)) {
table.getColumns().add(modeCol);
}
if (!table.getColumns().contains(ownerCol)) {
table.getColumns().add(table.getColumns().size() - 1, ownerCol);
} else {
table.getColumns().remove(ownerCol);
modeCol.setVisible(true);
if (table.getWidth() > 1000) {
ownerCol.setVisible(true);
}
}
}
if (!table.getItems().equals(newItems)) {
// Sort the list ourselves as sorting the table would incur a lot of cell updates
var obs = FXCollections.observableList(newItems);
table.getItems().setAll(obs);
}
var currentDirectory = fileList.getFileSystemModel().getCurrentDirectory();
if (!Objects.equals(lastDir.get(), currentDirectory)) {
var width = getFilenameWidth(table);
filenameCol.setPrefWidth(width);
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
if (skin != null) {
VirtualFlow<?> flow =
(VirtualFlow<?>) skin.getChildren().get(1);
ScrollBar vbar =
(ScrollBar) flow.getChildrenUnmodifiable().get(2);
var currentDirectory = fileList.getFileSystemModel().getCurrentDirectory();
if (skin != null && !Objects.equals(lastDir.get(), currentDirectory)) {
VirtualFlow<?> flow = (VirtualFlow<?>) skin.getChildren().get(1);
ScrollBar vbar = (ScrollBar) flow.getChildrenUnmodifiable().get(2);
if (vbar.getValue() != 0.0) {
table.scrollTo(0);
}
}
}
lastDir.setValue(currentDirectory);
});
};
updateHandler.run();
fileList.getShown().addListener((observable, oldValue, newValue) -> {
updateHandler.run();
@ -606,9 +612,7 @@ public final class BrowserFileListComp extends SimpleComp {
.getCurrentParentDirectory());
return notDir || isParentLink;
},
itemProperty())
.not()
.not())
itemProperty()))
.focusTraversable(false)
.createRegion();

View file

@ -5,10 +5,9 @@ import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialect;
import lombok.Getter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Getter
@ -16,8 +15,8 @@ public class OpenFileSystemCache extends ShellControlCache {
private final OpenFileSystemModel model;
private final String username;
private final Map<Integer, String> users = new HashMap<>();
private final Map<Integer, String> groups = new HashMap<>();
private final Map<Integer, String> users = new LinkedHashMap<>();
private final Map<Integer, String> groups = new LinkedHashMap<>();
public OpenFileSystemCache(OpenFileSystemModel model) throws Exception {
super(model.getFileSystem().getShell().orElseThrow());

View file

@ -1,46 +0,0 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Getter;
@Getter
public class BrowserSessionMultiTab extends BrowserSessionTab<DataStore> {
protected final Property<BrowserSessionTab<?>> currentTab = new SimpleObjectProperty<>();
private final ObservableList<BrowserSessionTab<?>> allTabs = FXCollections.observableArrayList();
public BrowserSessionMultiTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<?> entry) {
super(browserModel, entry);
}
public Comp<?> comp() {
var map = FXCollections.<Comp<?>, ObservableValue<Boolean>>observableHashMap();
allTabs.addListener((ListChangeListener<? super BrowserSessionTab<?>>) c -> {
for (BrowserSessionTab<?> a : c.getAddedSubList()) {
map.put(a.comp(), BindingsHelper.map(currentTab, browserSessionTab -> a.equals(browserSessionTab)));
}
});
var mt = new MultiContentComp(map);
return mt;
}
public boolean canImmediatelyClose() {
return true;
}
public void init() {}
public void close() {}
}

View file

@ -3,11 +3,8 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@ -15,52 +12,28 @@ import java.util.Map;
public class MultiContentComp extends SimpleComp {
private final ObservableMap<Comp<?>, ObservableValue<Boolean>> content;
private final Map<Comp<?>, ObservableValue<Boolean>> content;
public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content) {
this.content = FXCollections.observableMap(content);
}
public MultiContentComp(ObservableMap<Comp<?>, ObservableValue<Boolean>> content) {
this.content = content;
}
@Override
protected Region createSimple() {
ObservableMap<Comp<?>, Region> m = FXCollections.observableHashMap();
content.addListener((MapChangeListener<? super Comp<?>, ? super ObservableValue<Boolean>>) change -> {
if (change.wasAdded()) {
var r = change.getKey().createRegion();
change.getValueAdded().subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> {
r.setManaged(val);
r.setVisible(val);
});
});
m.put(change.getKey(), r);
} else {
m.remove(change.getKey());
}
});
var stack = new StackPane();
m.addListener((MapChangeListener<? super Comp<?>, Region>) change -> {
if (change.wasAdded()) {
stack.getChildren().add(change.getValueAdded());
} else {
stack.getChildren().remove(change.getValueRemoved());
}
});
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> e : content.entrySet()) {
var r = e.getKey().createRegion();
e.getValue().subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> {
r.setManaged(val);
r.setVisible(val);
if (val && !stack.getChildren().contains(r)) {
stack.getChildren().add(r);
} else {
stack.getChildren().remove(r);
}
});
});
m.put(e.getKey(), r);
}
return stack;

View file

@ -4,13 +4,11 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCombination;
import javafx.stage.Window;
import javafx.util.Duration;
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
@ -46,7 +44,6 @@ public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
tt.setWrapText(true);
tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip");
tt.setHideDelay(Duration.INDEFINITE);
Tooltip.install(struc.get(), tt);
}

View file

@ -289,11 +289,7 @@
-fx-font-size: 0.9em;
-fx-font-weight: bolder;
-fx-opacity: 0.9;
-fx-padding: 0 0 0 12;
}
.browser .table-row-cell {
-fx-padding: 0 0 0 5;
-fx-padding: 0 0 0 7;
}
.browser .table-row-cell:empty {

View file

@ -1,6 +1,7 @@
.scroll-bar:vertical {
-fx-min-width: 7px;
-fx-pref-width: 7px;
-fx-max-width: 7px;
-fx-padding: 1;
-fx-background-color: transparent;
}

View file

@ -0,0 +1,72 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppI18n;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class ChgrpAction implements BranchAction {
@Override
public Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new FontIcon("mdi2a-account-group-outline");
}
@Override
public Category getCategory() {
return Category.MUTATION;
}
@Override
public ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return AppI18n.observable("chgrp");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS;
}
@Override
public List<LeafAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getCache().getGroups().entrySet().stream()
.filter(e -> !e.getValue().equals("nogroup") && (e.getKey().equals(0) || e.getKey() >= 1000))
.map(e -> e.getValue()).map(s -> (LeafAction) new Chgrp(s)).toList();
}
private static class Chgrp implements LeafAction {
private final String option;
private Chgrp(String option) {
this.option = option;
}
@Override
public ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new SimpleStringProperty(option);
}
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
model.getFileSystem()
.getShell()
.orElseThrow()
.executeSimpleCommand(CommandBuilder.of()
.add("chgrp", option)
.addFiles(entries.stream()
.map(browserEntry ->
browserEntry.getRawFileEntry().getPath())
.toList()));
}
}
}

View file

@ -0,0 +1,72 @@
package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.action.BranchAction;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppI18n;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class ChownAction implements BranchAction {
@Override
public Node getIcon(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new FontIcon("mdi2a-account-edit");
}
@Override
public Category getCategory() {
return Category.MUTATION;
}
@Override
public ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return AppI18n.observable("chown");
}
@Override
public boolean isApplicable(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS;
}
@Override
public List<LeafAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) {
return model.getCache().getUsers().entrySet().stream()
.filter(e -> !e.getValue().equals("nobody") && (e.getKey().equals(0) || e.getKey() >= 1000))
.map(e -> e.getValue()).map(s -> (LeafAction) new Chown(s)).toList();
}
private static class Chown implements LeafAction {
private final String option;
private Chown(String option) {
this.option = option;
}
@Override
public ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return new SimpleStringProperty(option);
}
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
model.getFileSystem()
.getShell()
.orElseThrow()
.executeSimpleCommand(CommandBuilder.of()
.add("chown", option)
.addFiles(entries.stream()
.map(browserEntry ->
browserEntry.getRawFileEntry().getPath())
.toList()));
}
}
}

View file

@ -52,6 +52,8 @@ open module io.xpipe.ext.base {
EditFileAction,
RunAction,
ChmodAction,
ChownAction,
ChgrpAction,
CopyAction,
CopyPathAction,
PasteAction,

View file

@ -0,0 +1,3 @@
chmod=Chmod
chgrp=Chgrp
chown=Chown

View file

@ -87,7 +87,6 @@ back=Gå tilbage
browseInWindowsExplorer=Gennemse i Windows explorer
browseInDefaultFileManager=Gennemse i standard filhåndtering
browseInFinder=Gennemse i finder
chmod=Chmod
copy=Kopi
paste=Indsæt
copyLocation=Kopiér placering

View file

@ -82,7 +82,6 @@ back=Zurückgehen
browseInWindowsExplorer=Blättern im Windows-Explorer
browseInDefaultFileManager=Blättern im Standard-Dateimanager
browseInFinder=Im Finder blättern
chmod=Chmod
#custom
copy=Kopieren
paste=Einfügen

View file

@ -81,7 +81,6 @@ back=Go back
browseInWindowsExplorer=Browse in Windows explorer
browseInDefaultFileManager=Browse in default file manager
browseInFinder=Browse in finder
chmod=Chmod
copy=Copy
paste=Paste
copyLocation=Copy location

View file

@ -81,7 +81,6 @@ back=Volver atrás
browseInWindowsExplorer=Navegar en el explorador de Windows
browseInDefaultFileManager=Navegar en el gestor de archivos por defecto
browseInFinder=Navegar en el buscador
chmod=Chmod
copy=Copia
paste=Pegar
copyLocation=Ubicación de la copia

View file

@ -81,7 +81,6 @@ back=Retourner
browseInWindowsExplorer=Naviguer dans l'explorateur Windows
browseInDefaultFileManager=Parcourir dans le gestionnaire de fichiers par défaut
browseInFinder=Parcourir dans finder
chmod=Chmod
copy=Copie
paste=Coller
copyLocation=Emplacement de la copie

View file

@ -81,7 +81,6 @@ back=Torna indietro
browseInWindowsExplorer=Sfogliare in Windows explorer
browseInDefaultFileManager=Sfoglia nel file manager predefinito
browseInFinder=Sfogliare in finder
chmod=Chmod
copy=Copia
paste=Incolla
copyLocation=Posizione di copia

View file

@ -81,7 +81,6 @@ back=戻る
browseInWindowsExplorer=Windowsエクスプローラでブラウズする
browseInDefaultFileManager=デフォルトのファイルマネージャーでブラウズする
browseInFinder=ファインダーでブラウズする
chmod=Chmod
copy=コピー
paste=貼り付け
copyLocation=コピー場所

View file

@ -81,7 +81,6 @@ back=Teruggaan
browseInWindowsExplorer=Bladeren in Windows verkenner
browseInDefaultFileManager=Bladeren in standaard bestandsbeheer
browseInFinder=Bladeren in finder
chmod=Chmod
copy=Kopiëren
paste=Plakken
copyLocation=Locatie kopiëren

View file

@ -81,7 +81,6 @@ back=Volta atrás
browseInWindowsExplorer=Navega no Windows Explorer
browseInDefaultFileManager=Navega no gestor de ficheiros predefinido
browseInFinder=Navega no localizador
chmod=Chmod
copy=Copia
paste=Cola
copyLocation=Copia a localização

View file

@ -81,7 +81,6 @@ back=Возвращайся назад
browseInWindowsExplorer=Обзор в проводнике Windows
browseInDefaultFileManager=Обзор в файловом менеджере по умолчанию
browseInFinder=Обзор в программе для поиска
chmod=Chmod
copy=Скопируй
paste=Paste
copyLocation=Место копирования

View file

@ -81,7 +81,6 @@ back=Geri dön
browseInWindowsExplorer=Windows gezgininde göz atın
browseInDefaultFileManager=Varsayılan dosya yöneticisine göz atın
browseInFinder=Bulucuya göz atın
chmod=Chmod
copy=Anlaşıldı
paste=Yapıştır
copyLocation=Kopyalama konumu

View file

@ -81,7 +81,6 @@ back=返回
browseInWindowsExplorer=在 Windows 资源管理器中浏览
browseInDefaultFileManager=在默认文件管理器中浏览
browseInFinder=在查找器中浏览
chmod=Chmod
copy=复制
paste=粘贴
copyLocation=复制位置