This commit is contained in:
crschnick 2024-05-30 02:34:31 +00:00
parent ba7c83a1e8
commit 9a71819611
39 changed files with 511 additions and 636 deletions

View file

@ -108,7 +108,6 @@ run {
}
workingDir = rootDir
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
}
task runAttachedDebugger(type: JavaExec) {
@ -122,7 +121,7 @@ task runAttachedDebugger(type: JavaExec) {
"-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.4.jar=port:7857,host:localhost".toString(),
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
)
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
jvmArgs += '-XX:+EnableDynamicAgentLoading'
systemProperties run.systemProperties
}

View file

@ -6,16 +6,18 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.SneakyThrows;
import java.util.List;
@ -68,8 +70,9 @@ public class BrowserOverviewComp extends SimpleComp {
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
var recent = ListBindingsHelper.mappedContentBinding(
model.getSavedState().getRecentDirectories(),
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);

View file

@ -8,7 +8,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings;
@ -47,7 +47,7 @@ public class BrowserTransferComp extends SimpleComp {
var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = new DerivedObservableList<>(syncItems, true).mapped(item -> item.getBrowserEntry()).getList();
var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry());
var list = new BrowserSelectionListComp(
binding,
entry -> Bindings.createStringBinding(

View file

@ -1,7 +1,5 @@
package io.xpipe.app.browser;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
@ -15,9 +13,10 @@ import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -31,6 +30,9 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import java.util.List;
public class BrowserWelcomeComp extends SimpleComp {
@ -65,7 +67,7 @@ public class BrowserWelcomeComp extends SimpleComp {
return new VBox(hbox);
}
var list = new DerivedObservableList<>(state.getEntries(), true).filtered(e -> {
var list = ListBindingsHelper.filteredContentBinding(state.getEntries(), e -> {
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
if (entry.isEmpty()) {
return false;
@ -76,7 +78,7 @@ public class BrowserWelcomeComp extends SimpleComp {
}
return true;
}).getList();
});
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
var headerBinding = BindingsHelper.flatMap(empty, b -> {

View file

@ -24,10 +24,6 @@ public class BrowserEntry {
}
private static BrowserIconFileType fileType(FileSystem.FileEntry rawFileEntry) {
if (rawFileEntry == null) {
return null;
}
if (rawFileEntry.getKind() == FileKind.DIRECTORY) {
return null;
}
@ -42,10 +38,6 @@ public class BrowserEntry {
}
private static BrowserIconDirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
if (rawFileEntry == null) {
return null;
}
if (rawFileEntry.getKind() != FileKind.DIRECTORY) {
return null;
}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.browser.session;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.FileReference;
@ -10,10 +10,12 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.util.FailableFunction;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import lombok.Setter;
@ -38,8 +40,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
return;
}
var l = new DerivedObservableList<>(fileSelection, true);
l.bindContent(newValue.getFileList().getSelection());
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
});
}

View file

@ -4,9 +4,8 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.scene.control.Button;
@ -39,13 +38,10 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
}))
.createRegion();
List<? extends ObservableValue<Boolean>> l = cm.getItems().stream()
.map(menuItem -> menuItem.getGraphic().visibleProperty())
.toList();
button.visibleProperty()
.bind(Bindings.createBooleanBinding(() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
}, l.toArray(ObservableValue[]::new)));
.bind(ListBindingsHelper.anyMatch(cm.getItems().stream()
.map(menuItem -> menuItem.getGraphic().visibleProperty())
.toList()));
var graphic = new FontIcon("mdi2c-chevron-double-down");
button.fontProperty().subscribe(c -> {

View file

@ -3,8 +3,9 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@ -88,8 +89,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
}
if (!listView.getChildren().equals(newShown)) {
var d = new DerivedObservableList<>(listView.getChildren(), true);
d.setContent(newShown);
ListBindingsHelper.setContent(listView.getChildren(), newShown);
}
};

View file

@ -52,6 +52,7 @@ public class StoreToggleComp extends SimpleComp {
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
v -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
section.getWrapper().refreshChildren();
});
}

View file

