Implement sorting

This commit is contained in:
crschnick 2023-08-11 18:52:05 +00:00
parent 202f9e4939
commit 473974ada9
7 changed files with 209 additions and 16 deletions

View file

@ -12,7 +12,7 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import java.util.HashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -34,7 +34,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
@Override @Override
public CompStructure<ScrollPane> createBase() { public CompStructure<ScrollPane> createBase() {
Map<T, Region> cache = new HashMap<>(); Map<T, Region> cache = new IdentityHashMap<>();
VBox vbox = new VBox(); VBox vbox = new VBox();
vbox.getStyleClass().add("content"); vbox.getStyleClass().add("content");

View file

@ -0,0 +1,112 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import java.util.List;
public class StoreOrganizationComp extends SimpleComp {
private final Property<StoreSortMode> sortMode;
public StoreOrganizationComp() {
this.sortMode = StoreViewState.get().getSortMode();
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
sortMode);
var alphabetical = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_DESC);
} else if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
} else {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC
|| sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
sortMode);
var date = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
sortMode.setValue(StoreSortMode.DATE_DESC);
} else if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
sortMode.setValue(StoreSortMode.DATE_ASC);
} else {
sortMode.setValue(StoreSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC
|| sortMode.getValue() == StoreSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
private Comp<?> createSortButtonBar() {
return new HorizontalComp(List.of(createDateSortButton(), createAlphabeticalSortButton()));
}
@Override
protected Region createSimple() {
return createSortButtonBar().styleClass("bar").prefHeight(40).createRegion();
}
}

View file

@ -8,11 +8,11 @@ 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.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import lombok.Value; import lombok.Value;
import java.time.Instant;
import java.util.Comparator; import java.util.Comparator;
@Value @Value
@ -32,6 +32,12 @@ public class StoreSection implements StorageFilter.Filterable {
int depth; int depth;
ObservableBooleanValue showDetails; ObservableBooleanValue showDetails;
static ObservableValue<Comparator<StoreSection>> sortMode = Bindings.createObjectBinding(() -> {
return Comparator.<StoreSection>comparingInt(value -> value.getWrapper().getEntry().getState().isUsable() ? 1 : -1)
.thenComparing(StoreViewState.get().getSortMode().getValue().comparator());
}, StoreViewState.get().getSortMode());
public StoreSection(StoreEntryWrapper wrapper, ObservableList<StoreSection> children, int depth) { public StoreSection(StoreEntryWrapper wrapper, ObservableList<StoreSection> children, int depth) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.children = children; this.children = children;
@ -48,14 +54,6 @@ public class StoreSection implements StorageFilter.Filterable {
} }
} }
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
o -> o.wrapper.getEntry().getState().isUsable()
? o.wrapper.getEntry().getLastModified()
: Instant.EPOCH)
.reversed()
.thenComparing(
storeEntrySection -> storeEntrySection.wrapper.getEntry().getName());
public static StoreSection createTopLevel() { public static StoreSection createTopLevel() {
var topLevel = BindingsHelper.cachedMappedContentBinding( var topLevel = BindingsHelper.cachedMappedContentBinding(
StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper, 1)); StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper, 1));
@ -64,7 +62,7 @@ public class StoreSection implements StorageFilter.Filterable {
.getParent(section.getWrapper().getEntry(), true) .getParent(section.getWrapper().getEntry(), true)
.isEmpty(); .isEmpty();
}); });
var ordered = BindingsHelper.orderedContentBinding(filtered, COMPARATOR); var ordered = BindingsHelper.orderedContentBinding(filtered, sortMode);
return new StoreSection(null, ordered, 0); return new StoreSection(null, ordered, 0);
} }
@ -81,7 +79,7 @@ public class StoreSection implements StorageFilter.Filterable {
.orElse(false); .orElse(false);
}); });
var children = BindingsHelper.cachedMappedContentBinding(filtered, entry1 -> create(entry1, depth + 1)); var children = BindingsHelper.cachedMappedContentBinding(filtered, entry1 -> create(entry1, depth + 1));
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR); var ordered = BindingsHelper.orderedContentBinding(children, sortMode);
return new StoreSection(e, ordered, depth); return new StoreSection(e, ordered, depth);
} }

