Category choice comp fixes and storage dirty optimizations

This commit is contained in:
crschnick 2023-10-22 14:10:08 +00:00
parent d87f74fffc
commit 1faff72321
16 changed files with 103 additions and 75 deletions

View file

@ -1,16 +1,12 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreSectionMiniComp;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import io.xpipe.app.util.FixedHierarchyStore;
@ -20,6 +16,7 @@ import io.xpipe.core.store.ShellStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Point2D;
@ -55,9 +52,10 @@ final class BrowserBookmarkList extends SimpleComp {
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
};
var selectedCategory = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
var section = StoreSectionMiniComp.createList(
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(), applicable, filterText, StoreViewState.get().getActiveCategory()),
StoreViewState.get().getAllEntries(), applicable, filterText, selectedCategory),
(s, comp) -> {
BooleanProperty busy = new SimpleBooleanProperty(false);
comp.disable(Bindings.createBooleanBinding(() -> {
@ -92,7 +90,7 @@ final class BrowserBookmarkList extends SimpleComp {
});
});
});
var category = new DataStoreCategoryChoiceComp(DataStorage.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory())
var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(), selectedCategory)
.styleClass(Styles.LEFT_PILL)
.grow(false, true);
var filter =

View file

@ -53,6 +53,10 @@ public class StoreCategoryWrapper {
update();
}
public StoreCategoryWrapper getRoot() {
return StoreViewState.get().getCategoryWrapper(root);
}
public StoreCategoryWrapper getParent() {
return StoreViewState.get().getCategories().stream()
.filter(storeCategoryWrapper ->
@ -122,7 +126,6 @@ public class StoreCategoryWrapper {
.getUuid()
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
.toList());
Optional.ofNullable(getParent())
.ifPresent(storeCategoryWrapper -> {
storeCategoryWrapper.update();

View file

@ -345,7 +345,7 @@ public abstract class StoreEntryComp extends SimpleComp {
if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
StoreViewState.get().getSortedCategories(DataStorage.get().getRootCategory(DataStorage.get().getStoreCategoryIfPresent(wrapper.getEntry().getCategoryUuid()).orElseThrow())).forEach(storeCategoryWrapper -> {
StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue()).forEach(storeCategoryWrapper -> {
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());

View file

@ -7,7 +7,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@ -25,15 +24,14 @@ public class StoreEntryListStatusComp extends SimpleComp {
private Region createGroupListHeader() {
var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory().getCategory()) ? "Connections" : "Scripts";
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "Connections" : "Scripts";
}, StoreViewState.get().getActiveCategory()));
label.getStyleClass().add("name");
var all = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> {
var cat = DataStorage.get().getStoreCategoryIfPresent(storeEntryWrapper.getEntry().getCategoryUuid()).orElse(null);
var storeRoot = cat != null ? DataStorage.get().getRootCategory(cat) : null;
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(storeRoot);
},
StoreViewState.get().getActiveCategory());

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
@ -39,6 +38,7 @@ public class StoreEntryWrapper {
private final Property<Object> persistentState = new SimpleObjectProperty<>();
private final MapProperty<String, Object> cache = new SimpleMapProperty<>(FXCollections.observableHashMap());
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
public StoreEntryWrapper(DataStoreEntry entry) {
this.entry = entry;
@ -118,12 +118,6 @@ public class StoreEntryWrapper {
}
public void update() {
// var cat = StoreViewState.get().getCategories().stream()
// .filter(storeCategoryWrapper ->
// Objects.equals(storeCategoryWrapper.getCategory().getUuid(), entry.getCategoryUuid()))
// .findFirst();
// category.setValue(cat.orElseThrow());
// Avoid reupdating name when changed from the name property!
if (!entry.getName().equals(name.getValue())) {
name.setValue(entry.getName());
@ -142,6 +136,10 @@ public class StoreEntryWrapper {
deletable.setValue(entry.getConfiguration().isDeletable()
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
if (StoreViewState.get() != null) {
category.setValue(StoreViewState.get().getCategoryWrapper(DataStorage.get().getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow()));
}
actionProviders.keySet().forEach(dataStoreActionProvider -> {
if (!isInStorage()) {
actionProviders.get(dataStoreActionProvider).set(false);

View file

@ -98,7 +98,7 @@ public class StoreSection {
section -> {
var showFilter = filterString == null || section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
var sameCategory = category == null || category.getValue().contains(section.getWrapper().getEntry());
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
return showFilter && matchesSelector && sameCategory;
},
category,

View file

@ -55,18 +55,18 @@ public class StoreViewState {
currentTopLevelSection = tl;
}
public ObservableList<StoreCategoryWrapper> getSortedCategories(DataStoreCategory root) {
public ObservableList<StoreCategoryWrapper> getSortedCategories(StoreCategoryWrapper root) {
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
@Override
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
var o1Root = o1.getRoot();
var o2Root = o2.getRoot();
if (o1Root.equals(getAllConnectionsCategory().getCategory()) && !o1Root.equals(o2Root)) {
if (o1Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
return -1;
}
if (o2Root.equals(getAllConnectionsCategory().getCategory()) && !o1Root.equals(o2Root)) {
if (o2Root.equals(getAllConnectionsCategory()) && !o1Root.equals(o2Root)) {
return 1;
}
@ -117,6 +117,15 @@ public class StoreViewState {
.orElseThrow();
}
public StoreCategoryWrapper getCategoryWrapper(DataStoreCategory entry) {
return categories.stream()
.filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().equals(entry))
.findFirst()
.orElseThrow();
}
public static void init() {
if (INSTANCE != null) {
return;

View file

@ -106,7 +106,8 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
comp.disable(new SimpleBooleanProperty(true));
}
});
var category = new DataStoreCategoryChoiceComp(initialCategory != null ? initialCategory.getRoot() : null, selectedCategory).styleClass(Styles.LEFT_PILL);
var category = new DataStoreCategoryChoiceComp(initialCategory != null ? initialCategory.getRoot() : null, StoreViewState.get().getActiveCategory(),
selectedCategory).styleClass(Styles.LEFT_PILL);
var filter = new FilterComp(filterText)
.styleClass(Styles.CENTER_PILL)
.hgrow()

View file

@ -135,7 +135,6 @@ public class DataStoreEntry extends StorageElement {
null,
false, null
);
entry.refresh();
return entry;
}
@ -294,7 +293,7 @@ public class DataStoreEntry extends StorageElement {
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
this.dirty = true;
refresh();
notifyUpdate();
}
public void setCategoryUuid(UUID categoryUuid) {
@ -339,10 +338,16 @@ public class DataStoreEntry extends StorageElement {
dirty = true;
provider = e.provider;
childrenCache = null;
refresh();
validity = store == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE;
notifyUpdate();
}
public void setStoreInternal(DataStore store, boolean updateTime) {
var changed = !Objects.equals(this.store, store);
if (!changed) {
return;
}
this.store = store;
this.storeNode = DataStorageWriter.storeToNode(store);
if (updateTime) {
@ -384,6 +389,12 @@ public class DataStoreEntry extends StorageElement {
return false;
}
store = DataStorageParser.storeFromNode(storeNode);
if (store == null) {
validity = Validity.LOAD_FAILED;
return false;
}
var newComplete = store.isComplete();
if (!newComplete) {
return false;
@ -403,6 +414,12 @@ public class DataStoreEntry extends StorageElement {
return false;
}
store = DataStorageParser.storeFromNode(storeNode);
if (store == null) {
validity = Validity.LOAD_FAILED;
return false;
}
var newComplete = store.isComplete();
if (newComplete) {
return false;
@ -413,29 +430,6 @@ public class DataStoreEntry extends StorageElement {
return true;
}
public void refresh() {
var oldStore = store;
DataStore newStore = DataStorageParser.storeFromNode(storeNode);
if (newStore == null
|| DataStoreProviders.byStoreClass(newStore.getClass()).isEmpty()) {
store = null;
validity = Validity.LOAD_FAILED;
provider = null;
dirty = dirty || oldStore != null;
notifyUpdate();
} else {
dirty = dirty || !oldStore.equals(newStore);
store = newStore;
var complete = newStore.isComplete();
if (complete) {
validity = Validity.COMPLETE;
} else {
validity = Validity.INCOMPLETE;
}
notifyUpdate();
}
}
@SneakyThrows
public void initializeEntry() {
if (store instanceof ExpandedLifecycleStore lifecycleStore) {

View file

@ -269,16 +269,13 @@ public class StandardStorage extends DataStorage {
local.setColor(DataStoreColor.BLUE);
}
// Refresh to update state
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.refresh());
refreshValidities(true);
storeEntries.forEach(entry -> {
var syntheticParent = getSyntheticParent(entry);
syntheticParent.ifPresent(entry1 -> {
addStoreEntryIfNotPresent(entry1);
});
});
refreshValidities(true);
// Save to apply changes

View file

@ -4,7 +4,7 @@ import io.xpipe.app.comp.store.StoreCategoryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.property.Property;
import javafx.geometry.Insets;
import javafx.scene.control.ComboBox;
@ -15,22 +15,35 @@ import lombok.Value;
public class DataStoreCategoryChoiceComp extends SimpleComp {
private final DataStoreCategory root;
private final Property<StoreCategoryWrapper> selected;
private final StoreCategoryWrapper root;
private final Property<StoreCategoryWrapper> external;
private final Property<StoreCategoryWrapper> value;
public DataStoreCategoryChoiceComp(DataStoreCategory root, Property<StoreCategoryWrapper> selected) {
public DataStoreCategoryChoiceComp(StoreCategoryWrapper root, Property<StoreCategoryWrapper> external, Property<StoreCategoryWrapper> value) {
this.root = root;
this.selected = selected;
this.external = external;
this.value = value;
}
@Override
protected Region createSimple() {
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root));
box.setValue(selected.getValue());
box.valueProperty().addListener((observable, oldValue, newValue) -> {
selected.setValue(newValue);
SimpleChangeListener.apply(external, newValue -> {
if (newValue == null) {
value.setValue(root);
} else if (root == null) {
value.setValue(newValue);
} else if (!newValue.getRoot().equals(root)) {
value.setValue(root);
} else {
value.setValue(newValue);
}
});
selected.addListener((observable, oldValue, newValue) -> {
var box = new ComboBox<>(StoreViewState.get().getSortedCategories(root));
box.setValue(value.getValue());
box.valueProperty().addListener((observable, oldValue, newValue) -> {
value.setValue(newValue);
});
value.addListener((observable, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> box.setValue(newValue));
});
box.setCellFactory(param -> {
@ -54,6 +67,8 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
if (w != null) {
textProperty().bind(w.nameProperty());
setPadding(new Insets(6, 6, 6, 8 + (indent ? w.getDepth() * 8 : 0)));
} else {
setText("None");
}
}
}

View file

@ -1,10 +1,7 @@
package io.xpipe.core.util;
import io.xpipe.core.process.*;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ProcessOutputException;
import io.xpipe.core.process.ShellControl;
import lombok.Getter;
import lombok.SneakyThrows;
@ -58,20 +55,22 @@ public class XPipeInstallation {
public static String createExternalAsyncLaunchCommand(
String installationBase, XPipeDaemonMode mode, String arguments) {
var suffix = (arguments != null ? " " + arguments : "");
var modeOption = mode != null ? " --mode " + mode.getDisplayName() : null;
if (OsType.getLocal().equals(OsType.LINUX)) {
return "nohup \"" + installationBase + "/app/bin/xpiped\" --mode " + mode.getDisplayName() + suffix
return "nohup \"" + installationBase + "/app/bin/xpiped\"" + modeOption + suffix
+ " & disown";
} else if (OsType.getLocal().equals(OsType.MACOS)) {
return "open \"" + installationBase + "\" --args --mode " + mode.getDisplayName() + suffix;
return "open \"" + installationBase + "\" --args" + modeOption + suffix;
}
return "\"" + FileNames.join(installationBase, XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()))
+ "\" --mode " + mode.getDisplayName() + suffix;
+ "\"" + modeOption + suffix;
}
public static String createExternalLaunchCommand(String command, String arguments, XPipeDaemonMode mode) {
var suffix = (arguments != null ? " " + arguments : "");
return "\"" + command + "\" --mode " + mode.getDisplayName() + suffix;
var modeOption = mode != null ? " --mode " + mode.getDisplayName() : null;
return "\"" + command + "\"" + modeOption + suffix;
}
@SneakyThrows

View file

@ -1,11 +1,19 @@
# Update procedure
Note that the automatic updater is broken in version 1.6.0. It will freeze the application and not perform the update. **So do not try to click the install button in XPipe**!
You have to install it manually from https://github.com/xpipe-io/xpipe/releases/tag/1.7.0. You can easily do this as uninstalling the old version does not delete any user data. Installing a newer version of XPipe also automatically uninstalls any old ones, so you don't have to manually uninstall it.
You have to install it manually from https://github.com/xpipe-io/xpipe/releases/tag/1.7.2. You can easily do this as uninstalling the old version does not delete any user data. Installing a newer version of XPipe also automatically uninstalls any old ones, so you don't have to manually uninstall it.
## Changes in 1.7.2
- Fix tray mode not working on newer Gnome desktop environments
### Bring your scripts with you
This update introduces a new toggle available for all scripts that if enabled, will automatically copy these scripts to the target system and put them into the PATH when launching a new terminal session. This allows you to easily call your scripts on any system without any setup.
### Other changes
- Improve startup time on Linux and macOS by skipping tray initialization
- Add support for tray icon on newer Gnome desktop environments
- Fix application not starting up on newer Gnome desktop environments
- Fix killing of local unresponsive shell leading to further errors
## Changes in 1.7.0

View file

@ -27,6 +27,9 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
@Override
public Comp<?> customEntryComp(StoreSection sec, boolean preferLarge) {
ScriptGroupStore s = sec.getWrapper().getEntry().getStore().asNeeded();
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
}
var def = new StoreToggleComp("base.isDefaultGroup", sec, s.getState().isDefault(), aBoolean -> {
var state = s.getState();

View file

@ -87,6 +87,7 @@ public class SimpleScriptStore extends ScriptStore {
@Override
public void checkComplete() throws Exception {
Validators.nonNull(group);
super.checkComplete();
Validators.nonNull(executionType);
Validators.nonNull(minimumDialect);

View file

@ -43,6 +43,10 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
@Override
public Comp<?> customEntryComp(StoreSection sec, boolean preferLarge) {
SimpleScriptStore s = sec.getWrapper().getEntry().getStore().asNeeded();
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
}
var groupWrapper = StoreViewState.get().getEntryWrapper(s.getGroup().getEntry());
var def = new StoreToggleComp("base.isDefault", sec, s.getState().isDefault(), aBoolean -> {