From cc30ba52a0115b098a20e09f416e6d477d0bad90 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 17 Jun 2025 14:42:51 +0000 Subject: [PATCH] Rework --- .../action/impl/ChgrpActionProvider.java | 1 + .../action/impl/ChmodActionProvider.java | 1 + .../action/impl/ChownActionProvider.java | 1 + .../ComputeDirectorySizesActionProvider.java | 41 +++++++++++++++++++ .../app/browser/file/BrowserFileListComp.java | 29 +++++++++++-- .../browser/file/BrowserFileListModel.java | 15 +++---- .../browser/file/BrowserFileListNameCell.java | 2 +- .../file/BrowserFileSystemTabModel.java | 13 +----- .../browser/menu/impl/ChmodMenuProvider.java | 19 ++++----- .../browser/menu/impl/ChownMenuProvider.java | 19 ++++----- ...=> ComputeDirectorySizesMenuProvider.java} | 28 +++++-------- .../browser/menu/impl/CopyMenuProvider.java | 2 +- .../menu/impl/CopyPathMenuProvider.java | 6 +++ .../menu/impl/NewItemMenuProvider.java | 2 +- .../java/io/xpipe/app/ext/ShellStore.java | 7 +--- .../xpipe/app/ext/SingletonSessionStore.java | 8 ++-- .../app/hub/comp/StoreCategoryConfigComp.java | 8 ++-- .../app/hub/comp/StoreCreationModel.java | 5 +++ .../io/xpipe/app/hub/comp/StoreEntryComp.java | 15 ++++--- .../xpipe/app/hub/comp/StoreEntryWrapper.java | 2 +- .../io/xpipe/app/storage/DataStorage.java | 2 +- .../app/storage/DataStoreCategoryConfig.java | 4 +- .../io/xpipe/app/storage/DataStoreEntry.java | 22 +++++----- app/src/main/java/module-info.java | 7 ++-- .../app/resources/theme/cupertinoDark.css | 2 +- .../app/resources/theme/cupertinoLight.css | 2 +- dist/changelogs/17.0.md | 17 ++++---- lang/strings/translations_en.properties | 11 +++-- 28 files changed, 179 insertions(+), 112 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/action/impl/ComputeDirectorySizesActionProvider.java rename app/src/main/java/io/xpipe/app/browser/menu/impl/{ComputeDirectorySizesAction.java => ComputeDirectorySizesMenuProvider.java} (57%) diff --git a/app/src/main/java/io/xpipe/app/browser/action/impl/ChgrpActionProvider.java b/app/src/main/java/io/xpipe/app/browser/action/impl/ChgrpActionProvider.java index 24901b949..fcd0cfeca 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/impl/ChgrpActionProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/action/impl/ChgrpActionProvider.java @@ -55,6 +55,7 @@ public class ChgrpActionProvider implements BrowserActionProvider { .getPath() .toString()) .toList())); + model.refreshEntriesSync(getEntries()); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/action/impl/ChmodActionProvider.java b/app/src/main/java/io/xpipe/app/browser/action/impl/ChmodActionProvider.java index 2169b1c59..c3a85048d 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/impl/ChmodActionProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/action/impl/ChmodActionProvider.java @@ -54,6 +54,7 @@ public class ChmodActionProvider implements BrowserActionProvider { .getPath() .toString()) .toList())); + model.refreshEntriesSync(getEntries()); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/action/impl/ChownActionProvider.java b/app/src/main/java/io/xpipe/app/browser/action/impl/ChownActionProvider.java index 04d2897e1..fec60a56d 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/impl/ChownActionProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/action/impl/ChownActionProvider.java @@ -55,6 +55,7 @@ public class ChownActionProvider implements BrowserActionProvider { .getPath() .toString()) .toList())); + model.refreshSync(); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/action/impl/ComputeDirectorySizesActionProvider.java b/app/src/main/java/io/xpipe/app/browser/action/impl/ComputeDirectorySizesActionProvider.java new file mode 100644 index 000000000..30b552a28 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/action/impl/ComputeDirectorySizesActionProvider.java @@ -0,0 +1,41 @@ +package io.xpipe.app.browser.action.impl; + +import io.xpipe.app.browser.action.BrowserAction; +import io.xpipe.app.browser.action.BrowserActionProvider; +import io.xpipe.app.browser.file.BrowserEntry; +import io.xpipe.app.browser.file.BrowserFileSystemHelper; +import io.xpipe.core.store.FileKind; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +public class ComputeDirectorySizesActionProvider implements BrowserActionProvider { + + @Jacksonized + @SuperBuilder + public static class Action extends BrowserAction { + + @Override + public void executeImpl() throws Exception { + var entries = getEntries(); + if (entries.size() == 1 && entries.getFirst().getRawFileEntry().equals(model.getCurrentDirectory())) { + entries = model.getFileList().getAll().getValue(); + } + + for (BrowserEntry be : entries) { + if (be.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) { + continue; + } + + var size = model.getFileSystem().getDirectorySize(be.getRawFileEntry().resolved().getPath()); + var fileEntry = be.getRawFileEntry(); + fileEntry.resolved().setSize("" + size); + model.getFileList().updateEntry(be.getRawFileEntry().getPath(), fileEntry); + } + } + } + + @Override + public String getId() { + return "computeDirectorySizes"; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 2bb89ecba..c30d2d7f9 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -29,6 +29,7 @@ import java.time.Instant; import java.time.ZoneId; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; import static io.xpipe.app.util.HumanReadableFormat.byteCount; import static javafx.scene.control.TableColumn.SortType.ASCENDING; @@ -493,8 +494,25 @@ public final class BrowserFileListComp extends SimpleComp { TableColumn modeCol, TableColumn ownerCol) { var lastDir = new SimpleObjectProperty(); - Runnable updateHandler = () -> { + BiConsumer, List> updateHandler = (o, n) -> { PlatformThread.runLaterIfNeeded(() -> { + // Optimization for single entry updates + if (o != null && n != null && o.size() == n.size()) { + var left = new HashSet<>(n); + o.forEach(left::remove); + if (left.size() == 1) { + var updatedEntry = left.iterator().next(); + var found = o.stream().filter(browserEntry -> browserEntry.getRawFileEntry().getPath() + .equals(updatedEntry.getRawFileEntry().getPath())).findFirst(); + if (found.isPresent()) { + table.refresh(); + table.getItems().set(table.getItems().indexOf(found.get()), updatedEntry); + return; + } + } + } + + table.setDisable(true); var newItems = new ArrayList<>(fileList.getShown().getValue()); table.getItems().clear(); @@ -551,17 +569,20 @@ public final class BrowserFileListComp extends SimpleComp { } } lastDir.setValue(currentDirectory); + table.setDisable(false); }); }; - updateHandler.run(); + updateHandler.accept(null, null); fileList.getShown().addListener((observable, oldValue, newValue) -> { // Delay to prevent internal tableview exceptions when sorting - Platform.runLater(updateHandler); + Platform.runLater(() -> { + updateHandler.accept(oldValue, newValue); + }); }); fileList.getFileSystemModel().getCurrentPath().addListener((observable, oldValue, newValue) -> { if (oldValue == null) { - updateHandler.run(); + updateHandler.accept(null, null); } }); } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java index 65aa71e0e..9c2e233cf 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java @@ -7,6 +7,7 @@ import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileEntry; import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FilePath; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; @@ -57,12 +58,13 @@ public final class BrowserFileListModel { } } - public void updateEntry(BrowserEntry old, FileEntry n) { - var index = all.getValue().indexOf(old); - if (index == -1) { + public void updateEntry(FilePath p, FileEntry n) { + var found = all.getValue().stream().filter(browserEntry -> browserEntry.getRawFileEntry().getPath().equals(p)).findFirst(); + if (found.isEmpty()) { return; } + var index = all.getValue().indexOf(found.get()); var l = new ArrayList<>(all.getValue()); if (n != null) { l.set(index, new BrowserEntry(n, this)); @@ -78,7 +80,7 @@ public final class BrowserFileListModel { refreshShown(); } - private void refreshShown() { + void refreshShown() { List filtered = fileSystemModel.getFilter().getValue() != null ? all.getValue().stream() .filter(entry -> { @@ -107,7 +109,7 @@ public final class BrowserFileListModel { return us; } - public BrowserEntry rename(BrowserEntry old, String newName) { + public BrowserEntry rename(BrowserEntry old, String newName) throws Exception { if (old == null || newName == null || fileSystemModel == null @@ -116,7 +118,6 @@ public final class BrowserFileListModel { return old; } - var fullPath = fileSystemModel.getCurrentPath().get().join(old.getFileName()); var newFullPath = fileSystemModel.getCurrentPath().get().join(newName); // This check will fail on case-insensitive file systems when changing the case of the file @@ -138,7 +139,7 @@ public final class BrowserFileListModel { ErrorEventFactory.fromMessage("Target " + newFullPath + " does already exist") .expected() .handle(); - fileSystemModel.refresh(); + fileSystemModel.refreshSync(); return old; } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java index 3e80d1127..e5166cb74 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java @@ -144,7 +144,7 @@ class BrowserFileListNameCell extends TableCell { getTableRow().requestFocus(); var it = getTableRow().getItem(); editing.setValue(null); - ThreadHelper.runAsync(() -> { + ThreadHelper.runFailableAsync(() -> { if (it == null) { return; } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java index 874e01433..341d40f46 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java @@ -171,13 +171,6 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab { - cdSyncWithoutCheck(currentPath.get()); - }); - } - public void refreshSync() throws Exception { cdSyncWithoutCheck(currentPath.get()); } @@ -195,10 +188,8 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab getName(BrowserFileSystemTabModel model, List entries) { - return new SimpleStringProperty(group); + return new SimpleStringProperty(permissions); } @Override public void execute(BrowserFileSystemTabModel model, List entries) { - var builder = ChgrpActionProvider.Action.builder(); + var builder = ChmodActionProvider.Action.builder(); builder.initEntries(model, entries); - builder.group(group); + builder.permissions(permissions); builder.recursive(recursive); var action = builder.build(); action.executeAsync(); @@ -162,9 +161,9 @@ public class ChmodMenuProvider implements BrowserMenuBranchProvider { return; } - var builder = ChgrpActionProvider.Action.builder(); + var builder = ChmodActionProvider.Action.builder(); builder.initEntries(model, entries); - builder.group(permissions.getValue()); + builder.permissions(permissions.getValue()); builder.recursive(recursive); var action = builder.build(); action.executeAsync(); diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/ChownMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/ChownMenuProvider.java index f7b46761a..19cb7ec57 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/ChownMenuProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/ChownMenuProvider.java @@ -1,6 +1,7 @@ package io.xpipe.app.browser.menu.impl; import io.xpipe.app.browser.action.impl.ChgrpActionProvider; +import io.xpipe.app.browser.action.impl.ChownActionProvider; import io.xpipe.app.browser.file.BrowserEntry; import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.browser.menu.BrowserMenuBranchProvider; @@ -18,8 +19,6 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.value.ObservableValue; import javafx.scene.control.TextField; -import org.kordamp.ikonli.javafx.FontIcon; - import java.util.List; import java.util.stream.Stream; @@ -110,24 +109,24 @@ public class ChownMenuProvider implements BrowserMenuBranchProvider { private static class FixedProvider implements BrowserMenuLeafProvider { - private final String group; + private final String owner; private final boolean recursive; - private FixedProvider(String group, boolean recursive) { - this.group = group; + private FixedProvider(String owner, boolean recursive) { + this.owner = owner; this.recursive = recursive; } @Override public ObservableValue getName(BrowserFileSystemTabModel model, List entries) { - return new SimpleStringProperty(group); + return new SimpleStringProperty(owner); } @Override public void execute(BrowserFileSystemTabModel model, List entries) { - var builder = ChgrpActionProvider.Action.builder(); + var builder = ChownActionProvider.Action.builder(); builder.initEntries(model, entries); - builder.group(group); + builder.owner(owner); builder.recursive(recursive); var action = builder.build(); action.executeAsync(); @@ -163,9 +162,9 @@ public class ChownMenuProvider implements BrowserMenuBranchProvider { return; } - var builder = ChgrpActionProvider.Action.builder(); + var builder = ChownActionProvider.Action.builder(); builder.initEntries(model, entries); - builder.group(user.getValue()); + builder.owner(user.getValue()); builder.recursive(recursive); var action = builder.build(); action.executeAsync(); diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesAction.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesMenuProvider.java similarity index 57% rename from app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesAction.java rename to app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesMenuProvider.java index e6789d314..5bf15d83e 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesAction.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/ComputeDirectorySizesMenuProvider.java @@ -1,5 +1,8 @@ package io.xpipe.app.browser.menu.impl; +import io.xpipe.app.action.AbstractAction; +import io.xpipe.app.browser.action.impl.ComputeDirectorySizesActionProvider; +import io.xpipe.app.browser.action.impl.DeleteActionProvider; import io.xpipe.app.browser.file.BrowserEntry; import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.browser.menu.BrowserMenuCategory; @@ -10,25 +13,15 @@ import io.xpipe.core.store.FileKind; import javafx.beans.value.ObservableValue; -import org.kordamp.ikonli.javafx.FontIcon; - import java.util.List; -public class ComputeDirectorySizesAction implements BrowserMenuLeafProvider { +public class ComputeDirectorySizesMenuProvider implements BrowserMenuLeafProvider { @Override - public void execute(BrowserFileSystemTabModel model, List entries) throws Exception { - for (BrowserEntry be : model.getFileList().getAll().getValue()) { - if (be.getRawFileEntry().getKind() != FileKind.DIRECTORY) { - continue; - } - - var size = - model.getFileSystem().getDirectorySize(be.getRawFileEntry().getPath()); - var fileEntry = be.getRawFileEntry(); - fileEntry.setSize("" + size); - model.getFileList().updateEntry(be, fileEntry); - } + public AbstractAction createAction(BrowserFileSystemTabModel model, List entries) { + var builder = ComputeDirectorySizesActionProvider.Action.builder(); + builder.initEntries(model, entries); + return builder.build(); } public String getId() { @@ -42,12 +35,13 @@ public class ComputeDirectorySizesAction implements BrowserMenuLeafProvider { @Override public ObservableValue getName(BrowserFileSystemTabModel model, List entries) { - return AppI18n.observable("computeDirectorySizes"); + var topLevel = entries.size() == 1 && entries.getFirst().getRawFileEntry().equals(model.getCurrentDirectory()); + return AppI18n.observable(topLevel ? "computeDirectorySizes" : "computeSize"); } @Override public boolean isApplicable(BrowserFileSystemTabModel model, List entries) { - return entries.size() == 1 && entries.getFirst().getRawFileEntry().equals(model.getCurrentDirectory()); + return entries.stream().allMatch(browserEntry -> browserEntry.getRawFileEntry().getKind() == FileKind.DIRECTORY); } @Override diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyMenuProvider.java index b6514cc65..8a4d0666d 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyMenuProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyMenuProvider.java @@ -26,7 +26,7 @@ public class CopyMenuProvider implements BrowserMenuLeafProvider { @Override public LabelGraphic getIcon(BrowserFileSystemTabModel model, List entries) { - return new LabelGraphic.IconGraphic("mdi2c-content-copy"); + return new LabelGraphic.IconGraphic("mdoal-file_copy"); } @Override diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyPathMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyPathMenuProvider.java index fe0df6b28..ba84d422c 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyPathMenuProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/CopyPathMenuProvider.java @@ -7,6 +7,7 @@ import io.xpipe.app.browser.menu.BrowserMenuCategory; import io.xpipe.app.browser.menu.BrowserMenuLeafProvider; import io.xpipe.app.core.AppI18n; import io.xpipe.app.util.ClipboardHelper; +import io.xpipe.app.util.LabelGraphic; import io.xpipe.core.store.FileKind; import javafx.beans.property.SimpleObjectProperty; @@ -35,6 +36,11 @@ public class CopyPathMenuProvider implements BrowserMenuBranchProvider { return AppI18n.observable("copyLocation"); } + @Override + public LabelGraphic getIcon(BrowserFileSystemTabModel model, List entries) { + return new LabelGraphic.IconGraphic("mdi2c-content-copy"); + } + @Override public List getBranchingActions( BrowserFileSystemTabModel model, List entries) { diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/NewItemMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/NewItemMenuProvider.java index 272088c20..2a015f223 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/NewItemMenuProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/NewItemMenuProvider.java @@ -34,7 +34,7 @@ public class NewItemMenuProvider implements BrowserMenuBranchProvider { @Override public BrowserMenuCategory getCategory() { - return BrowserMenuCategory.MUTATION; + return BrowserMenuCategory.ACTION; } @Override diff --git a/app/src/main/java/io/xpipe/app/ext/ShellStore.java b/app/src/main/java/io/xpipe/app/ext/ShellStore.java index 34c09aa2d..f3d97a0bd 100644 --- a/app/src/main/java/io/xpipe/app/ext/ShellStore.java +++ b/app/src/main/java/io/xpipe/app/ext/ShellStore.java @@ -29,14 +29,11 @@ public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore } } - startSessionIfNeeded(); + var session = startSessionIfNeeded(); - var session = getSession(); // This might be null if this store has been removed from this storage since the session was started // Then, the cache returns null - if (session == null) { - return standaloneControl().start(); - } + // getSession() return new StubShellControl(session.getShellControl()); } diff --git a/app/src/main/java/io/xpipe/app/ext/SingletonSessionStore.java b/app/src/main/java/io/xpipe/app/ext/SingletonSessionStore.java index c8e8512f2..aa7e63f91 100644 --- a/app/src/main/java/io/xpipe/app/ext/SingletonSessionStore.java +++ b/app/src/main/java/io/xpipe/app/ext/SingletonSessionStore.java @@ -38,16 +38,16 @@ public interface SingletonSessionStore return (T) getCache("session", getSessionClass(), null); } - default void startSessionIfNeeded() throws Exception { + default T startSessionIfNeeded() throws Exception { synchronized (this) { var s = getSession(); if (s != null) { if (s.isRunning()) { - return; + return s; } s.start(); - return; + return s; } try { @@ -60,8 +60,10 @@ public interface SingletonSessionStore s.addListener(running -> { onStateChange(running); }); + return s; } else { setSessionEnabled(false); + return null; } } catch (Exception ex) { setSessionEnabled(false); diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryConfigComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryConfigComp.java index 207b209bc..4c6a328a0 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryConfigComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryConfigComp.java @@ -56,7 +56,7 @@ public class StoreCategoryConfigComp extends SimpleComp { var scripts = new SimpleObjectProperty<>(c.getDontAllowScripts()); var confirm = new SimpleObjectProperty<>(c.getConfirmAllModifications()); var sync = new SimpleObjectProperty<>(c.getSync()); - var readOnly = new SimpleObjectProperty<>(c.getReadOnly()); + var freeze = new SimpleObjectProperty<>(c.getFreezeConfigurations()); var ref = new SimpleObjectProperty<>( c.getDefaultIdentityStore() != null ? DataStorage.get() @@ -73,10 +73,10 @@ public class StoreCategoryConfigComp extends SimpleComp { .nameAndDescription("categoryDontAllowScripts") .addYesNoToggle(scripts) .hide(!connectionsCategory) - .nameAndDescription("categoryReadOnly") - .addYesNoToggle(readOnly) .nameAndDescription("categoryConfirmAllModifications") .addYesNoToggle(confirm) + .nameAndDescription("categoryFreeze") + .addYesNoToggle(freeze) .hide(!connectionsCategory) .nameAndDescription("categoryDefaultIdentity") .addComp( @@ -96,7 +96,7 @@ public class StoreCategoryConfigComp extends SimpleComp { scripts.get(), confirm.get(), sync.get(), - readOnly.get(), + freeze.get(), ref.get() != null ? ref.get().get().getUuid() : null); }, config) diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java index 39dd9db9a..2663f9b7f 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationModel.java @@ -226,6 +226,11 @@ public class StoreCreationModel { validate(); commit(true); } catch (Throwable ex) { + var changedStore = !store.getValue().equals(entry.getValue().getStore()); + if (changedStore) { + int a = 0; + } + if (ex instanceof ValidationException) { ErrorEventFactory.expected(ex); } else if (ex instanceof StackOverflowError) { diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java index 188a3d95d..11113462c 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryComp.java @@ -19,7 +19,6 @@ import io.xpipe.app.hub.action.HubMenuItemProvider; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreColor; -import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.*; import io.xpipe.core.process.OsType; @@ -389,8 +388,8 @@ public abstract class StoreEntryComp extends SimpleComp { notes.visibleProperty().bind(BindingsHelper.map(getWrapper().getNotes(), s -> s.getCommited() == null)); items.add(2, notes); - var readOnly = new MenuItem(); - readOnly.graphicProperty() + var freeze = new MenuItem(); + freeze.graphicProperty() .bind(Bindings.createObjectBinding( () -> { var is = getWrapper().getReadOnly().get(); @@ -399,17 +398,17 @@ public abstract class StoreEntryComp extends SimpleComp { : new FontIcon("mdi2l-lock-open-outline"); }, getWrapper().getReadOnly())); - readOnly.textProperty() + freeze.textProperty() .bind(Bindings.createStringBinding( () -> { var is = getWrapper().getReadOnly().get(); - return is ? AppI18n.get("unsetReadOnly") : AppI18n.get("setReadOnly"); + return is ? AppI18n.get("unfreezeConfiguration") : AppI18n.get("freezeConfiguration"); }, AppI18n.activeLanguage(), getWrapper().getReadOnly())); - readOnly.setOnAction(event -> - getWrapper().getEntry().setReadOnly(!getWrapper().getReadOnly().get())); - items.add(3, readOnly); + freeze.setOnAction(event -> + getWrapper().getEntry().setFreeze(!getWrapper().getReadOnly().get())); + items.add(freeze); } if (cat == StoreActionCategory.DEVELOPER) { diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java index 8fc926db2..89db7d457 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java @@ -179,7 +179,7 @@ public class StoreEntryWrapper { color.setValue(entry.getColor()); notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes())); customIcon.setValue(entry.getIcon()); - readOnly.setValue(entry.isReadOnly()); + readOnly.setValue(entry.isFreeze()); iconFile.setValue(entry.getEffectiveIconFile()); busy.setValue(entry.getBusyCounter().get() != 0); deletable.setValue( diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 25b5bb545..2e073e948 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -880,7 +880,7 @@ public abstract class DataStorage { public boolean getEffectiveReadOnlyState(DataStoreEntry entry) { var cat = getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow(); var catConfig = getEffectiveCategoryConfig(cat); - return catConfig.getReadOnly() != null ? catConfig.getReadOnly() : entry.isReadOnly(); + return catConfig.getFreezeConfigurations() != null ? catConfig.getFreezeConfigurations() : entry.isFreeze(); } public DataStoreColor getEffectiveColor(DataStoreEntry entry) { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreCategoryConfig.java b/app/src/main/java/io/xpipe/app/storage/DataStoreCategoryConfig.java index 8bf80f51b..95c0ef85b 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreCategoryConfig.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreCategoryConfig.java @@ -41,7 +41,7 @@ public class DataStoreCategoryConfig { sync = config.sync; } if (readOnly == null) { - readOnly = config.readOnly; + readOnly = config.freezeConfigurations; } } return new DataStoreCategoryConfig( @@ -58,7 +58,7 @@ public class DataStoreCategoryConfig { @With Boolean sync; - Boolean readOnly; + Boolean freezeConfigurations; UUID defaultIdentityStore; } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index e1e166bfb..b92212f94 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -77,7 +77,7 @@ public class DataStoreEntry extends StorageElement { @NonFinal @Getter - boolean readOnly; + boolean freeze; @Getter @NonFinal @@ -99,7 +99,7 @@ public class DataStoreEntry extends StorageElement { DataStoreColor color, String notes, String icon, - boolean readOnly, + boolean freeze, int orderIndex) { super(directory, uuid, name, lastUsed, lastModified, expanded, dirty); this.color = color; @@ -111,7 +111,7 @@ public class DataStoreEntry extends StorageElement { this.storePersistentStateNode = storePersistentState; this.notes = notes; this.icon = icon; - this.readOnly = readOnly; + this.freeze = freeze; this.orderIndex = orderIndex; } @@ -124,7 +124,7 @@ public class DataStoreEntry extends StorageElement { Instant lastModified, DataStore store, String icon, - boolean readOnly, + boolean freeze, int orderIndex) { super(directory, uuid, name, lastUsed, lastModified, false, false); this.categoryUuid = categoryUuid; @@ -135,7 +135,7 @@ public class DataStoreEntry extends StorageElement { this.expanded = false; this.provider = null; this.storePersistentStateNode = null; - this.readOnly = readOnly; + this.freeze = freeze; this.orderIndex = orderIndex; } @@ -236,7 +236,7 @@ public class DataStoreEntry extends StorageElement { } }) .orElse(null); - var readOnly = Optional.ofNullable(json.get("readOnly")) + var freeze = Optional.ofNullable(json.get("freeze")) .map(jsonNode -> jsonNode.booleanValue()) .orElse(false); @@ -315,7 +315,7 @@ public class DataStoreEntry extends StorageElement { color, notes, icon, - readOnly, + freeze, orderIndex)); } @@ -464,7 +464,7 @@ public class DataStoreEntry extends StorageElement { obj.put("categoryUuid", categoryUuid.toString()); obj.set("color", mapper.valueToTree(color)); obj.set("icon", mapper.valueToTree(icon)); - obj.put("readOnly", readOnly); + obj.put("freeze", freeze); obj.put("orderIndex", orderIndex); ObjectNode stateObj = JsonNodeFactory.instance.objectNode(); @@ -512,9 +512,9 @@ public class DataStoreEntry extends StorageElement { } } - public void setReadOnly(boolean newValue) { - var changed = readOnly != newValue; - this.readOnly = newValue; + public void setFreeze(boolean newValue) { + var changed = freeze != newValue; + this.freeze = newValue; if (changed) { notifyUpdate(false, true); } diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index 92bdfbea7..a20f087b1 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -126,7 +126,7 @@ open module io.xpipe.app { ScanHubBatchProvider, RunCommandInBrowserActionProvider, RunCommandInBackgroundActionProvider, - RunCommandInTerminalActionProvider, + RunCommandInTerminalActionProvider, ComputeDirectorySizesMenuProvider, FollowLinkMenuProvider, BackMenuProvider, ForwardMenuProvider, @@ -142,6 +142,7 @@ open module io.xpipe.app { TransferFilesActionProvider, EditFileMenuProvider, RunFileMenuProvider, + RenameMenuProvider, ChmodMenuProvider, ChownMenuProvider, ChgrpActionProvider, @@ -149,16 +150,16 @@ open module io.xpipe.app { CopyMenuProvider, CopyPathMenuProvider, PasteMenuProvider, + CompressMenuProvider, NewItemMenuProvider, - RenameMenuProvider, DeleteActionProvider, + ComputeDirectorySizesActionProvider, DeleteMenuProvider, ChownActionProvider, ChmodActionProvider, TarActionProvider, UntarActionProvider, ZipActionProvider, - CompressMenuProvider, UnzipActionProvider, UnzipHereUnixMenuProvider, UnzipDirectoryUnixMenuProvider, diff --git a/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoDark.css b/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoDark.css index e1d3fac5b..a5c08b275 100644 --- a/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoDark.css +++ b/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoDark.css @@ -2,7 +2,7 @@ .root:macos { -color-bg-default-transparent: #0d0d10D6; } -.root .button { +.root .button, .root .toggle-button { -fx-effect: NONE; } diff --git a/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoLight.css b/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoLight.css index 76db77ec3..0136d5a50 100644 --- a/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoLight.css +++ b/app/src/main/resources/io/xpipe/app/resources/theme/cupertinoLight.css @@ -1,6 +1,6 @@ .root { -color-bg-default-transparent: #FFFFFFAF; } -.root .button { +.root .button, .root .toggle-button { -fx-effect: NONE; } diff --git a/dist/changelogs/17.0.md b/dist/changelogs/17.0.md index cae604169..1dd28952d 100644 --- a/dist/changelogs/17.0.md +++ b/dist/changelogs/17.0.md @@ -1,17 +1,20 @@ -## Browser +## File browser +- Actions which modify a single file will now automatically refresh the file list to show updated changes +- There is now a new file browser action to compute directory sizes - Renaming a file now moves the caret to the end of the base file name - Fix file renaming not working if previous rename operation was cancelled +- The transfer speed in the file browser on Windows for multiple files has been optimized + +## Connection hub + +- You can now set connection configurations to be frozen, meaning that the connection entry can't be modified or deleted. This is helpful for templating and team vault setups +- When editing an incomplete connection configuration, the focus will automatically jump to the first incomplete/invalid value. This makes keyboard usage easier +- Password managers now support retrieving both username and password of an entry. For that, you can now create password manager identities that automatically provide the username and password ## Other - You can now specify a git username and password in the settings menu if your local system does not have a git client with configured credentials -- You can now set connection configurations to read-only, meaning that the connection entry can't be modified or deleted. This is helpful for templating and team vault setups -- File browser actions which modify a single file will now automatically refresh the file list to show updated changes -- The transfer speed in the file browser on Windows systems has been optimized -- When editing an incomplete connection configuration, the focus will automatically jump to the first incomplete/invalid value. This makes keyboard usage easier -- Password managers now support retrieving both username and password of an entry - You can now disable icon sources without having to remove them -- There is now a new file browser action to compute directory sizes - Windows ARM build diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index de077acfa..c7c7bdb78 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1462,13 +1462,16 @@ gitPasswordDescription=The password or personal access token to use to authentic setReadOnly=Set read-only unsetReadOnly=Unset read-only readOnlyStoreError=This entry is marked as read-only. Choose a different name to save your changes to a new copy. -categoryReadOnly=Read-only connection configuration -categoryReadOnlyDescription=Marks connection configurations as read-only. This means that no existing connection entry configuration in it can be modified. New connections can be added though. +#force +categoryFreeze=Freeze connection configurations +#force +categoryFreezeDescription=Marks connection configurations as read-only. This means that no existing connection entry configuration in this category can be modified. New connections can be added though. updateFail=Update installation did not succeed updateFailAction=Install update manually updateFailActionDescription=Check out the latest releases at GitHub onePasswordPlaceholder=Item name computeDirectorySizes=Compute directory sizes +computeSize=Compute size vncClient=VNC client vncClientDescription=The VNC client to launch when opening VNC connections in XPipe.\n\nYou have the option to either use the integrated VNC client within XPipe or alternatively launch an external locally installed VNC client if you are looking for more customization. integratedXPipeVncClient=Integrated XPipe VNC client @@ -1530,4 +1533,6 @@ moveToBottom=Move to bottom moveToTop=Move to top category=Category includeRoot=Include root -excludeRoot=Exclude root \ No newline at end of file +excludeRoot=Exclude root +freezeConfiguration=Freeze configuration +unfreezeConfiguration=Unfreeze configuration \ No newline at end of file