mirror of
https://github.com/xpipe-io/xpipe.git
synced 2025-04-19 10:43:39 +00:00
Rework list bindings
This commit is contained in:
parent
e14b38b31f
commit
ba7c83a1e8
24 changed files with 373 additions and 366 deletions
|
@ -6,18 +6,16 @@ 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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
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;
|
||||
|
@ -70,9 +68,8 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
||||
|
||||
var recent = ListBindingsHelper.mappedContentBinding(
|
||||
model.getSavedState().getRecentDirectories(),
|
||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
|
||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true).mapped(
|
||||
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory())).getList();
|
||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
||||
|
||||
|
|
|
@ -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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
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 = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry());
|
||||
var binding = new DerivedObservableList<>(syncItems, true).mapped(item -> item.getBrowserEntry()).getList();
|
||||
var list = new BrowserSelectionListComp(
|
||||
binding,
|
||||
entry -> Bindings.createStringBinding(
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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;
|
||||
|
@ -13,10 +15,9 @@ 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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
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;
|
||||
|
@ -30,9 +31,6 @@ 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 {
|
||||
|
@ -67,7 +65,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
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());
|
||||
if (entry.isEmpty()) {
|
||||
return false;
|
||||
|
@ -78,7 +76,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}).getList();
|
||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||
|
||||
var headerBinding = BindingsHelper.flatMap(empty, b -> {
|
||||
|
|
|
@ -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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
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.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;
|
||||
|
||||
|
@ -40,7 +38,8 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFil
|
|||
return;
|
||||
}
|
||||
|
||||
ListBindingsHelper.bindContent(fileSelection, newValue.getFileList().getSelection());
|
||||
var l = new DerivedObservableList<>(fileSelection, true);
|
||||
l.bindContent(newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ 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;
|
||||
|
@ -38,10 +39,13 @@ 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(ListBindingsHelper.anyMatch(cm.getItems().stream()
|
||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||
.toList()));
|
||||
.bind(Bindings.createBooleanBinding(() -> {
|
||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
||||
}, l.toArray(ObservableValue[]::new)));
|
||||
|
||||
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
||||
button.fontProperty().subscribe(c -> {
|
||||
|
|
|
@ -3,9 +3,8 @@ 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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
@ -89,7 +88,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
}
|
||||
|
||||
if (!listView.getChildren().equals(newShown)) {
|
||||
ListBindingsHelper.setContent(listView.getChildren(), newShown);
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public class StoreCategoryWrapper {
|
|||
}
|
||||
|
||||
public StoreCategoryWrapper getParent() {
|
||||
return StoreViewState.get().getCategories().stream()
|
||||
return StoreViewState.get().getCategories().getList().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().stream()
|
||||
containedEntries.setAll(StoreViewState.get().getAllEntries().getList().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().stream()
|
||||
children.setAll(StoreViewState.get().getCategories().getList().stream()
|
||||
.filter(storeCategoryWrapper -> getCategory()
|
||||
.getUuid()
|
||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||
|
|
|
@ -410,6 +410,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
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());
|
||||
|
@ -447,7 +448,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
order.getItems().add(stick);
|
||||
order.getItems().add(new SeparatorMenuItem());
|
||||
var section = StoreViewState.get().getParentSectionForWrapper(wrapper);
|
||||
section.get().getAllChildren().forEach(other -> {
|
||||
section.get().getAllChildren().getList().forEach(other -> {
|
||||
var ow = other.getWrapper();
|
||||
var op = ow.getEntry().getProvider();
|
||||
MenuItem m = new MenuItem(ow.getName().getValue(),
|
||||
|
|
|
@ -18,8 +18,8 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
|
||||
private Comp<?> createList() {
|
||||
var content = new ListBoxViewComp<>(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren().getList(),
|
||||
(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().stream()
|
||||
var connections = StoreViewState.get().getAllEntries().getList().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(),
|
||||
StoreViewState.get().getAllEntries().getList(),
|
||||
StoreViewState.get().getActiveCategory());
|
||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(
|
||||
createList(),
|
||||
Bindings.not(Bindings.isEmpty(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
||||
|
||||
map.put(new StoreIntroComp(), showIntro);
|
||||
map.put(
|
||||
new StoreNotFoundComp(),
|
||||
Bindings.and(
|
||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
|
||||
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries().getList())),
|
||||
Bindings.isEmpty(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren().getList())));
|
||||
return new MultiContentComp(map).createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ 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;
|
||||
|
@ -56,8 +55,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
label.textProperty().bind(name);
|
||||
label.getStyleClass().add("name");
|
||||
|
||||
var all = ListBindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
var all = StoreViewState.get().getAllEntries().filtered(
|
||||
storeEntryWrapper -> {
|
||||
var rootCategory = storeEntryWrapper.getCategory().getValue().getRoot();
|
||||
var inRootCategory = StoreViewState.get().getActiveCategory().getValue().getRoot().equals(rootCategory);
|
||||
|
@ -68,14 +66,13 @@ public class StoreEntryListStatusComp extends SimpleComp {
|
|||
return inRootCategory && showProvider;
|
||||
},
|
||||
StoreViewState.get().getActiveCategory());
|
||||
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||
all,
|
||||
var shownList = all.filtered(
|
||||
storeEntryWrapper -> {
|
||||
return storeEntryWrapper.shouldShow(
|
||||
StoreViewState.get().getFilterString().getValue());
|
||||
},
|
||||
StoreViewState.get().getFilterString());
|
||||
var count = new CountComp<>(shownList, all);
|
||||
var count = new CountComp<>(shownList.getList(), all.getList());
|
||||
|
||||
var c = count.createRegion();
|
||||
var topBar = new HBox(
|
||||
|
|
|
@ -9,17 +9,13 @@ 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.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
@Getter
|
||||
public class StoreEntryWrapper {
|
||||
|
@ -65,6 +61,10 @@ public class StoreEntryWrapper {
|
|||
setupListeners();
|
||||
}
|
||||
|
||||
public List<Observable> getUpdateObservables() {
|
||||
return List.of(category);
|
||||
}
|
||||
|
||||
public void moveTo(DataStoreCategory category) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
DataStorage.get().updateCategory(entry, category);
|
||||
|
|
|
@ -26,7 +26,7 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||
}
|
||||
|
||||
private ContextMenu createMenu() {
|
||||
if (section.getShownChildren().isEmpty()) {
|
||||
if (section.getShownChildren().getList().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.isEmpty()) {
|
||||
if (c.getList().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) {
|
||||
for (StoreSection sub : c.getList()) {
|
||||
if (!sub.getWrapper().getValidity().getValue().isUsable()) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -2,19 +2,16 @@ 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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
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;
|
||||
|
@ -24,15 +21,15 @@ import java.util.function.Predicate;
|
|||
public class StoreSection {
|
||||
|
||||
StoreEntryWrapper wrapper;
|
||||
ObservableList<StoreSection> allChildren;
|
||||
ObservableList<StoreSection> shownChildren;
|
||||
DerivedObservableList<StoreSection> allChildren;
|
||||
DerivedObservableList<StoreSection> shownChildren;
|
||||
int depth;
|
||||
ObservableBooleanValue showDetails;
|
||||
|
||||
public StoreSection(
|
||||
StoreEntryWrapper wrapper,
|
||||
ObservableList<StoreSection> allChildren,
|
||||
ObservableList<StoreSection> shownChildren,
|
||||
DerivedObservableList<StoreSection> allChildren,
|
||||
DerivedObservableList<StoreSection> shownChildren,
|
||||
int depth) {
|
||||
this.wrapper = wrapper;
|
||||
this.allChildren = allChildren;
|
||||
|
@ -41,10 +38,10 @@ public class StoreSection {
|
|||
if (wrapper != null) {
|
||||
this.showDetails = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return wrapper.getExpanded().get() || allChildren.isEmpty();
|
||||
return wrapper.getExpanded().get() || allChildren.getList().isEmpty();
|
||||
},
|
||||
wrapper.getExpanded(),
|
||||
allChildren);
|
||||
allChildren.getList());
|
||||
} else {
|
||||
this.showDetails = new SimpleBooleanProperty(true);
|
||||
}
|
||||
|
@ -59,8 +56,8 @@ public class StoreSection {
|
|||
}
|
||||
}
|
||||
|
||||
private static ObservableList<StoreSection> sorted(
|
||||
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
private static DerivedObservableList<StoreSection> sorted(
|
||||
DerivedObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
if (category == null) {
|
||||
return list;
|
||||
}
|
||||
|
@ -94,9 +91,7 @@ public class StoreSection {
|
|||
var mappedSortMode = BindingsHelper.flatMap(
|
||||
category,
|
||||
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||
return ListBindingsHelper.orderedContentBinding(
|
||||
list,
|
||||
(o1, o2) -> {
|
||||
return list.sorted((o1, o2) -> {
|
||||
var current = mappedSortMode.getValue();
|
||||
if (current != null) {
|
||||
return comp.thenComparing(current.comparator())
|
||||
|
@ -109,23 +104,18 @@ public class StoreSection {
|
|||
}
|
||||
|
||||
public static StoreSection createTopLevel(
|
||||
ObservableList<StoreEntryWrapper> all,
|
||||
DerivedObservableList<StoreEntryWrapper> all,
|
||||
Predicate<StoreEntryWrapper> entryFilter,
|
||||
ObservableStringValue filterString,
|
||||
ObservableValue<StoreCategoryWrapper> category) {
|
||||
var topLevel = ListBindingsHelper.filteredContentBinding(
|
||||
all,
|
||||
section -> {
|
||||
var topLevel = all.filtered(section -> {
|
||||
return DataStorage.get().isRootEntry(section.getEntry());
|
||||
},
|
||||
category);
|
||||
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||
topLevel,
|
||||
topLevel,
|
||||
var cached = topLevel.mapped(
|
||||
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
|
||||
var ordered = sorted(cached, category);
|
||||
var shown = ListBindingsHelper.filteredContentBinding(
|
||||
ordered,
|
||||
var shown = ordered.filtered(
|
||||
section -> {
|
||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
||||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
|
@ -142,15 +132,17 @@ public class StoreSection {
|
|||
private static StoreSection create(
|
||||
StoreEntryWrapper e,
|
||||
int depth,
|
||||
ObservableList<StoreEntryWrapper> all,
|
||||
DerivedObservableList<StoreEntryWrapper> all,
|
||||
Predicate<StoreEntryWrapper> entryFilter,
|
||||
ObservableStringValue filterString,
|
||||
ObservableValue<StoreCategoryWrapper> category) {
|
||||
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
|
||||
// if (true) return DataStorage.get()
|
||||
// .getDisplayParent(other.getEntry())
|
||||
|
@ -163,26 +155,25 @@ public class StoreSection {
|
|||
other.getEntry().getProvider().shouldShow(other);
|
||||
return isChildren && showProvider;
|
||||
}, e.getPersistentState(), e.getCache());
|
||||
var cached = ListBindingsHelper.cachedMappedContentBinding(
|
||||
allChildren,
|
||||
allChildren,
|
||||
var cached = allChildren.mapped(
|
||||
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
|
||||
var ordered = sorted(cached, category);
|
||||
var filtered = ListBindingsHelper.filteredContentBinding(
|
||||
ordered,
|
||||
var filtered = ordered.filtered(
|
||||
section -> {
|
||||
var showFilter = filterString == null || section.matchesFilter(filterString.get());
|
||||
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
|
||||
|| 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
|
||||
// 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 && sameCategory && notRoot && showProvider;
|
||||
return showFilter && matchesSelector && showCategory && notRoot && showProvider;
|
||||
},
|
||||
category,
|
||||
filterString,
|
||||
|
@ -217,6 +208,6 @@ public class StoreSection {
|
|||
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {
|
||||
return c == null
|
||||
|| c.test(wrapper)
|
||||
|| allChildren.stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
|
||||
|| allChildren.getList().stream().anyMatch(storeEntrySection -> storeEntrySection.anyMatches(c));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,8 @@ 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;
|
||||
|
@ -42,9 +40,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
private Comp<CompStructure<Button>> createQuickAccessButton() {
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().isEmpty();
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
},
|
||||
section.getShownChildren());
|
||||
section.getShownChildren().getList());
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
w.executeDefaultAction();
|
||||
|
@ -71,11 +69,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
var expandButton = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().size() > 0
|
||||
&& section.getShownChildren().getList().size() > 0
|
||||
? "mdal-keyboard_arrow_down"
|
||||
: "mdal-keyboard_arrow_right",
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren()),
|
||||
section.getShownChildren().getList()),
|
||||
() -> {
|
||||
section.getWrapper().toggleExpanded();
|
||||
});
|
||||
|
@ -89,7 +87,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
return "Expand " + section.getWrapper().getName().getValue();
|
||||
},
|
||||
section.getWrapper().getName()))
|
||||
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.styleClass("expand-button")
|
||||
.maxHeight(100)
|
||||
.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
|
||||
// section is actually expanded
|
||||
var listSections = ListBindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20
|
||||
var listSections = section.getShownChildren().filtered(
|
||||
storeSection -> section.getAllChildren().getList().size() <= 20
|
||||
|| section.getWrapper().getExpanded().get(),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getAllChildren());
|
||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||
section.getAllChildren().getList());
|
||||
var content = new ListBoxViewComp<>(listSections.getList(), section.getAllChildren().getList(), (StoreSection e) -> {
|
||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||
})
|
||||
.minHeight(0)
|
||||
|
@ -143,10 +140,10 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
var expanded = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().size() > 0;
|
||||
&& section.getShownChildren().getList().size() > 0;
|
||||
},
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren());
|
||||
section.getShownChildren().getList());
|
||||
var full = new VerticalComp(List.of(
|
||||
topEntryList,
|
||||
Comp.separator().hide(expanded.not()),
|
||||
|
@ -155,7 +152,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()).isEqualTo(0)))));
|
||||
Bindings.size(section.getShownChildren().getList()).isEqualTo(0)))));
|
||||
return full.styleClass("store-entry-section-comp")
|
||||
.apply(struc -> {
|
||||
struc.get().setFillWidth(true);
|
||||
|
|
|
@ -8,9 +8,7 @@ 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;
|
||||
|
@ -84,7 +82,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
expanded =
|
||||
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().size() > 0);
|
||||
&& section.getShownChildren().getList().size() > 0);
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> 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()))
|
||||
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
|
||||
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
|
||||
.grow(false, true)
|
||||
.styleClass("expand-button");
|
||||
|
||||
var quickAccessDisabled = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getShownChildren().isEmpty();
|
||||
return section.getShownChildren().getList().isEmpty();
|
||||
},
|
||||
section.getShownChildren());
|
||||
section.getShownChildren().getList());
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = action;
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||
.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
|
||||
// section is actually expanded
|
||||
var listSections = section.getWrapper() != null
|
||||
? ListBindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
|
||||
? section.getShownChildren().filtered(
|
||||
storeSection -> section.getAllChildren().getList().size() <= 20 || expanded.get(),
|
||||
expanded,
|
||||
section.getAllChildren())
|
||||
section.getAllChildren().getList())
|
||||
: 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);
|
||||
})
|
||||
.minHeight(0)
|
||||
|
@ -148,7 +145,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.hide(Bindings.or(
|
||||
Bindings.not(expanded),
|
||||
Bindings.size(section.getAllChildren()).isEqualTo(0))));
|
||||
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
|
||||
|
||||
var vert = new VerticalComp(list);
|
||||
if (condensedStyle) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public interface StoreSortMode {
|
|||
@Override
|
||||
public StoreSection representative(StoreSection s) {
|
||||
return Stream.concat(
|
||||
s.getShownChildren().stream()
|
||||
s.getShownChildren().getList().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().stream()
|
||||
s.getShownChildren().getList().stream()
|
||||
.filter(section -> section.getWrapper()
|
||||
.getEntry()
|
||||
.getValidity()
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
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.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.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableIntegerValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
@ -29,12 +24,14 @@ public class StoreViewState {
|
|||
private final StringProperty filter = new SimpleStringProperty();
|
||||
|
||||
@Getter
|
||||
private final ObservableList<StoreEntryWrapper> allEntries =
|
||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||
private final DerivedObservableList<StoreEntryWrapper> allEntries =
|
||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
||||
|
||||
@Getter
|
||||
private final ObservableList<StoreCategoryWrapper> categories =
|
||||
FXCollections.observableList(new CopyOnWriteArrayList<>());
|
||||
private final DerivedObservableList<StoreCategoryWrapper> categories =
|
||||
new DerivedObservableList<>(FXCollections.observableList(new CopyOnWriteArrayList<>()), true);
|
||||
|
||||
private final ObservableIntegerValue updateObservable = new SimpleIntegerProperty();
|
||||
|
||||
@Getter
|
||||
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
|
||||
|
@ -76,8 +73,8 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
private void updateContent() {
|
||||
categories.forEach(c -> c.update());
|
||||
allEntries.forEach(e -> e.update());
|
||||
categories.getList().forEach(c -> c.update());
|
||||
allEntries.getList().forEach(e -> e.update());
|
||||
}
|
||||
|
||||
private void initSections() {
|
||||
|
@ -86,16 +83,19 @@ public class StoreViewState {
|
|||
StoreSection.createTopLevel(allEntries, storeEntryWrapper -> true, filter, activeCategory);
|
||||
} catch (Exception exception) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void initContent() {
|
||||
allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||
allEntries.getList().setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream()
|
||||
.map(StoreEntryWrapper::new)
|
||||
.toList()));
|
||||
categories.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreCategories().stream()
|
||||
categories.getList().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.stream()
|
||||
activeCategory.setValue(categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(selected))
|
||||
.findFirst()
|
||||
.orElse(categories.stream()
|
||||
.orElse(categories.getList().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);
|
||||
allEntries.clear();
|
||||
allEntries.setAll(l);
|
||||
var l = new ArrayList<>(allEntries.getList());
|
||||
allEntries.getList().clear();
|
||||
allEntries.getList().setAll(l);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -129,6 +129,7 @@ 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)
|
||||
|
@ -142,11 +143,11 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
allEntries.addAll(l);
|
||||
allEntries.getList().addAll(l);
|
||||
}
|
||||
synchronized (this) {
|
||||
categories.stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||
categories.getList().stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||
.getEntry()
|
||||
.getCategoryUuid()
|
||||
|
@ -163,14 +164,14 @@ public class StoreViewState {
|
|||
var a = Arrays.stream(entry).collect(Collectors.toSet());
|
||||
List<StoreEntryWrapper> l;
|
||||
synchronized (this) {
|
||||
l = allEntries.stream()
|
||||
l = allEntries.getList().stream()
|
||||
.filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry()))
|
||||
.toList();
|
||||
}
|
||||
List<StoreCategoryWrapper> cats;
|
||||
synchronized (this) {
|
||||
cats = categories.stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.stream()
|
||||
cats = categories.getList().stream()
|
||||
.filter(storeCategoryWrapper -> allEntries.getList().stream()
|
||||
.anyMatch(storeEntryWrapper -> storeEntryWrapper
|
||||
.getEntry()
|
||||
.getCategoryUuid()
|
||||
|
@ -186,7 +187,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
allEntries.removeAll(l);
|
||||
allEntries.getList().removeAll(l);
|
||||
}
|
||||
cats.forEach(storeCategoryWrapper -> storeCategoryWrapper.update());
|
||||
});
|
||||
|
@ -203,7 +204,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
categories.add(l);
|
||||
categories.getList().add(l);
|
||||
}
|
||||
l.update();
|
||||
});
|
||||
|
@ -213,7 +214,7 @@ public class StoreViewState {
|
|||
public void onCategoryRemove(DataStoreCategory category) {
|
||||
Optional<StoreCategoryWrapper> found;
|
||||
synchronized (this) {
|
||||
found = categories.stream()
|
||||
found = categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().equals(category))
|
||||
.findFirst();
|
||||
|
@ -229,7 +230,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
categories.remove(found.get());
|
||||
categories.getList().remove(found.get());
|
||||
}
|
||||
var p = found.get().getParent();
|
||||
if (p != null) {
|
||||
|
@ -243,12 +244,12 @@ public class StoreViewState {
|
|||
public Optional<StoreSection> getParentSectionForWrapper(StoreEntryWrapper wrapper) {
|
||||
StoreSection current = getCurrentTopLevelSection();
|
||||
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()) {
|
||||
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()) {
|
||||
current = traverse.get();
|
||||
} 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<>() {
|
||||
@Override
|
||||
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
|
||||
|
@ -294,13 +295,11 @@ public class StoreViewState {
|
|||
.compareToIgnoreCase(o2.nameProperty().getValue());
|
||||
}
|
||||
};
|
||||
return ListBindingsHelper.filteredContentBinding(
|
||||
categories, cat -> root == null || cat.getRoot().equals(root))
|
||||
.sorted(comparator);
|
||||
return categories.filtered(cat -> root == null || cat.getRoot().equals(root)).sorted(comparator);
|
||||
}
|
||||
|
||||
public StoreCategoryWrapper getAllConnectionsCategory() {
|
||||
return categories.stream()
|
||||
return categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
|
@ -308,7 +307,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public StoreCategoryWrapper getAllScriptsCategory() {
|
||||
return categories.stream()
|
||||
return categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
|
@ -316,14 +315,14 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public StoreEntryWrapper getEntryWrapper(DataStoreEntry entry) {
|
||||
return allEntries.stream()
|
||||
return allEntries.getList().stream()
|
||||
.filter(storeCategoryWrapper -> storeCategoryWrapper.getEntry().equals(entry))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
|
||||
return categories.stream()
|
||||
return categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().equals(entry))
|
||||
.findFirst()
|
||||
|
|
|
@ -4,17 +4,14 @@ 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;
|
||||
|
||||
|
@ -79,7 +76,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
list.add(null);
|
||||
}
|
||||
|
||||
ListBindingsHelper.setContent(cb.getItems(), list);
|
||||
cb.getItems().setAll(list);
|
||||
});
|
||||
|
||||
cb.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
|
|
|
@ -11,11 +11,10 @@ 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.ListBindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
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;
|
||||
|
@ -26,7 +25,6 @@ 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;
|
||||
|
@ -79,13 +77,12 @@ public class StoreCategoryComp extends SimpleComp {
|
|||
showing.bind(cm.showingProperty());
|
||||
return cm;
|
||||
}));
|
||||
var shownList = ListBindingsHelper.filteredContentBinding(
|
||||
category.getContainedEntries(),
|
||||
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
|
||||
storeEntryWrapper -> {
|
||||
return storeEntryWrapper.shouldShow(
|
||||
StoreViewState.get().getFilterString().getValue());
|
||||
},
|
||||
StoreViewState.get().getFilterString());
|
||||
StoreViewState.get().getFilterString()).getList();
|
||||
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
|
||||
var hover = new SimpleBooleanProperty();
|
||||
var focus = new SimpleBooleanProperty();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -329,13 +329,8 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
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());
|
||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(arr));
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
|
|
|
@ -408,7 +408,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
stateObj.set("persistentState", storePersistentStateNode);
|
||||
obj.set("configuration", mapper.valueToTree(configuration));
|
||||
stateObj.put("expanded", expanded);
|
||||
stateObj.put("orderBefore", orderBefore.toString());
|
||||
stateObj.put("orderBefore", orderBefore != null ? orderBefore.toString() : null);
|
||||
|
||||
var entryString = mapper.writeValueAsString(obj);
|
||||
var stateString = mapper.writeValueAsString(stateObj);
|
||||
|
|
|
@ -40,7 +40,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
|
|||
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.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
value.setValue(newValue);
|
||||
|
|
Loading…
Add table
Reference in a new issue