mirror of
https://github.com/xpipe-io/xpipe.git
synced 2025-04-19 02:33:39 +00:00
Rework derived lists
This commit is contained in:
parent
c050890e6b
commit
43a7978f5d
17 changed files with 67 additions and 191 deletions
|
@ -14,7 +14,13 @@ public class ConnectionAddExchangeImpl extends ConnectionAddExchange {
|
|||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws Throwable {
|
||||
var found = DataStorage.get().getStoreEntryIfPresent(msg.getData(), false);
|
||||
if (found.isEmpty()) {
|
||||
found = DataStorage.get().getStoreEntryIfPresent(msg.getName());
|
||||
}
|
||||
|
||||
if (found.isPresent()) {
|
||||
var data = msg.getData();
|
||||
found.get().setStoreInternal(data, true);
|
||||
return Response.builder().connection(found.get().getUuid()).build();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public class BrowserFileChooserSessionModel extends BrowserAbstractSessionModel<
|
|||
return;
|
||||
}
|
||||
|
||||
var l = new DerivedObservableList<>(fileSelection, true);
|
||||
var l = DerivedObservableList.wrap(fileSelection, true);
|
||||
l.bindContent(newValue.getFileList().getSelection());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||
@Override
|
||||
protected Region createSimple() {
|
||||
var state = BrowserHistorySavedStateImpl.get();
|
||||
var list = new DerivedObservableList<>(state.getEntries(), true)
|
||||
var list = DerivedObservableList.wrap(state.getEntries(), true)
|
||||
.filtered(e -> {
|
||||
if (DataStorage.get() == null) {
|
||||
return false;
|
||||
|
|
|
@ -65,7 +65,7 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview, false);
|
||||
|
||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true)
|
||||
var recent = DerivedObservableList.wrap(model.getSavedState().getRecentDirectories(), true)
|
||||
.mapped(s -> FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
|
||||
.getList();
|
||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||
|
|
|
@ -51,7 +51,7 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
.styleClass("gray")
|
||||
.styleClass("download-background");
|
||||
|
||||
var binding = new DerivedObservableList<>(model.getItems(), true)
|
||||
var binding = DerivedObservableList.wrap(model.getItems(), true)
|
||||
.mapped(item -> item.getBrowserEntry())
|
||||
.getList();
|
||||
var list = new BrowserFileSelectionListComp(binding, entry -> {
|
||||
|
|
|
@ -305,7 +305,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
r.pseudoClassStateChanged(LAST, i == newShown.size() - 1);
|
||||
}
|
||||
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
var d = DerivedObservableList.wrap(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
if (refreshVisibilities) {
|
||||
updateVisibilities(scroll, listView);
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListVirtualViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
|
||||
private static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
|
||||
private static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
|
||||
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
private final int limit = Integer.MAX_VALUE;
|
||||
private final boolean scrollBar;
|
||||
|
||||
@Setter
|
||||
private int platformPauseInterval = -1;
|
||||
|
||||
public ListVirtualViewComp(
|
||||
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
||||
this.shown = shown;
|
||||
this.all = all;
|
||||
this.compFunction = compFunction;
|
||||
this.scrollBar = scrollBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<ScrollPane> createBase() {
|
||||
Map<T, Region> cache = new IdentityHashMap<>();
|
||||
|
||||
var vbox = new VirtualFlow<>();
|
||||
vbox.getStyleClass().add("list-box-content");
|
||||
vbox.setFocusTraversable(false);
|
||||
|
||||
var scroll = new ScrollPane(vbox);
|
||||
if (scrollBar) {
|
||||
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
scroll.skinProperty().subscribe(newValue -> {
|
||||
if (newValue != null) {
|
||||
ScrollBar bar = (ScrollBar) scroll.lookup(".scroll-bar:vertical");
|
||||
bar.opacityProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var v = bar.getVisibleAmount();
|
||||
// Check for rounding and accuracy issues
|
||||
// It might not be exactly equal to 1.0
|
||||
return v < 0.99 ? 1.0 : 0.0;
|
||||
},
|
||||
bar.visibleAmountProperty()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToHeight(true);
|
||||
}
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.getStyleClass().add("list-box-view-comp");
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private void refresh(
|
||||
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
||||
Runnable update = () -> {
|
||||
synchronized (cache) {
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
}
|
||||
|
||||
final long[] lastPause = {System.currentTimeMillis()};
|
||||
// Create copy to reduce chances of concurrent modification
|
||||
var shownCopy = new ArrayList<>(shown);
|
||||
var newShown = shownCopy.stream()
|
||||
.map(v -> {
|
||||
var elapsed = System.currentTimeMillis() - lastPause[0];
|
||||
if (platformPauseInterval != -1 && elapsed > platformPauseInterval) {
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
lastPause[0] = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
cache.put(v, comp != null ? comp.createRegion() : null);
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.limit(limit)
|
||||
.toList();
|
||||
|
||||
if (listView.getChildren().equals(newShown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < newShown.size(); i++) {
|
||||
var r = newShown.get(i);
|
||||
r.pseudoClassStateChanged(ODD, i % 2 != 0);
|
||||
r.pseudoClassStateChanged(EVEN, i % 2 == 0);
|
||||
r.pseudoClassStateChanged(FIRST, i == 0);
|
||||
r.pseudoClassStateChanged(LAST, i == newShown.size() - 1);
|
||||
}
|
||||
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,8 +58,8 @@ public class StoreCategoryWrapper {
|
|||
this.lastAccess = new SimpleObjectProperty<>(category.getLastAccess());
|
||||
this.sortMode = new SimpleObjectProperty<>(category.getSortMode());
|
||||
this.sync = new SimpleObjectProperty<>(category.isSync());
|
||||
this.children = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
this.directContainedEntries = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
this.children = DerivedObservableList.arrayList(true);
|
||||
this.directContainedEntries = DerivedObservableList.arrayList(true);
|
||||
this.color.setValue(category.getColor());
|
||||
setupListeners();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import javafx.beans.property.*;
|
|||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.skin.ScrollPaneSkin;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -76,10 +77,13 @@ public class StoreCreationComp extends ModalOverlayContentComp {
|
|||
valSp.getChildren().add(propR);
|
||||
|
||||
var sp = new ScrollPane(valSp);
|
||||
sp.setSkin(new ScrollPaneSkin(sp));
|
||||
sp.setFitToWidth(true);
|
||||
var vbar = (ScrollBar) sp.lookup(".scroll-bar:vertical");
|
||||
|
||||
var sep = new Separator();
|
||||
sep.setPadding(new Insets(0, 0, 0, 0));
|
||||
sep.visibleProperty().bind(vbar.visibleProperty());
|
||||
|
||||
var vbox = new VBox(sp, sep);
|
||||
VBox.setVgrow(sp, Priority.ALWAYS);
|
||||
|
|
|
@ -43,7 +43,7 @@ public class StoreCreationDialog {
|
|||
DataStorage.get().updateEntry(e, newE);
|
||||
if (madeValid) {
|
||||
StoreViewState.get().triggerStoreListUpdate();
|
||||
if (e.getProvider().shouldShowScan()
|
||||
if (validated && e.getProvider().shouldShowScan()
|
||||
&& AppPrefs.get()
|
||||
.openConnectionSearchWindowOnConnectionCreation()
|
||||
.get()) {
|
||||
|
|
|
@ -200,7 +200,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
}
|
||||
|
||||
protected Region createButtonBar() {
|
||||
var list = new DerivedObservableList<>(getWrapper().getActionProviders(), false);
|
||||
var list = DerivedObservableList.wrap(getWrapper().getActionProviders(), false);
|
||||
var buttons = list.mapped(actionProvider -> {
|
||||
var button = buildButton(actionProvider);
|
||||
return button != null ? button.createRegion() : null;
|
||||
|
|
|
@ -74,7 +74,7 @@ public class StoreEntryListStatusBarComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private ObservableList<Comp<?>> createActions(BooleanProperty busy) {
|
||||
var l = new DerivedObservableList<ActionProvider>(FXCollections.observableArrayList(), true);
|
||||
var l = DerivedObservableList.<ActionProvider>arrayList(true);
|
||||
StoreViewState.get().getEffectiveBatchModeSelection().getList().addListener((ListChangeListener<
|
||||
? super StoreEntryWrapper>)
|
||||
c -> {
|
||||
|
|
|
@ -164,8 +164,8 @@ public class StoreSection {
|
|||
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return new StoreSection(
|
||||
e,
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
DerivedObservableList.arrayList(true),
|
||||
DerivedObservableList.arrayList(true),
|
||||
depth);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,12 +27,10 @@ public class StoreViewState {
|
|||
private final StringProperty filter = new SimpleStringProperty();
|
||||
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> allEntries = new DerivedObservableList<>(
|
||||
FXCollections.synchronizedObservableList(FXCollections.observableArrayList()), true);
|
||||
private final DerivedObservableList<StoreEntryWrapper> allEntries = DerivedObservableList.synchronizedArrayList(true);
|
||||
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreCategoryWrapper> categories = new DerivedObservableList<>(
|
||||
FXCollections.synchronizedObservableList(FXCollections.observableArrayList()), true);
|
||||
private final DerivedObservableList<StoreCategoryWrapper> categories = DerivedObservableList.synchronizedArrayList(true);
|
||||
|
||||
@Getter
|
||||
private final IntegerProperty entriesListVisibilityObservable = new SimpleIntegerProperty();
|
||||
|
@ -50,8 +48,7 @@ public class StoreViewState {
|
|||
private final BooleanProperty batchMode = new SimpleBooleanProperty(false);
|
||||
|
||||
@Getter
|
||||
private final DerivedObservableList<StoreEntryWrapper> batchModeSelection = new DerivedObservableList<>(
|
||||
FXCollections.synchronizedObservableList(FXCollections.observableArrayList()), true);
|
||||
private final DerivedObservableList<StoreEntryWrapper> batchModeSelection = DerivedObservableList.synchronizedArrayList(true);
|
||||
|
||||
@Getter
|
||||
private boolean initialized = false;
|
||||
|
@ -111,6 +108,7 @@ public class StoreViewState {
|
|||
}
|
||||
|
||||
public void selectBatchMode(StoreSection section) {
|
||||
System.out.println("Select " + section.getWrapper());
|
||||
var wrapper = section.getWrapper();
|
||||
if (wrapper != null && !batchModeSelection.getList().contains(wrapper)) {
|
||||
batchModeSelection.getList().add(wrapper);
|
||||
|
@ -171,8 +169,8 @@ public class StoreViewState {
|
|||
} catch (Exception exception) {
|
||||
currentTopLevelSection = new StoreSection(
|
||||
null,
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
new DerivedObservableList<>(FXCollections.observableArrayList(), true),
|
||||
DerivedObservableList.arrayList(true),
|
||||
DerivedObservableList.arrayList(true),
|
||||
0);
|
||||
ErrorEvent.fromThrowable(exception).handle();
|
||||
}
|
||||
|
|
|
@ -54,17 +54,11 @@ public class BitwardenPasswordManager implements PasswordManager {
|
|||
return null;
|
||||
}
|
||||
var cmd = sc.command(CommandBuilder.of()
|
||||
.add("bw", "unlock", "--passwordenv", "BW_PASSWORD")
|
||||
.add("bw", "unlock", "--raw", "--passwordenv", "BW_PASSWORD")
|
||||
.fixedEnvironment("BW_PASSWORD", pw.getSecret().getSecretValue()));
|
||||
cmd.setSensitive();
|
||||
var out = cmd.readStdoutOrThrow();
|
||||
var matcher = Pattern.compile("export BW_SESSION=\"(.+)\"").matcher(out);
|
||||
if (matcher.find()) {
|
||||
var sessionKey = matcher.group(1);
|
||||
sc.view().setSensitiveEnvironmentVariable("BW_SESSION", sessionKey);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
sc.view().setSensitiveEnvironmentVariable("BW_SESSION", out);
|
||||
}
|
||||
|
||||
var b = CommandBuilder.of()
|
||||
|
|
|
@ -525,6 +525,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
}
|
||||
childrenCache = null;
|
||||
dirty = true;
|
||||
notifyUpdate(false, updateTime);
|
||||
}
|
||||
|
||||
public void reassignStoreNode() {
|
||||
|
|
|
@ -14,25 +14,44 @@ import lombok.Getter;
|
|||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
public class DerivedObservableList<T> {
|
||||
|
||||
public static <T> DerivedObservableList<T> synchronizedArrayList(boolean unique) {
|
||||
var list = new ArrayList<T>();
|
||||
return new DerivedObservableList<>(list, FXCollections.synchronizedObservableList(FXCollections.observableList(list)), unique);
|
||||
}
|
||||
|
||||
public static <T> DerivedObservableList<T> arrayList(boolean unique) {
|
||||
var list = new ArrayList<T>();
|
||||
return new DerivedObservableList<>(list, FXCollections.observableList(list), unique);
|
||||
}
|
||||
|
||||
public static <T> DerivedObservableList<T> wrap(ObservableList<T> list, boolean unique) {
|
||||
return new DerivedObservableList<>(null, list, unique);
|
||||
}
|
||||
|
||||
private final List<T> backingList;
|
||||
private final ObservableList<T> list;
|
||||
private final boolean unique;
|
||||
|
||||
public DerivedObservableList(ObservableList<T> list, boolean unique) {
|
||||
public DerivedObservableList(List<T> backingList, ObservableList<T> list, boolean unique) {
|
||||
this.backingList = backingList;
|
||||
this.list = list;
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
private <V> DerivedObservableList<V> createNewDerived() {
|
||||
var name = list.getClass().getSimpleName();
|
||||
var backingList = new ArrayList<V>();
|
||||
var l = name.toLowerCase().contains("synchronized")
|
||||
? FXCollections.<V>synchronizedObservableList(FXCollections.observableArrayList())
|
||||
: FXCollections.<V>observableArrayList();
|
||||
BindingsHelper.preserve(l, list);
|
||||
return new DerivedObservableList<>(l, unique);
|
||||
? FXCollections.synchronizedObservableList(FXCollections.observableList(backingList))
|
||||
: FXCollections.observableList(backingList);
|
||||
var derived = new DerivedObservableList<>(backingList, l, unique);
|
||||
BindingsHelper.preserve(l, this);
|
||||
return derived;
|
||||
}
|
||||
|
||||
public void setContent(List<? extends T> newList) {
|
||||
|
@ -142,13 +161,21 @@ public class DerivedObservableList<T> {
|
|||
list.setAll(newList);
|
||||
}
|
||||
|
||||
private Stream<T> listStream() {
|
||||
if (backingList != null) {
|
||||
return backingList.stream();
|
||||
}
|
||||
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
public <V> DerivedObservableList<V> mapped(Function<T, V> map) {
|
||||
var cache = new HashMap<T, V>();
|
||||
var l1 = this.<V>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
var listSet = new HashSet<>(list);
|
||||
cache.keySet().removeIf(t -> !listSet.contains(t));
|
||||
l1.setContent(list.stream()
|
||||
l1.setContent(listStream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, map.apply(v));
|
||||
|
@ -192,9 +219,8 @@ public class DerivedObservableList<T> {
|
|||
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()
|
||||
d.setContent(predicate.getValue() != null
|
||||
? listStream().filter(predicate.getValue()).toList()
|
||||
: list);
|
||||
};
|
||||
runnable.run();
|
||||
|
@ -223,7 +249,7 @@ public class DerivedObservableList<T> {
|
|||
public DerivedObservableList<T> sorted(ObservableValue<Comparator<T>> comp) {
|
||||
var d = this.<T>createNewDerived();
|
||||
Runnable runnable = () -> {
|
||||
d.setContent(list.stream().sorted(comp.getValue()).toList());
|
||||
d.setContent(listStream().sorted(comp.getValue()).toList());
|
||||
};
|
||||
runnable.run();
|
||||
list.addListener((ListChangeListener<? super T>) c -> {
|
||||
|
@ -234,19 +260,4 @@ public class DerivedObservableList<T> {
|
|||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue