Rework list bindings

This commit is contained in:
crschnick 2024-05-29 14:25:45 +00:00
parent e14b38b31f
commit ba7c83a1e8
24 changed files with 373 additions and 366 deletions

View file

@ -6,18 +6,16 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.ListBindingsHelper; import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.List; import java.util.List;
@ -70,9 +68,8 @@ public class BrowserOverviewComp extends SimpleComp {
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false); var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview); var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
var recent = ListBindingsHelper.mappedContentBinding( var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
model.getSavedState().getRecentDirectories(), s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
var recentOverview = new BrowserFileOverviewComp(model, recent, true); var recentOverview = new BrowserFileOverviewComp(model, recent, true);
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview); 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.SimpleComp;
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment; import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
import io.xpipe.app.fxcomps.impl.*; import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.ListBindingsHelper; import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@ -47,7 +47,7 @@ public class BrowserTransferComp extends SimpleComp {
var backgroundStack = var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry()); var binding = new DerivedObservableList<>(syncItems, true).mapped(item -> item.getBrowserEntry()).getList();
var list = new BrowserSelectionListComp( var list = new BrowserSelectionListComp(
binding, binding,
entry -> Bindings.createStringBinding( entry -> Bindings.createStringBinding(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -410,6 +410,7 @@ public abstract class StoreEntryComp extends SimpleComp {
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline")); var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
StoreViewState.get() StoreViewState.get()
.getSortedCategories(wrapper.getCategory().getValue().getRoot()) .getSortedCategories(wrapper.getCategory().getValue().getRoot())
.getList()
.forEach(storeCategoryWrapper -> { .forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem(); MenuItem m = new MenuItem();
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue()); m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
@ -447,7 +448,7 @@ public abstract class StoreEntryComp extends SimpleComp {
order.getItems().add(stick); order.getItems().add(stick);
order.getItems().add(new SeparatorMenuItem()); order.getItems().add(new SeparatorMenuItem());
var section = StoreViewState.get().getParentSectionForWrapper(wrapper); var section = StoreViewState.get().getParentSectionForWrapper(wrapper);
section.get().getAllChildren().forEach(other -> { section.get().getAllChildren().getList().forEach(other -> {
var ow = other.getWrapper(); var ow = other.getWrapper();
var op = ow.getEntry().getProvider(); var op = ow.getEntry().getProvider();
MenuItem m = new MenuItem(ow.getName().getValue(), MenuItem m = new MenuItem(ow.getName().getValue(),

View file

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

View file

@ -8,7 +8,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp; import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp; import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@ -56,8 +55,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
label.textProperty().bind(name); label.textProperty().bind(name);
label.getStyleClass().add("name"); label.getStyleClass().add("name");
var all = ListBindingsHelper.filteredContentBinding( var all = StoreViewState.get().getAllEntries().filtered(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> { storeEntryWrapper -> {
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot(); var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory); var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
@ -68,14 +66,13 @@ public class StoreEntryListStatusComp extends SimpleComp {
return inRootCategory && showProvider; return inRootCategory && showProvider;
}, },
StoreViewState.get().getActiveCategory()); StoreViewState.get().getActiveCategory());
var shownList = ListBindingsHelper.filteredContentBinding( var shownList = all.filtered(
all,
storeEntryWrapper -> { storeEntryWrapper -> {
return storeEntryWrapper.shouldShow( return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue()); StoreViewState.get().getFilterString().getValue());
}, },
StoreViewState.get().getFilterString()); StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, all); var count = new CountComp<>(shownList.getList(), all.getList());
var c = count.createRegion(); var c = count.createRegion();
var topBar = new HBox( var topBar = new HBox(

View file

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

View file

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

View file

@ -2,19 +2,16 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper; import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Value; import lombok.Value;
import java.util.Comparator; import java.util.Comparator;
@ -24,15 +21,15 @@ import java.util.function.Predicate;
public class StoreSection { public class StoreSection {
StoreEntryWrapper wrapper; StoreEntryWrapper wrapper;
ObservableList<StoreSection> allChildren; DerivedObservableList<StoreSection> allChildren;
ObservableList<StoreSection> shownChildren; DerivedObservableList<StoreSection> shownChildren;
int depth; int depth;
ObservableBooleanValue showDetails; ObservableBooleanValue showDetails;
public StoreSection( public StoreSection(
StoreEntryWrapper wrapper, StoreEntryWrapper wrapper,
ObservableList<StoreSection> allChildren, DerivedObservableList<StoreSection> allChildren,
ObservableList<StoreSection> shownChildren, DerivedObservableList<StoreSection> shownChildren,
int depth) { int depth) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.allChildren = allChildren; this.allChildren = allChildren;
@ -41,10 +38,10 @@ public class StoreSection {
if (wrapper != null) { if (wrapper != null) {
this.showDetails = Bindings.createBooleanBinding( this.showDetails = Bindings.createBooleanBinding(
() -> { () -> {
return wrapper.getExpanded().get() || allChildren.isEmpty(); return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
}, },
wrapper.getExpanded(), wrapper.getExpanded(),
allChildren); allChildren.getList());
} else { } else {
this.showDetails = new SimpleBooleanProperty(true); this.showDetails = new SimpleBooleanProperty(true);
} }
@ -59,8 +56,8 @@ public class StoreSection {
} }
} }
private static ObservableList<StoreSection> sorted( private static DerivedObservableList<StoreSection> sorted(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) { DerivedObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
if (category == null) { if (category == null) {
return list; return list;
} }
@ -94,9 +91,7 @@ public class StoreSection {
var mappedSortMode = BindingsHelper.flatMap( var mappedSortMode = BindingsHelper.flatMap(
category, category,
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null); storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
return ListBindingsHelper.orderedContentBinding( return list.sorted((o1, o2) -> {
list,
(o1, o2) -> {
var current = mappedSortMode.getValue(); var current = mappedSortMode.getValue();
if (current != null) { if (current != null) {
return comp.thenComparing(current.comparator()) return comp.thenComparing(current.comparator())
@ -109,23 +104,18 @@ public class StoreSection {
} }
public static StoreSection createTopLevel( public static StoreSection createTopLevel(
ObservableList<StoreEntryWrapper> all, DerivedObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter, Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString, ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) { ObservableValue<StoreCategoryWrapper> category) {
var topLevel = ListBindingsHelper.filteredContentBinding( var topLevel = all.filtered(section -> {
all,
section -> {
return DataStorage.get().isRootEntry(section.getEntry()); return DataStorage.get().isRootEntry(section.getEntry());
}, },
category); category);
var cached = ListBindingsHelper.cachedMappedContentBinding( var cached = topLevel.mapped(
topLevel,
topLevel,
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category)); storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category); var ordered = sorted(cached, category);
var shown = ListBindingsHelper.filteredContentBinding( var shown = ordered.filtered(
ordered,
section -> { section -> {
var showFilter = filterString == null || section.matchesFilter(filterString.get()); var showFilter = filterString == null || section.matchesFilter(filterString.get());
var matchesSelector = section.anyMatches(entryFilter); var matchesSelector = section.anyMatches(entryFilter);
@ -142,15 +132,17 @@ public class StoreSection {
private static StoreSection create( private static StoreSection create(
StoreEntryWrapper e, StoreEntryWrapper e,
int depth, int depth,
ObservableList<StoreEntryWrapper> all, DerivedObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter, Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString, ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) { ObservableValue<StoreCategoryWrapper> category) {
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth); return new StoreSection(e, new DerivedObservableList<>(
FXCollections.observableArrayList(), true), new DerivedObservableList<>(
FXCollections.observableArrayList(), true), depth);
} }
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> { var allChildren = all.filtered(other -> {
// Legacy implementation that does not use children caches. Use for testing // Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get() // if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry()) // .getDisplayParent(other.getEntry())
@ -163,26 +155,25 @@ public class StoreSection {
other.getEntry().getProvider().shouldShow(other); other.getEntry().getProvider().shouldShow(other);
return isChildren && showProvider; return isChildren && showProvider;
}, e.getPersistentState(), e.getCache()); }, e.getPersistentState(), e.getCache());
var cached = ListBindingsHelper.cachedMappedContentBinding( var cached = allChildren.mapped(
allChildren,
allChildren,
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category)); entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category); var ordered = sorted(cached, category);
var filtered = ListBindingsHelper.filteredContentBinding( var filtered = ordered.filtered(
ordered,
section -> { section -> {
var showFilter = filterString == null || section.matchesFilter(filterString.get()); var showFilter = filterString == null || section.matchesFilter(filterString.get());
var matchesSelector = section.anyMatches(entryFilter); var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null // Prevent updates for children on category switching by checking depth
var showCategory = category == null
|| category.getValue() == null || category.getValue() == null
|| showInCategory(category.getValue(), section.getWrapper()); || showInCategory(category.getValue(), section.getWrapper())
|| depth > 0;
// If this entry is already shown as root due to a different category than parent, don't show it // If this entry is already shown as root due to a different category than parent, don't show it
// again here // again here
var notRoot = var notRoot =
!DataStorage.get().isRootEntry(section.getWrapper().getEntry()); !DataStorage.get().isRootEntry(section.getWrapper().getEntry());
var showProvider = section.getWrapper().getEntry().getProvider() == null || var showProvider = section.getWrapper().getEntry().getProvider() == null ||
section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper()); section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper());
return showFilter && matchesSelector && sameCategory && notRoot && showProvider; return showFilter && matchesSelector && showCategory && notRoot && showProvider;
}, },
category, category,
filterString, filterString,
@ -217,6 +208,6 @@ public class StoreSection {
public boolean anyMatches(Predicate<StoreEntryWrapper> c) { public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
return c == null return c == null
|| c.test(wrapper) || c.test(wrapper)
|| allChildren.stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c)); || allChildren.getList().stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,228 @@
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

@ -1,190 +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.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,13 +329,8 @@ public abstract class DataStorage {
} }
var children = getDeepStoreChildren(entry); var children = getDeepStoreChildren(entry);
var arr = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
listeners.forEach(storageListener -> storageListener.onStoreRemove(arr));
entry.setCategoryUuid(newCategory.getUuid()); entry.setCategoryUuid(newCategory.getUuid());
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid())); children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
listeners.forEach(storageListener -> storageListener.onStoreAdd(arr));
saveAsync(); saveAsync();
} }

View file

@ -408,7 +408,7 @@ public class DataStoreEntry extends StorageElement {
stateObj.set("persistentState", storePersistentStateNode); stateObj.set("persistentState", storePersistentStateNode);
obj.set("configuration", mapper.valueToTree(configuration)); obj.set("configuration", mapper.valueToTree(configuration));
stateObj.put("expanded", expanded); stateObj.put("expanded", expanded);
stateObj.put("orderBefore", orderBefore.toString()); stateObj.put("orderBefore", orderBefore != null ? orderBefore.toString() : null);
var entryString = mapper.writeValueAsString(obj); var entryString = mapper.writeValueAsString(obj);
var stateString = mapper.writeValueAsString(stateObj); var stateString = mapper.writeValueAsString(stateObj);

View file

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