@ -60,7 +60,7 @@ public class StoreCategoryWrapper {
}
public StoreCategoryWrapper getParent() {
return StoreViewState.get().getCategories().getList().stream()
return StoreViewState.get().getCategories().stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory()))
.findAny()
@ -122,7 +122,7 @@ public class StoreCategoryWrapper {
sortMode.setValue(category.getSortMode());
share.setValue(category.isShare());
containedEntries.setAll(StoreViewState.get().getAllEntries().getList().stream()
containedEntries.setAll(StoreViewState.get().getAllEntries().stream()
.filter(entry -> {
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|| (AppPrefs.get()
@ -132,7 +132,7 @@ public class StoreCategoryWrapper {
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
})
.toList());
children.setAll(StoreViewState.get().getCategories().getList().stream()
children.setAll(StoreViewState.get().getCategories().stream()
.filter(storeCategoryWrapper -> getCategory()
.getUuid()
.equals(storeCategoryWrapper.getCategory().getParentCategory()))

View file

@ -372,14 +372,6 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(new SeparatorMenuItem());
}
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2n-note-text"));
notes.setOnAction(event -> {
wrapper.getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
event.consume();
});
notes.visibleProperty().bind(BindingsHelper.map(wrapper.getNotes(), s -> s.getCommited() == null));
contextMenu.getItems().add(notes);
if (AppPrefs.get().developerMode().getValue()) {
var browse = new MenuItem(AppI18n.get("browseInternalStorage"), new FontIcon("mdi2f-folder-open-outline"));
browse.setOnAction(
@ -387,6 +379,26 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(browse);
}
if (wrapper.getEntry().getProvider() != null) {
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
StoreViewState.get()
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
.forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem();
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
if (storeCategoryWrapper.getParent() == null) {
m.setDisable(true);
}
move.getItems().add(m);
});
contextMenu.getItems().add(move);
}
if (DataStorage.get().isRootEntry(wrapper.getEntry())) {
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
var none = new MenuItem("None");
@ -406,66 +418,13 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(color);
}
if (wrapper.getEntry().getProvider() != null) {
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
StoreViewState.get()
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
.getList()
.forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem();
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
event.consume();
});
if (storeCategoryWrapper.getParent() == null) {
m.setDisable(true);
}
move.getItems().add(m);
});
contextMenu.getItems().add(move);
}
var order = new Menu(AppI18n.get("order"), new FontIcon("mdal-bookmarks"));
var noOrder = new MenuItem("None");
noOrder.setOnAction(event -> {
DataStorage.get().orderBefore(wrapper.getEntry(), null);
var notes = new MenuItem(AppI18n.get("addNotes"), new FontIcon("mdi2n-note-text"));
notes.setOnAction(event -> {
wrapper.getNotes().setValue(new StoreNotes(null, getDefaultNotes()));
event.consume();
});
if (wrapper.getEntry().getOrderBefore() == null) {
noOrder.setDisable(true);
}
order.getItems().add(noOrder);
var stick = new MenuItem(AppI18n.get("stickToTop"));
stick.setOnAction(event -> {
DataStorage.get().orderBefore(wrapper.getEntry(), wrapper.getEntry());
event.consume();
});
if (wrapper.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
stick.setDisable(true);
}
order.getItems().add(stick);
order.getItems().add(new SeparatorMenuItem());
var section = StoreViewState.get().getParentSectionForWrapper(wrapper);
section.get().getAllChildren().getList().forEach(other -> {
var ow = other.getWrapper();
var op = ow.getEntry().getProvider();
MenuItem m = new MenuItem(ow.getName().getValue(),
op != null ? PrettyImageHelper.ofFixedSizeSquare(op.getDisplayIconFileName(ow.getEntry().getStore()),
16).createRegion() : null);
if (ow.getEntry().getUuid().equals(wrapper.getEntry().getOrderBefore())) {
m.setDisable(true);
}
m.setOnAction(event -> {
wrapper.orderBefore(ow);
event.consume();
});
order.getItems().add(m);
});
contextMenu.getItems().add(order);
contextMenu.getItems().add(new SeparatorMenuItem());
notes.visibleProperty().bind(BindingsHelper.map(wrapper.getNotes(), s -> s.getCommited() == null));
contextMenu.getItems().add(notes);
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty()

View file

@ -18,8 +18,8 @@ public class StoreEntryListComp extends SimpleComp {
private Comp<?> createList() {
var content = new ListBoxViewComp<>(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList(),
StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList(),
StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
@ -35,7 +35,7 @@ public class StoreEntryListComp extends SimpleComp {
var showIntro = Bindings.createBooleanBinding(
() -> {
var all = StoreViewState.get().getAllConnectionsCategory();
var connections = StoreViewState.get().getAllEntries().getList().stream()
var connections = StoreViewState.get().getAllEntries().stream()
.filter(wrapper -> all.contains(wrapper))
.toList();
return initialCount == connections.size()
@ -45,21 +45,21 @@ public class StoreEntryListComp extends SimpleComp {
.getRoot()
.equals(StoreViewState.get().getAllConnectionsCategory());
},
StoreViewState.get().getAllEntries().getList(),
StoreViewState.get().getAllEntries(),
StoreViewState.get().getActiveCategory());
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
map.put(
createList(),
Bindings.not(Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
map.put(new StoreIntroComp(), showIntro);
map.put(
new StoreNotFoundComp(),
Bindings.and(
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries().getList())),
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
return new MultiContentComp(map).createRegion();
}
}

View file

@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings;
@ -55,24 +56,25 @@ public class StoreEntryListStatusComp extends SimpleComp {
label.textProperty().bind(name);
label.getStyleClass().add("name");
var all = StoreViewState.get().getAllEntries().filtered(
var all = ListBindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> {
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
// Sadly the all binding does not update when the individual visibility of entries changes
// But it is good enough.
var showProvider = storeEntryWrapper.getEntry().getProvider() == null ||
storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
return inRootCategory && showProvider;
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
return StoreViewState.get()
.getActiveCategory()
.getValue()
.getRoot()
.equals(storeRoot);
},
StoreViewState.get().getActiveCategory());
var shownList = all.filtered(
var shownList = ListBindingsHelper.filteredContentBinding(
all,
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList.getList(), all.getList());
var count = new CountComp<>(shownList, all);
var c = count.createRegion();
var topBar = new HBox(

View file

@ -9,13 +9,17 @@ import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.Observable;
import javafx.beans.property.*;
import lombok.Getter;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Getter
public class StoreEntryWrapper {
@ -61,22 +65,12 @@ public class StoreEntryWrapper {
setupListeners();
}
public List<Observable> getUpdateObservables() {
return List.of(category);
}
public void moveTo(DataStoreCategory category) {
ThreadHelper.runAsync(() -> {
DataStorage.get().updateCategory(entry, category);
});
}
public void orderBefore(StoreEntryWrapper other) {
ThreadHelper.runAsync(() -> {
DataStorage.get().orderBefore(getEntry(),other.getEntry());
});
}
public boolean isInStorage() {
return DataStorage.get().getStoreEntries().contains(entry);
}

View file

@ -26,7 +26,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
}
private ContextMenu createMenu() {
if (section.getShownChildren().getList().isEmpty()) {
if (section.getShownChildren().isEmpty()) {
return null;
}
@ -42,7 +42,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
var w = section.getWrapper();
var graphic =
w.getEntry().getProvider().getDisplayIconFileName(w.getEntry().getStore());
if (c.getList().isEmpty()) {
if (c.isEmpty()) {
var item = ContextMenuHelper.item(
PrettyImageHelper.ofFixedSizeSquare(graphic, 16),
w.getName().getValue());
@ -55,7 +55,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
}
var items = new ArrayList<MenuItem>();
for (StoreSection sub : c.getList()) {
for (StoreSection sub : c) {
if (!sub.getWrapper().getValidity().getValue().isUsable()) {
continue;
}

View file

@ -2,16 +2,19 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Value;
import java.util.Comparator;
@ -21,15 +24,15 @@ import java.util.function.Predicate;
public class StoreSection {
StoreEntryWrapper wrapper;
DerivedObservableList<StoreSection> allChildren;
DerivedObservableList<StoreSection> shownChildren;
ObservableList<StoreSection> allChildren;
ObservableList<StoreSection> shownChildren;
int depth;
ObservableBooleanValue showDetails;
public StoreSection(
StoreEntryWrapper wrapper,
DerivedObservableList<StoreSection> allChildren,
DerivedObservableList<StoreSection> shownChildren,
ObservableList<StoreSection> allChildren,
ObservableList<StoreSection> shownChildren,
int depth) {
this.wrapper = wrapper;
this.allChildren = allChildren;
@ -38,10 +41,10 @@ public class StoreSection {
if (wrapper != null) {
this.showDetails = Bindings.createBooleanBinding(
() -> {
return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
return wrapper.getExpanded().get() || allChildren.isEmpty();
},
wrapper.getExpanded(),
allChildren.getList());
allChildren);
} else {
this.showDetails = new SimpleBooleanProperty(true);
}
@ -56,68 +59,51 @@ public class StoreSection {
}
}
private static DerivedObservableList<StoreSection> sorted(
DerivedObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
private static ObservableList<StoreSection> sorted(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
if (category == null) {
return list;
}
var explicitOrderComp = new Comparator<StoreSection>() {
@Override
public int compare(StoreSection o1, StoreSection o2) {
var explicit1 = o1.getWrapper().getEntry().getOrderBefore();
var explicit2 = o2.getWrapper().getEntry().getOrderBefore();
if (explicit1 == null && explicit2 == null) {
return 0;
}
if (explicit1 != null && explicit2 == null) {
return -1;
}
if (explicit2 != null && explicit1 == null) {
return 1;
}
if (explicit1.equals(o2.getWrapper().getEntry().getUuid())) {
return -1;
}
if (explicit2.equals(o1.getWrapper().getEntry().getUuid())) {
return -1;
}
return 0;
}
};
var usableComp = Comparator.<StoreSection>comparingInt(
var c = Comparator.<StoreSection>comparingInt(
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
var comp = explicitOrderComp.thenComparing(usableComp);
var mappedSortMode = BindingsHelper.flatMap(
category,
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
return list.sorted((o1, o2) -> {
return ListBindingsHelper.orderedContentBinding(
list,
(o1, o2) -> {
var current = mappedSortMode.getValue();
if (current != null) {
return comp.thenComparing(current.comparator())
return c.thenComparing(current.comparator())
.compare(current.representative(o1), current.representative(o2));
} else {
return comp.compare(o1, o2);
return c.compare(o1, o2);
}
},
mappedSortMode);
}
public static StoreSection createTopLevel(
DerivedObservableList<StoreEntryWrapper> all,
ObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) {
var topLevel = all.filtered(section -> {
var topLevel = ListBindingsHelper.filteredContentBinding(
all,
section -> {
return DataStorage.get().isRootEntry(section.getEntry());
},
category);
var cached = topLevel.mapped(
var cached = ListBindingsHelper.cachedMappedContentBinding(
topLevel,
topLevel,
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var shown = ordered.filtered(
var shown = ListBindingsHelper.filteredContentBinding(
ordered,
section -> {
var showFilter = filterString == null || section.matchesFilter(filterString.get());
var showFilter = filterString == null || section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null
|| category.getValue() == null
@ -132,17 +118,15 @@ public class StoreSection {
private static StoreSection create(
StoreEntryWrapper e,
int depth,
DerivedObservableList<StoreEntryWrapper> all,
ObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) {
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return new StoreSection(e, new DerivedObservableList<>(
FXCollections.observableArrayList(), true), new DerivedObservableList<>(
FXCollections.observableArrayList(), true), depth);
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
}
var allChildren = all.filtered(other -> {
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
@ -150,35 +134,29 @@ public class StoreSection {
// .orElse(false);
// This check is fast as the children are cached in the storage
var isChildren = DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
var showProvider = other.getEntry().getProvider() == null ||
other.getEntry().getProvider().shouldShow(other);
return isChildren && showProvider;
}, e.getPersistentState(), e.getCache());
var cached = allChildren.mapped(
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
});
var cached = ListBindingsHelper.cachedMappedContentBinding(
allChildren,
allChildren,
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var filtered = ordered.filtered(
var filtered = ListBindingsHelper.filteredContentBinding(
ordered,
section -> {
var showFilter = filterString == null || section.matchesFilter(filterString.get());
var showFilter = filterString == null || section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
// Prevent updates for children on category switching by checking depth
var showCategory = category == null
var sameCategory = category == null
|| category.getValue() == null
|| showInCategory(category.getValue(), section.getWrapper())
|| depth > 0;
|| showInCategory(category.getValue(), section.getWrapper());
// If this entry is already shown as root due to a different category than parent, don't show it
// again here
var notRoot =
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
var showProvider = section.getWrapper().getEntry().getProvider() == null ||
section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper());
return showFilter && matchesSelector && showCategory && notRoot && showProvider;
return showFilter && matchesSelector && sameCategory && notRoot;
},
category,
filterString,
e.getPersistentState(),
e.getCache());
filterString);
return new StoreSection(e, cached, filtered, depth);
}
@ -201,13 +179,13 @@ public class StoreSection {
return false;
}
public boolean matchesFilter(String filter) {
public boolean shouldShow(String filter) {
return anyMatches(storeEntryWrapper -> storeEntryWrapper.shouldShow(filter));
}
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
return c == null
|| c.test(wrapper)
|| allChildren.getList().stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
|| allChildren.stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
}
}

View file

@ -7,8 +7,10 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
@ -40,9 +42,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
private Comp<CompStructure<Button>> createQuickAccessButton() {
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().getList().isEmpty();
return section.getShownChildren().isEmpty();
},
section.getShownChildren().getList());
section.getShownChildren());
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
ThreadHelper.runFailableAsync(() -> {
w.executeDefaultAction();
@ -69,11 +71,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
var expandButton = new IconButtonComp(
Bindings.createStringBinding(
() -> section.getWrapper().getExpanded().get()
&& section.getShownChildren().getList().size() > 0
&& section.getShownChildren().size() > 0
? "mdal-keyboard_arrow_down"
: "mdal-keyboard_arrow_right",
section.getWrapper().getExpanded(),
section.getShownChildren().getList()),
section.getShownChildren()),
() -> {
section.getWrapper().toggleExpanded();
});
@ -87,7 +89,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
return "Expand " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
.styleClass("expand-button")
.maxHeight(100)
.vgrow();
@ -126,12 +128,13 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getShownChildren().filtered(
storeSection -> section.getAllChildren().getList().size() <= 20
var listSections = ListBindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20
|| section.getWrapper().getExpanded().get(),
section.getWrapper().getExpanded(),
section.getAllChildren().getList());
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
section.getAllChildren());
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
})
.minHeight(0)
@ -140,10 +143,10 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
var expanded = Bindings.createBooleanBinding(
() -> {
return section.getWrapper().getExpanded().get()
&& section.getShownChildren().getList().size() > 0;
&& section.getShownChildren().size() > 0;
},
section.getWrapper().getExpanded(),
section.getShownChildren().getList());
section.getShownChildren());
var full = new VerticalComp(List.of(
topEntryList,
Comp.separator().hide(expanded.not()),
@ -152,7 +155,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getShownChildren().getList()).isEqualTo(0)))));
Bindings.size(section.getShownChildren()).isEqualTo(0)))));
return full.styleClass("store-entry-section-comp")
.apply(struc -> {
struc.get().setFillWidth(true);

View file

@ -8,7 +8,9 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -82,7 +84,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
expanded =
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
&& section.getShownChildren().getList().size() > 0);
&& section.getShownChildren().size() > 0);
var button = new IconButtonComp(
Bindings.createStringBinding(
() -> expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right",
@ -99,15 +101,15 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
+ section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
.grow(false, true)
.styleClass("expand-button");
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().getList().isEmpty();
return section.getShownChildren().isEmpty();
},
section.getShownChildren().getList());
section.getShownChildren());
Consumer<StoreEntryWrapper> quickAccessAction = action;
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
.vgrow()
@ -129,12 +131,13 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getWrapper() != null
? section.getShownChildren().filtered(
storeSection -> section.getAllChildren().getList().size() <= 20 || expanded.get(),
? ListBindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
expanded,
section.getAllChildren().getList())
section.getAllChildren())
: section.getShownChildren();
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
return new StoreSectionMiniComp(e, this.augment, this.action, this.condensedStyle);
})
.minHeight(0)
@ -145,7 +148,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
Bindings.size(section.getAllChildren()).isEqualTo(0))));
var vert = new VerticalComp(list);
if (condensedStyle) {

View file

@ -48,7 +48,7 @@ public interface StoreSortMode {
@Override
public StoreSection representative(StoreSection s) {
return Stream.concat(
s.getShownChildren().getList().stream()
s.getShownChildren().stream()
.filter(section -> section.getWrapper()
.getEntry()
.getValidity()
@ -76,7 +76,7 @@ public interface StoreSortMode {
@Override
public StoreSection representative(StoreSection s) {
return Stream.concat(
s.getShownChildren().getList().stream()
s.getShownChildren().stream()
.filter(section -> section.getWrapper()
.getEntry()
.getValidity()

View file

@ -1,17 +1,22 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.beans.value.ObservableIntegerValue;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.util.*;
@ -24,14 +29,12 @@ public class StoreViewState {
private final StringProperty filter = new SimpleStringProperty();
@Getter
private final DerivedObservableList<StoreEntryWrapper> allEntries =
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
private final ObservableList<StoreEntryWrapper> allEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final DerivedObservableList<StoreCategoryWrapper> categories =
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
private final ObservableIntegerValue updateObservable = new SimpleIntegerProperty();
private final ObservableList<StoreCategoryWrapper> categories =
FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
@ -73,8 +76,8 @@ public class StoreViewState {
}
private void updateContent() {
categories.getList().forEach(c -> c.update());
allEntries.getList().forEach(e -> e.update());
categories.forEach(c -> c.update());
allEntries.forEach(e -> e.update());
}
private void initSections() {
@ -83,19 +86,16 @@ public class StoreViewState {
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
} catch (Exception exception) {
currentTopLevelSection =
new StoreSection(null,
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
0);
new StoreSection(null, FXCollections.emptyObservableList(), FXCollections.emptyObservableList(), 0);
ErrorEvent.fromThrowable(exception).handle();
}
}
private void initContent() {
allEntries.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
.map(StoreEntryWrapper::new)
.toList()));
categories.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
categories.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
.map(StoreCategoryWrapper::new)
.toList()));
@ -103,11 +103,11 @@ public class StoreViewState {
DataStorage.get().setSelectedCategory(newValue.getCategory());
});
var selected = AppCache.get("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
activeCategory.setValue(categories.getList().stream()
activeCategory.setValue(categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(selected))
.findFirst()
.orElse(categories.getList().stream()
.orElse(categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.DEFAULT_CATEGORY_UUID))
.findFirst()
@ -119,9 +119,9 @@ public class StoreViewState {
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
synchronized (this) {
var l = new ArrayList<>(allEntries.getList());
allEntries.getList().clear();
allEntries.getList().setAll(l);
var l = new ArrayList<>(allEntries);
allEntries.clear();
allEntries.setAll(l);
}
});
});
@ -129,7 +129,6 @@ public class StoreViewState {
// Watch out for synchronizing all calls to the entries and categories list!
DataStorage.get().addListener(new StorageListener() {
@Override
public void onStoreAdd(DataStoreEntry... entry) {
var l = Arrays.stream(entry)
@ -143,11 +142,11 @@ public class StoreViewState {
}
synchronized (this) {
allEntries.getList().addAll(l);
allEntries.addAll(l);
}
synchronized (this) {
categories.getList().stream()
.filter(storeCategoryWrapper -> allEntries.getList().stream()
categories.stream()
.filter(storeCategoryWrapper -> allEntries.stream()
.anyMatch(storeEntryWrapper -> storeEntryWrapper
.getEntry()
.getCategoryUuid()
@ -164,14 +163,14 @@ public class StoreViewState {
var a = Arrays.stream(entry).collect(Collectors.toSet());
List<StoreEntryWrapper> l;
synchronized (this) {
l = allEntries.getList().stream()
l = allEntries.stream()
.filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry()))
.toList();
}
List<StoreCategoryWrapper> cats;
synchronized (this) {
cats = categories.getList().stream()
.filter(storeCategoryWrapper -> allEntries.getList().stream()
cats = categories.stream()
.filter(storeCategoryWrapper -> allEntries.stream()
.anyMatch(storeEntryWrapper -> storeEntryWrapper
.getEntry()
.getCategoryUuid()
@ -187,7 +186,7 @@ public class StoreViewState {
}
synchronized (this) {
allEntries.getList().removeAll(l);
allEntries.removeAll(l);
}
cats.forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
});
@ -204,7 +203,7 @@ public class StoreViewState {
}
synchronized (this) {
categories.getList().add(l);
categories.add(l);
}
l.update();
});
@ -214,7 +213,7 @@ public class StoreViewState {
public void onCategoryRemove(DataStoreCategory category) {
Optional<StoreCategoryWrapper> found;
synchronized (this) {
found = categories.getList().stream()
found = categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().equals(category))
.findFirst();
@ -230,7 +229,7 @@ public class StoreViewState {
}
synchronized (this) {
categories.getList().remove(found.get());
categories.remove(found.get());
}
var p = found.get().getParent();
if (p != null) {
@ -241,24 +240,7 @@ public class StoreViewState {
});
}
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
StoreSection current = getCurrentTopLevelSection();
while (true) {
var child = current.getAllChildren().getList().stream().filter(section -> section.getWrapper().equals(wrapper)).findFirst();
if (child.isPresent()) {
return Optional.of(current);
}
var traverse = current.getAllChildren().getList().stream().filter(section -> section.anyMatches(w -> w.equals(wrapper))).findFirst();
if (traverse.isPresent()) {
current = traverse.get();
} else {
return Optional.empty();
}
}
}
public DerivedObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
public ObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
@Override
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
@ -295,11 +277,13 @@ public class StoreViewState {
.compareToIgnoreCase(o2.nameProperty().getValue());
}
};
return categories.filtered(cat -> root == null || cat.getRoot().equals(root)).sorted(comparator);
return ListBindingsHelper.filteredContentBinding(
categories, cat -> root == null || cat.getRoot().equals(root))
.sorted(comparator);
}
public StoreCategoryWrapper getAllConnectionsCategory() {
return categories.getList().stream()
return categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
.findFirst()
@ -307,7 +291,7 @@ public class StoreViewState {
}
public StoreCategoryWrapper getAllScriptsCategory() {
return categories.getList().stream()
return categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
.findFirst()
@ -315,14 +299,14 @@ public class StoreViewState {
}
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
return allEntries.getList().stream()
return allEntries.stream()
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
.findFirst()
.orElseThrow();
}
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
return categories.getList().stream()
return categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().equals(entry))
.findFirst()

View file

@ -28,10 +28,6 @@ import java.util.List;
public interface DataStoreProvider {
default boolean shouldShow(StoreEntryWrapper w) {
return true;
}
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
return new SimpleBooleanProperty(false);
}

View file

@ -4,14 +4,17 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.Translatable;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.util.StringConverter;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@ -76,7 +79,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
list.add(null);
}
cb.getItems().setAll(list);
ListBindingsHelper.setContent(cb.getItems(), list);
});
cb.valueProperty().addListener((observable, oldValue, newValue) -> {

View file

@ -11,10 +11,11 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.util.ContextMenuHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
@ -25,6 +26,7 @@ import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;
@ -77,12 +79,13 @@ public class StoreCategoryComp extends SimpleComp {
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
var shownList = ListBindingsHelper.filteredContentBinding(
category.getContainedEntries(),
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString()).getList();
StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
var hover = new SimpleBooleanProperty();
var focus = new SimpleBooleanProperty();

View file

@ -1,228 +0,0 @@
package io.xpipe.app.fxcomps.util;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
@Getter
public class DerivedObservableList<T> {
private final ObservableList<T> list;
private final boolean unique;
public DerivedObservableList(ObservableList<T> list, boolean unique) {
this.list = list;
this.unique = unique;
}
private <V> DerivedObservableList<V> createNewDerived() {
var l = FXCollections.<V>observableArrayList();
BindingsHelper.preserve(l, list);
return new DerivedObservableList<>(l, unique);
}
public void setContent(List<? extends T> newList) {
if (list.equals(newList)) {
return;
}
if (list.size() == 0) {
list.addAll(newList);
return;
}
if (newList.size() == 0) {
list.clear();
return;
}
if (unique) {
setContentUnique(newList);
} else {
setContentNonUnique(newList);
}
}
public void setContentNonUnique(List<? extends T> newList) {
var target = list;
var targetSet = new HashSet<>(target);
var newSet = new HashSet<>(newList);
// Only add missing element
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
var l = new HashSet<>(newSet);
l.removeAll(targetSet);
if (l.size() > 0) {
var found = l.iterator().next();
var index = newList.indexOf(found);
target.add(index, found);
return;
}
}
// Only remove not needed element
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
var l = new HashSet<>(targetSet);
l.removeAll(newSet);
if (l.size() > 0) {
target.remove(l.iterator().next());
return;
}
}
// Other cases are more difficult
target.setAll(newList);
}
private void setContentUnique(List<? extends T> newList) {
var listSet = new HashSet<>(list);
var newSet = new HashSet<>(newList);
// Addition
if (newSet.containsAll(list)) {
var l = new ArrayList<>(newList);
l.removeIf(t -> !listSet.contains(t));
// Reordering occurred
if (!l.equals(list)) {
list.setAll(newList);
return;
}
var start = 0;
for (int end = 0; end <= list.size(); end++) {
var index = end < list.size() ? newList.indexOf(list.get(end)) : newList.size();
for (; start < index; start++) {
list.add(start, newList.get(start));
}
start = index + 1;
}
return;
}
// Removal
if (listSet.containsAll(newList)) {
var l = new ArrayList<>(list);
l.removeIf(t -> !newSet.contains(t));
// Reordering occurred
if (!l.equals(newList)) {
list.setAll(newList);
return;
}
var toRemove = new ArrayList<>(list);
toRemove.removeIf(t -> newSet.contains(t));
list.removeAll(toRemove);
return;
}
// Other cases are more difficult
list.setAll(newList);
}
public <V> DerivedObservableList<V> mapped(Function<T, V> map) {
var l1 = this.<V>createNewDerived();
Runnable runnable = () -> {
l1.setContent(list.stream().map(map).toList());
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {
runnable.run();
});
return l1;
}
public void bindContent(ObservableList<T> other) {
setContent(other);
other.addListener((ListChangeListener<? super T>) c -> {
setContent(other);
});
}
public DerivedObservableList<T> filtered(Predicate<T> predicate) {
return filtered(new SimpleObjectProperty<>(predicate));
}
public DerivedObservableList<T> filtered(Predicate<T> predicate, Observable... observables) {
return filtered(
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(T v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
}
public DerivedObservableList<T> filtered(ObservableValue<Predicate<T>> predicate) {
var d = this.<T>createNewDerived();
Runnable runnable = () -> {
d.setContent(
predicate.getValue() != null
? list.stream().filter(predicate.getValue()).toList()
: list);
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {
runnable.run();
});
predicate.addListener(observable -> {
runnable.run();
});
return d;
}
public DerivedObservableList<T> sorted(Comparator<T> comp, Observable... observables) {
return sorted(Bindings.createObjectBinding(
() -> {
return new Comparator<>() {
@Override
public int compare(T o1, T o2) {
return comp.compare(o1, o2);
}
};
},
observables));
}
public DerivedObservableList<T> sorted(ObservableValue<Comparator<T>> comp) {
var d = this.<T>createNewDerived();
Runnable runnable = () -> {
d.setContent(list.stream().sorted(comp.getValue()).toList());
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {
runnable.run();
});
comp.addListener(observable -> {
d.list.sort(comp.getValue());
});
return d;
}
public DerivedObservableList<T> blockUpdatesIf(ObservableBooleanValue block) {
var d = this.<T>createNewDerived();
Runnable runnable = () -> {
d.setContent(list);
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {
runnable.run();
});
block.addListener(observable -> {
runnable.run();
});
return d;
}
}

View file

@ -0,0 +1,190 @@
package io.xpipe.app.fxcomps.util;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
public class ListBindingsHelper {
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
setContent(l1, l2);
l2.addListener((ListChangeListener<? super T>) c -> {
setContent(l1, l2);
});
}
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
return Bindings.createBooleanBinding(
() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
},
l.toArray(ObservableValue[]::new));
}
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().map(map).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <T, V> ObservableList<T> cachedMappedContentBinding(
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
var cache = new HashMap<V, T>();
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
cache.keySet().removeIf(t -> !all.contains(t));
setContent(
l1,
shown.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
})
.toList());
};
runnable.run();
shown.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
BindingsHelper.preserve(l1, all);
BindingsHelper.preserve(l1, shown);
return l1;
}
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
return orderedContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Comparator<>() {
@Override
public int compare(V o1, V o2) {
return comp.compare(o1, o2);
}
};
},
observables));
}
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
comp.addListener((observable, oldValue, newValue) -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
return filteredContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(V v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(
l1,
predicate.getValue() != null
? l2.stream().filter(predicate.getValue()).toList()
: l2);
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
predicate.addListener((c, o, n) -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
if (target.equals(newList)) {
return;
}
if (target.size() == 0) {
target.setAll(newList);
return;
}
if (newList.size() == 0) {
target.clear();
return;
}
var targetSet = new HashSet<>(target);
var newSet = new HashSet<>(newList);
// Only add missing element
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
var l = new HashSet<>(newSet);
l.removeAll(targetSet);
if (l.size() > 0) {
var found = l.iterator().next();
var index = newList.indexOf(found);
target.add(index, found);
return;
}
}
// Only remove not needed element
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
var l = new HashSet<>(targetSet);
l.removeAll(newSet);
if (l.size() > 0) {
target.remove(l.iterator().next());
return;
}
}
// Other cases are more difficult
target.setAll(newList);
}
}

View file

@ -329,21 +329,17 @@ public abstract class DataStorage {
}
var children = getDeepStoreChildren(entry);
var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
entry.setCategoryUuid(newCategory.getUuid());
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
saveAsync();
}
public void orderBefore(DataStoreEntry entry, DataStoreEntry reference) {
var children = getDeepStoreChildren(entry);
var arr = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
listeners.forEach(storageListener -> storageListener.onStoreRemove(arr));
entry.setOrderBefore(reference != null ? reference.getUuid() : null);
listeners.forEach(storageListener -> storageListener.onStoreAdd(arr));
}
public boolean refreshChildren(DataStoreEntry e) {
if (!(e.getStore() instanceof FixedHierarchyStore)) {
return false;
@ -443,8 +439,8 @@ public abstract class DataStorage {
pair.getKey().setStoreInternal(merged, false);
}
var s = pair.getKey().getStorePersistentState();
var mergedState = s.mergeCopy(pair.getValue().get().getStorePersistentState());
var mergedState = pair.getKey().getStorePersistentState().deepCopy();
mergedState.merge(pair.getValue().get().getStorePersistentState());
pair.getKey().setStorePersistentState(mergedState);
}
}
@ -792,7 +788,9 @@ public abstract class DataStorage {
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
return storeEntriesSet.stream()
.filter(n -> n.getStore() == store || (!identityOnly && (n.getStore() != null
.filter(n -> n.getStore() == store
|| (!identityOnly
&& (n.getStore() != null
&& Objects.equals(
store.getClass(), n.getStore().getClass())
&& store.equals(n.getStore()))))

View file

@ -72,9 +72,6 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
String notes;
@NonFinal
UUID orderBefore;
private DataStoreEntry(
Path directory,
UUID uuid,
@ -89,8 +86,7 @@ public class DataStoreEntry extends StorageElement {
JsonNode storePersistentState,
boolean expanded,
DataStoreColor color,
String notes, UUID orderBefore
) {
String notes) {
super(directory, uuid, name, lastUsed, lastModified, dirty);
this.categoryUuid = categoryUuid;
this.store = DataStorageParser.storeFromNode(storeNode);
@ -99,7 +95,6 @@ public class DataStoreEntry extends StorageElement {
this.configuration = configuration;
this.expanded = expanded;
this.color = color;
this.orderBefore = orderBefore;
this.provider = store != null
? DataStoreProviders.byStoreClass(store.getClass()).orElse(null)
: null;
@ -114,12 +109,10 @@ public class DataStoreEntry extends StorageElement {
String name,
Instant lastUsed,
Instant lastModified,
DataStore store, UUID orderBefore
) {
DataStore store) {
super(directory, uuid, name, lastUsed, lastModified, false);
this.categoryUuid = categoryUuid;
this.store = store;
this.orderBefore = orderBefore;
this.storeNode = null;
this.validity = Validity.INCOMPLETE;
this.configuration = Configuration.defaultConfiguration();
@ -137,8 +130,7 @@ public class DataStoreEntry extends StorageElement {
UUID.randomUUID().toString(),
Instant.now(),
Instant.now(),
store,
null);
store);
}
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
@ -167,7 +159,6 @@ public class DataStoreEntry extends StorageElement {
null,
false,
null,
null,
null);
return entry;
}
@ -185,8 +176,7 @@ public class DataStoreEntry extends StorageElement {
JsonNode storePersistentState,
boolean expanded,
DataStoreColor color,
String notes,
UUID orderBeforeEntry) {
String notes) {
return new DataStoreEntry(
directory,
uuid,
@ -201,8 +191,7 @@ public class DataStoreEntry extends StorageElement {
storePersistentState,
expanded,
color,
notes,
orderBeforeEntry);
notes);
}
public static Optional<DataStoreEntry> fromDirectory(Path dir) throws Exception {
@ -237,15 +226,6 @@ public class DataStoreEntry extends StorageElement {
.map(jsonNode -> jsonNode.textValue())
.map(Instant::parse)
.orElse(Instant.EPOCH);
var order = Optional.ofNullable(stateJson.get("orderBefore"))
.map(node -> {
try {
return mapper.treeToValue(node, UUID.class);
} catch (JsonProcessingException e) {
return null;
}
})
.orElse(null);
var configuration = Optional.ofNullable(json.get("configuration"))
.map(node -> {
try {
@ -295,19 +275,10 @@ public class DataStoreEntry extends StorageElement {
persistentState,
expanded,
color,
notes,
order
notes
));
}
public void setOrderBefore(UUID uuid) {
var changed = !Objects.equals(orderBefore, uuid);
this.orderBefore = uuid;
if (changed) {
notifyUpdate(false, true);
}
}
@Override
public int hashCode() {
return getUuid().hashCode();
@ -359,7 +330,7 @@ public class DataStoreEntry extends StorageElement {
storePersistentStateNode = JacksonMapper.getDefault().valueToTree(storePersistentState);
}
}
return (T) storePersistentState;
return (T) sds.getStateClass().cast(storePersistentState);
}
public void setStorePersistentState(DataStoreState value) {
@ -408,7 +379,6 @@ public class DataStoreEntry extends StorageElement {
stateObj.set("persistentState", storePersistentStateNode);
obj.set("configuration", mapper.valueToTree(configuration));
stateObj.put("expanded", expanded);
stateObj.put("orderBefore", orderBefore != null ? orderBefore.toString() : null);
var entryString = mapper.writeValueAsString(obj);
var stateString = mapper.writeValueAsString(stateObj);

View file

@ -40,7 +40,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
value.setValue(newValue);
}
});
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root).getList());
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root));
box.setValue(value.getValue());
box.valueProperty().addListener((observable, oldValue, newValue) -> {
value.setValue(newValue);

View file

@ -58,13 +58,12 @@ public interface ShellControl extends ProcessControl {
default <T extends ShellStoreState> ShellControl withShellStateInit(StatefulDataStore<T> store) {
return onInit(shellControl -> {
var s = store.getState().toBuilder()
.osType(shellControl.getOsType())
.shellDialect(shellControl.getOriginalShellDialect())
.running(true)
.osName(shellControl.getOsName())
.build();
store.setState(s.asNeeded());
var s = store.getState();
s.setOsType(shellControl.getOsType());
s.setShellDialect(shellControl.getOriginalShellDialect());
s.setRunning(true);
s.setOsName(shellControl.getOsName());
store.setState(s);
});
}
@ -75,8 +74,9 @@ public interface ShellControl extends ProcessControl {
return;
}
var s = store.getState().toBuilder().running(false).build();
store.setState(s.asNeeded());
var s = store.getState();
s.setRunning(false);
store.setState(s);
});
}

View file

@ -1,24 +0,0 @@
package io.xpipe.core.process;
import io.xpipe.core.store.DataStoreState;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@Value
@EqualsAndHashCode(callSuper=true)
@SuperBuilder(toBuilder = true)
@Jacksonized
public class ShellNameStoreState extends ShellStoreState {
String shellName;
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
var n = (ShellNameStoreState) newer;
var b = toBuilder();
mergeBuilder(n,b);
return b.shellName(useNewer(shellName, n.shellName)).build();
}
}

View file

@ -1,18 +1,19 @@
package io.xpipe.core.process;
import io.xpipe.core.store.DataStoreState;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Setter
@Getter
@EqualsAndHashCode(callSuper=true)
@SuperBuilder(toBuilder = true)
@Jacksonized
@SuperBuilder
public class ShellStoreState extends DataStoreState implements OsNameState {
OsType.Any osType;
@ -25,17 +26,11 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
}
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
public void merge(DataStoreState newer) {
var shellStoreState = (ShellStoreState) newer;
var b = toBuilder();
mergeBuilder(shellStoreState, b);
return b.build();
}
protected void mergeBuilder(ShellStoreState shellStoreState, ShellStoreStateBuilder<?,?> b) {
b.osType(useNewer(osType, shellStoreState.getOsType()))
.osName(useNewer(osName, shellStoreState.getOsName()))
.shellDialect(useNewer(shellDialect, shellStoreState.getShellDialect()))
.running(useNewer(running, shellStoreState.getRunning()));
osType = useNewer(osType, shellStoreState.getOsType());
osName = useNewer(osName, shellStoreState.getOsName());
shellDialect = useNewer(shellDialect, shellStoreState.getShellDialect());
running = useNewer(running, shellStoreState.getRunning());
}
}

View file

@ -1,22 +1,49 @@
package io.xpipe.core.store;
import io.xpipe.core.util.JacksonMapper;
import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder;
@SuperBuilder(toBuilder = true)
@SuperBuilder
public abstract class DataStoreState {
public DataStoreState() {}
@SuppressWarnings("unchecked")
public <DS extends DataStoreState> DS asNeeded() {
return (DS) this;
}
protected static <T> T useNewer(T older, T newer) {
return newer != null ? newer : older;
}
public DataStoreState mergeCopy(DataStoreState newer) {
return this;
public abstract void merge(DataStoreState newer);
@SneakyThrows
public DataStoreState deepCopy() {
return JacksonMapper.getDefault().treeToValue(JacksonMapper.getDefault().valueToTree(this), getClass());
}
@Override
public final int hashCode() {
var tree = JacksonMapper.getDefault().valueToTree(this);
return tree.hashCode();
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass() != o.getClass()) {
return false;
}
var tree = JacksonMapper.getDefault().valueToTree(this);
var otherTree = JacksonMapper.getDefault().valueToTree(o);
return tree.equals(otherTree);
}
@SneakyThrows
public String toString() {
var tree = JacksonMapper.getDefault().valueToTree(this);
return tree.toPrettyString();
}
}

View file

@ -1,9 +1,11 @@
package io.xpipe.core.store;
import io.xpipe.core.util.DataStateProvider;
import lombok.SneakyThrows;
import java.util.Arrays;
import java.util.function.Supplier;
public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
@ -17,14 +19,20 @@ public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
return getStateClass().cast(m.invoke(b));
}
@SuppressWarnings("unchecked")
default T getState() {
return DataStateProvider.get().getState(this, this::createDefaultState);
return (T)
DataStateProvider.get().getState(this, this::createDefaultState).deepCopy();
}
default void setState(T val) {
DataStateProvider.get().setState(this, val);
}
default T getState(Supplier<T> def) {
return DataStateProvider.get().getState(this, def);
}
@SneakyThrows
@SuppressWarnings("unchecked")
default Class<T> getStateClass() {

View file

@ -8,6 +8,8 @@ The file transfer mechanism when editing files had some flaws, which under rare
The entire transfer implementation has been rewritten to iron out these issues and increase reliability. Other file browser actions have also been made more reliable.
There seems to be another separate issue with a PowerShell bug when connecting to a Windows system, causing file transfers of more than around 1MB to stall. For now, xpipe can fall back to pwsh if it is installed to work around this issue.
## Git vault improvements
The conflict resolution has been improved
@ -20,6 +22,13 @@ The conflict resolution has been improved
- You can now add simple RDP connections without a file
- Fix VMware Player/Workstation and MSYS2 not being detected on Windows. Now simply searching for connections should add them automatically if they are installed
- The file browser sidebar now only contains connections that can be opened in it, reducing the amount of connection shown
- Clarify error message for RealVNC servers, highlighting that RealVNC uses a proprietary protocol spec that can't be supported by third-party VNC clients like xpipe
- Fix Linux builds containing unnecessary debug symbols
- Fix AUR package also installing a debug package
- Fix application restart not working properly on macOS
- Fix possibility of selecting own children connections as hosts, causing a stack overflow. Please don't try to create cycles in your connection graphs
- Fix vault secrets not correctly updating unless restarted when changing vault passphrase
- Fix connection launcher desktop shortcuts and URLs not properly executing if xpipe is not running
- Fix SSH command failing on macOS with homebrew openssh package installed
- Fix SSH connections not opening the correct shell environment on Windows when username contained spaces due to an OpenSSH bug
- Fix newly added connections not having the correct order

View file

@ -31,13 +31,15 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
var def = StoreToggleComp.<ScriptGroupStore>simpleToggle(
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
var state = s.getState().toBuilder().isDefault(aBoolean).build();
var state = s.getState();
state.setDefault(aBoolean);
s.setState(state);
});
var bring = StoreToggleComp.<ScriptGroupStore>simpleToggle(
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
var state = s.getState().toBuilder().bringToShell(aBoolean).build();
var state = s.getState();
state.setBringToShell(aBoolean);
s.setState(state);
});

View file

@ -6,14 +6,15 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.ShellTemp;
import io.xpipe.app.util.Validators;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellInitCommand;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreState;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.util.JacksonizedValue;
import lombok.*;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@ -221,12 +222,20 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
@Value
@EqualsAndHashCode(callSuper=true)
@SuperBuilder(toBuilder = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Setter
@Getter
@SuperBuilder
@Jacksonized
public static class State extends DataStoreState {
boolean isDefault;
boolean bringToShell;
@Override
public void merge(DataStoreState newer) {
var s = (State) newer;
isDefault = s.isDefault;
bringToShell = s.bringToShell;
}
}
}

View file

@ -56,13 +56,15 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
var def = StoreToggleComp.<SimpleScriptStore>simpleToggle(
"base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
var state = s.getState().toBuilder().isDefault(aBoolean).build();
var state = s.getState();
state.setDefault(aBoolean);
s.setState(state);
});
var bring = StoreToggleComp.<SimpleScriptStore>simpleToggle(
"base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
var state = s.getState().toBuilder().bringToShell(aBoolean).build();
var state = s.getState();
state.setBringToShell(aBoolean);
s.setState(state);
});

View file

@ -454,5 +454,3 @@ history=Browsing history
skipAll=Skip all
notes=Notes
addNotes=Add notes
#context: verb
order=Order ...

View file

@ -1 +1 @@
9.4-3
9.4-4