View file

@ -10,13 +10,15 @@ import javafx.scene.layout.VBox;
import java.util.List; import java.util.List;
public class StoreSidebarComp extends SimpleComp { public class StoreSidebarComp extends SimpleComp {
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var sideBar = new VerticalComp(List.of( var sideBar = new VerticalComp(List.of(
new StoreEntryListHeaderComp(), new StoreEntryListHeaderComp(),
new StoreCreationBarComp(), new StoreCreationBarComp(),
new StoreOrganizationComp(),
Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar"))); Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar")));
sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(2), Priority.ALWAYS)); sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(3), Priority.ALWAYS));
sideBar.styleClass("sidebar"); sideBar.styleClass("sidebar");
return sideBar.createRegion(); return sideBar.createRegion();
} }

View file

@ -0,0 +1,65 @@
package io.xpipe.app.comp.storage.store;
import java.time.Instant;
import java.util.Comparator;
import java.util.Locale;
public interface StoreSortMode {
static StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() {
@Override
public String getId() {
return "alphabetical-desc";
}
@Override
public Comparator<StoreSection> comparator() {
return Comparator.<StoreSection, String>comparing(
e -> e.getWrapper().getName().toLowerCase(Locale.ROOT));
}
};
static StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() {
@Override
public String getId() {
return "alphabetical-asc";
}
@Override
public Comparator<StoreSection> comparator() {
return Comparator.<StoreSection, String>comparing(
e -> e.getWrapper().getName().toLowerCase(Locale.ROOT))
.reversed();
}
};
static StoreSortMode DATE_DESC = new StoreSortMode() {
@Override
public String getId() {
return "date-desc";
}
@Override
public Comparator<StoreSection> comparator() {
return Comparator.<StoreSection, Instant>comparing(
e -> e.getWrapper().getLastAccess());
}
};
static StoreSortMode DATE_ASC = new StoreSortMode() {
@Override
public String getId() {
return "date-asc";
}
@Override
public Comparator<StoreSection> comparator() {
return Comparator.<StoreSection, Instant>comparing(e -> e.getWrapper().getLastAccess())
.reversed();
}
};
String getId();
Comparator<StoreSection> comparator();
}

View file

@ -1,15 +1,18 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.storage.StorageFilter; import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
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 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.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.collections.ObservableList; import javafx.collections.ObservableList;
import lombok.Getter;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
@ -28,7 +31,17 @@ public class StoreViewState {
private final ObservableList<StoreEntryWrapper> shownEntries = private final ObservableList<StoreEntryWrapper> shownEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>()); FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter
private final Property<StoreSortMode> sortMode;
private StoreViewState() { private StoreViewState() {
var val = AppCache.getIfPresent("sortMode", StoreSortMode.class).orElse(StoreSortMode.ALPHABETICAL_DESC);
this.sortMode = new SimpleObjectProperty<>(val);
this.sortMode.addListener((observable, oldValue, newValue) -> {
AppCache.update("sortMode", newValue.getId());
});
try { try {
addStorageGroupListeners(); addStorageGroupListeners();
addShownContentChangeListeners(); addShownContentChangeListeners();

View file

@ -142,15 +142,18 @@ public class BindingsHelper {
return l1; return l1;
} }
public static <V> ObservableList<V> orderedContentBinding(ObservableList<V> l2, Comparator<V> comp) { public static <V> ObservableList<V> orderedContentBinding(ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>()); ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> { Runnable runnable = () -> {
setContent(l1, l2.stream().sorted(comp).toList()); setContent(l1, l2.stream().sorted(comp.getValue()).toList());
}; };
runnable.run(); runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> { l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run(); runnable.run();
}); });
comp.addListener((observable, oldValue, newValue) -> {
runnable.run();
});
linkPersistently(l2, l1); linkPersistently(l2, l1);
return l1; return l1;
} }