This commit is contained in:
crschnick 2024-06-07 08:18:23 +00:00
parent 07540e6779
commit 7d6138b9f0
19 changed files with 205 additions and 77 deletions

View file

@ -68,10 +68,7 @@ dependencies {
api group: 'org.slf4j', name: 'slf4j-jdk-platform-logging', version: '2.0.13'
api 'io.xpipe:modulefs:0.1.5'
api 'net.synedra:validatorfx:0.4.2'
api ('io.github.mkpaz:atlantafx-base:2.0.1') {
exclude group: 'org.openjfx', module: 'javafx-base'
exclude group: 'org.openjfx', module: 'javafx-controls'
}
api files("$rootDir/gradle/gradle_scripts/atlantafx-base-2.0.2.jar")
}
apply from: "$rootDir/gradle/gradle_scripts/local_junit_suite.gradle"

View file

@ -4,16 +4,17 @@ import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import lombok.Setter;
@ -25,6 +26,7 @@ import java.util.function.Function;
public class StoreToggleComp extends SimpleComp {
private final String nameKey;
private final ObservableValue<LabelGraphic> graphic;
private final StoreSection section;
private final BooleanProperty value;
private final Consumer<Boolean> onChange;
@ -33,9 +35,10 @@ public class StoreToggleComp extends SimpleComp {
private ObservableBooleanValue customVisibility = new SimpleBooleanProperty(true);
public static <T extends DataStore> StoreToggleComp simpleToggle(
String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
return new StoreToggleComp(
nameKey,
graphic,
section,
new SimpleBooleanProperty(
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
@ -45,27 +48,34 @@ public class StoreToggleComp extends SimpleComp {
}
public static <T extends DataStore> StoreToggleComp childrenToggle(
String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
return new StoreToggleComp(
nameKey,
section,
new SimpleBooleanProperty(
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
v -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
StoreViewState.get().toggleStoreListUpdate();
});
String nameKey, boolean graphic, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
var val = new SimpleBooleanProperty();
ObservableValue<LabelGraphic> g = graphic ? val.map(aBoolean -> aBoolean ?
new LabelGraphic.IconGraphic("mdi2c-circle-slice-8") : new LabelGraphic.IconGraphic("mdi2c-circle-half-full")) : null;
var t = new StoreToggleComp(nameKey, g, section,
new SimpleBooleanProperty(initial.apply(section.getWrapper().getEntry().getStore().asNeeded())), v -> {
Platform.runLater(() -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
StoreViewState.get().toggleStoreListUpdate();
});
});
t.value.subscribe((newValue) -> {
val.set(newValue);
});
return t;
}
public StoreToggleComp(String nameKey, StoreSection section, boolean initial, Consumer<Boolean> onChange) {
public StoreToggleComp(String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, boolean initial, Consumer<Boolean> onChange) {
this.nameKey = nameKey;
this.graphic = graphic;
this.section = section;
this.value = new SimpleBooleanProperty(initial);
this.onChange = onChange;
}
public StoreToggleComp(String nameKey, StoreSection section, BooleanProperty initial, Consumer<Boolean> onChange) {
public StoreToggleComp(String nameKey, ObservableValue<LabelGraphic> graphic, StoreSection section, BooleanProperty initial, Consumer<Boolean> onChange) {
this.nameKey = nameKey;
this.graphic = graphic;
this.section = section;
this.value = initial;
this.onChange = onChange;
@ -85,7 +95,7 @@ public class StoreToggleComp extends SimpleComp {
section.getWrapper().getValidity(),
section.getShowDetails(),
this.customVisibility);
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey), graphic)
.visible(visible)
.disable(disable);
value.addListener((observable, oldValue, newValue) -> {

View file

@ -1,25 +1,24 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.ToggleSwitch;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.LabelGraphic;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import atlantafx.base.controls.ToggleSwitch;
@Value
@EqualsAndHashCode(callSuper = true)
public class ToggleSwitchComp extends SimpleComp {
private final Property<Boolean> selected;
private final ObservableValue<String> name;
public ToggleSwitchComp(Property<Boolean> selected, ObservableValue<String> name) {
this.selected = selected;
this.name = name;
}
Property<Boolean> selected;
ObservableValue<String> name;
ObservableValue<LabelGraphic> graphic;
@Override
protected Region createSimple() {
@ -43,6 +42,9 @@ public class ToggleSwitchComp extends SimpleComp {
if (name != null) {
s.textProperty().bind(PlatformThread.sync(name));
}
if (graphic != null) {
s.graphicProperty().bind(PlatformThread.sync(graphic.map(labelGraphic -> labelGraphic.createGraphicNode())));
}
return s;
}
}

View file

@ -14,9 +14,7 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import lombok.Value;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
@ -125,16 +123,16 @@ public class StoreSection {
category,
StoreViewState.get().getEntriesListChangeObservable());
var cached = topLevel.mapped(
storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
storeEntryWrapper -> create(List.of(), storeEntryWrapper, 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var shown = ordered.filtered(
section -> {
var showFilter = filterString == null || section.matchesFilter(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null
|| category.getValue() == null
|| showInCategory(category.getValue(), section.getWrapper());
return showFilter && matchesSelector && sameCategory;
// matches filter
return (filterString == null || section.matchesFilter(filterString.get())) &&
// matches selector
(section.anyMatches(entryFilter)) &&
// same category
(category == null || category.getValue() == null || showInCategory(category.getValue(), section.getWrapper()));
},
category,
filterString);
@ -142,6 +140,7 @@ public class StoreSection {
}
private static StoreSection create(
List<StoreEntryWrapper> parents,
StoreEntryWrapper e,
int depth,
DerivedObservableList<StoreEntryWrapper> all,
@ -161,27 +160,28 @@ public class StoreSection {
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// This check is fast as the children are cached in the storage
var isChildren = DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
var showProvider = !other.getEntry().getValidity().isUsable() || other.getEntry().getProvider().shouldShow(other);
return isChildren && showProvider;
// is children. This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry()) &&
// show provider
(!other.getEntry().getValidity().isUsable() || other.getEntry().getProvider().shouldShow(other));
}, e.getPersistentState(), e.getCache(), StoreViewState.get().getEntriesListChangeObservable());
var cached = allChildren.mapped(
entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
var l = new ArrayList<>(parents);
l.add(e);
var cached = allChildren.mapped(c -> create(l, c, depth + 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var filtered = ordered.filtered(
section -> {
var matchesSelector = section.anyMatches(entryFilter);
// Prevent updates for children on category switching by checking depth
var showCategory = category == null
|| category.getValue() == null
|| 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 =
// matches filter
return (filterString == null || section.matchesFilter(filterString.get()) || l.stream().anyMatch(p -> p.matchesFilter(filterString.get()))) &&
// matches selector
section.anyMatches(entryFilter) &&
// matches category
// Prevent updates for children on category switching by checking depth
(category == null || category.getValue() == null || showInCategory(category.getValue(), section.getWrapper()) || depth > 0) &&
// not root
// If this entry is already shown as root due to a different category than parent, don't show it
// again here
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
return matchesSelector && showCategory && notRoot;
},
category,
filterString,

View file

@ -38,7 +38,7 @@ public interface SingletonSessionStoreProvider extends DataStoreProvider {
enabled.set(s.isSessionEnabled());
});
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
var t = new StoreToggleComp(null, null, sec, enabled, aBoolean -> {
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
if (s.isSessionEnabled() != aBoolean) {
ThreadHelper.runFailableAsync(() -> {

View file

@ -0,0 +1,42 @@
package io.xpipe.app.fxcomps.util;
import io.xpipe.app.fxcomps.Comp;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;
public abstract class LabelGraphic {
public static ObservableValue<LabelGraphic> fixedIcon(String icon) {
return new SimpleObjectProperty<>(new IconGraphic(icon));
}
public abstract Node createGraphicNode();
@Value
@EqualsAndHashCode(callSuper = true)
public static class IconGraphic extends LabelGraphic {
String icon;
@Override
public Node createGraphicNode() {
return new FontIcon(icon);
}
}
@Value
@EqualsAndHashCode(callSuper = true)
public static class CompGraphic extends LabelGraphic {
Comp<?> comp;
@Override
public Node createGraphicNode() {
return comp.createRegion();
}
}
}

View file

@ -58,7 +58,7 @@ public abstract class DataStorage {
@Setter
protected DataStoreCategory selectedCategory;
private final Map<DataStore, DataStoreEntry> storeEntryMapCache = Collections.synchronizedMap(new HashMap<>());
private final Map<DataStore, DataStoreEntry> storeEntryMapCache = Collections.synchronizedMap(new IdentityHashMap<>());
public DataStorage() {
var prefsDir = AppPrefs.get().storageDirectory().getValue();
@ -348,9 +348,8 @@ public abstract class DataStorage {
return false;
}
var oldChildren = getStoreEntries().stream()
.filter(other -> e.equals(getDefaultDisplayParent(other).orElse(null)))
.toList();
var oldChildren = getStoreChildren(e);
var toRemove = oldChildren.stream()
.filter(oc -> {
var oid = ((FixedChildStore) oc.getStore()).getFixedId();
@ -549,6 +548,7 @@ public abstract class DataStorage {
for (DataStoreEntry e : toAdd) {
var syntheticParent = getSyntheticParent(e);
if (syntheticParent.isPresent()) {
var exists =
addStoreEntryIfNotPresent(syntheticParent.get());
}
@ -786,10 +786,12 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
synchronized (storeEntryMapCache) {
var found = storeEntryMapCache.get(store);
if (found != null) {
return Optional.of(found);
if (identityOnly) {
synchronized (storeEntryMapCache) {
var found = storeEntryMapCache.get(store);
if (found != null) {
return Optional.of(found);
}
}
}
@ -800,8 +802,10 @@ public abstract class DataStorage {
&& store.equals(n.getStore()))))
.findFirst();
if (found.isPresent()) {
synchronized (storeEntryMapCache) {
storeEntryMapCache.put(store, found.get());
if (identityOnly) {
synchronized (storeEntryMapCache) {
storeEntryMapCache.put(store, found.get());
}
}
}
return found;
@ -866,12 +870,22 @@ public abstract class DataStorage {
}
public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) {
var forStoreIdentity = getStoreEntryIfPresent(store, true);
if (forStoreIdentity.isPresent()) {
return forStoreIdentity.get();
}
var uuid = UuidHelper.generateFromObject(parent.getUuid(), name);
var found = getStoreEntryIfPresent(uuid);
if (found.isPresent()) {
return found.get();
}
var forStore = getStoreEntryIfPresent(store, false);
if (forStore.isPresent()) {
return forStore.get();
}
return DataStoreEntry.createNew(uuid, parent.getCategoryUuid(), name, store);
}

View file

@ -221,7 +221,7 @@ public class OptionsBuilder {
}
public OptionsBuilder addToggle(Property<Boolean> prop) {
var comp = new ToggleSwitchComp(prop, null);
var comp = new ToggleSwitchComp(prop, null, null);
pushComp(comp);
props.add(prop);
return this;

View file

@ -68,11 +68,11 @@
-fx-background-color: -color-neutral-muted;
}
.store-entry-comp .button-bar, .store-entry-comp .dropdown-comp {
.store-entry-comp .button-bar, .store-entry-comp .dropdown-comp, .store-entry-comp .toggle-switch-comp {
-fx-opacity: 0.65;
}
.store-entry-comp:hover .button-bar, .store-entry-comp:hover .dropdown-comp {
.store-entry-comp:hover .button-bar, .store-entry-comp:hover .dropdown-comp, .store-entry-comp:hover .toggle-switch-comp {
-fx-opacity: 1.0;
}

View file

@ -5,6 +5,14 @@
}
*/
.toggle-switch .label {
-fx-font-size: 1.7em;
}
.toggle-switch {
-fx-font-size: 0.8em;
}
.store-layout .split-pane-divider {
-fx-background-color: transparent;
}

View file

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

View file

@ -0,0 +1,13 @@
package io.xpipe.ext.base.script;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum ScriptTargetType {
@JsonProperty("shellInit")
SHELL_INIT,
@JsonProperty("shellSession")
SHELL_SESSION,
@JsonProperty("file")
FILE
}

View file

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

View file

@ -26,7 +26,7 @@ public abstract class AbstractServiceGroupStoreProvider implements DataStoreProv
private StoreToggleComp createToggleComp(StoreSection sec) {
var enabled = new SimpleBooleanProperty();
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
var t = new StoreToggleComp(null, null, sec, enabled, aBoolean -> {
var children = DataStorage.get().getStoreChildren(sec.getWrapper().getEntry());
ThreadHelper.runFailableAsync(() -> {
for (DataStoreEntry child : children) {

View file

@ -13,7 +13,8 @@ public class FixedServiceStoreProvider extends AbstractServiceStoreProvider {
@Override
public DataStoreEntry getSyntheticParent(DataStoreEntry store) {
FixedServiceStore s = store.getStore().asNeeded();
return DataStorage.get().getOrCreateNewSyntheticEntry(s.getHost().get(), "Services", FixedServiceGroupStore.builder().parent(s.getDisplayParent().get().ref()).build());
return DataStorage.get().getOrCreateNewSyntheticEntry(s.getHost().get(), "Services",
FixedServiceGroupStore.builder().parent(s.getDisplayParent().get().ref()).build());
}
@Override

View file

@ -0,0 +1,15 @@
package io.xpipe.ext.base.service;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("mappedService")
public class MappedServiceStore extends FixedServiceStore {
private final int containerPort;
}

View file

@ -0,0 +1,26 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import java.util.List;
public class MappedServiceStoreProvider extends FixedServiceStoreProvider {
@Override
public List<String> getPossibleNames() {
return List.of("mappedService");
}
@Override
public ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
MappedServiceStore s = wrapper.getEntry().getStore().asNeeded();
return new SimpleStringProperty("Port " + s.getContainerPort() + " -> " + s.getRemotePort());
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(MappedServiceStore.class);
}
}

View file

@ -72,6 +72,6 @@ open module io.xpipe.ext.base {
EditStoreAction,
DeleteStoreChildrenAction,
BrowseStoreAction;
provides DataStoreProvider with FixedServiceGroupStoreProvider, ServiceGroupStoreProvider, CustomServiceStoreProvider, FixedServiceStoreProvider, SimpleScriptStoreProvider, DesktopEnvironmentStoreProvider, DesktopApplicationStoreProvider, DesktopCommandStoreProvider, ScriptGroupStoreProvider;
provides DataStoreProvider with FixedServiceGroupStoreProvider, ServiceGroupStoreProvider, CustomServiceStoreProvider, MappedServiceStoreProvider, FixedServiceStoreProvider, SimpleScriptStoreProvider, DesktopEnvironmentStoreProvider, DesktopApplicationStoreProvider, DesktopCommandStoreProvider, ScriptGroupStoreProvider;
provides DataStorageExtensionProvider with ScriptDataStorageProvider;
}

Binary file not shown.