More script rework, state rework, category fixes, and bug fixes

This commit is contained in:
crschnick 2023-10-05 23:40:52 +00:00
parent 87d1d45cae
commit 43d7e0830c
33 changed files with 500 additions and 309 deletions

View file

@ -10,9 +10,11 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import java.util.function.Consumer; import java.util.function.Consumer;
@AllArgsConstructor
public class StoreToggleComp extends SimpleComp { public class StoreToggleComp extends SimpleComp {
private final String nameKey; private final String nameKey;

View file

@ -19,6 +19,7 @@ import java.util.Optional;
@Getter @Getter
public class StoreCategoryWrapper { public class StoreCategoryWrapper {
private final DataStoreCategory root;
private final int depth; private final int depth;
private final Property<String> name; private final Property<String> name;
private final DataStoreCategory category; private final DataStoreCategory category;
@ -30,15 +31,18 @@ public class StoreCategoryWrapper {
public StoreCategoryWrapper(DataStoreCategory category) { public StoreCategoryWrapper(DataStoreCategory category) {
var d = 0; var d = 0;
DataStoreCategory last = category;
DataStoreCategory p = category; DataStoreCategory p = category;
while ((p = DataStorage.get() while ((p = DataStorage.get()
.getStoreCategoryIfPresent(p.getParentCategory()) .getStoreCategoryIfPresent(p.getParentCategory())
.orElse(null)) .orElse(null))
!= null) { != null) {
d++; d++;
last = p;
} }
depth = d; depth = d;
this.root = last;
this.category = category; this.category = category;
this.name = new SimpleStringProperty(); this.name = new SimpleStringProperty();
this.lastAccess = new SimpleObjectProperty<>(); this.lastAccess = new SimpleObjectProperty<>();

View file

@ -290,6 +290,16 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(item); contextMenu.getItems().add(item);
if (menu != null) { if (menu != null) {
var run = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
run.textProperty().bind(AppI18n.observable("base.execute"));
run.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
p.getKey().getDataStoreCallSite().createAction(wrapper.getEntry().getStore().asNeeded()).execute();
});
});
menu.getItems().add(run);
var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than")); var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than"));
var url = "xpipe://action/" + p.getKey().getId() + "/" var url = "xpipe://action/" + p.getKey().getId() + "/"
+ wrapper.getEntry().getUuid(); + wrapper.getEntry().getUuid();
@ -329,15 +339,17 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(browse); contextMenu.getItems().add(browse);
} }
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline")); if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
StoreViewState.get().getSortedCategories().forEach(storeCategoryWrapper -> { var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
MenuItem m = new MenuItem(storeCategoryWrapper.getName()); StoreViewState.get().getSortedCategories().forEach(storeCategoryWrapper -> {
m.setOnAction(event -> { MenuItem m = new MenuItem(storeCategoryWrapper.getName());
wrapper.moveTo(storeCategoryWrapper.getCategory()); m.setOnAction(event -> {
wrapper.moveTo(storeCategoryWrapper.getCategory());
});
move.getItems().add(m);
}); });
move.getItems().add(m); contextMenu.getItems().add(move);
}); }
contextMenu.getItems().add(move);
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline")); var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(wrapper.getDeletable().not()); del.disableProperty().bind(wrapper.getDeletable().not());

View file

@ -7,7 +7,9 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FilterComp; import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -21,17 +23,28 @@ import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListSideComp extends SimpleComp { public class StoreEntryListSideComp extends SimpleComp {
private Region createGroupListHeader() { private Region createGroupListHeader() {
var label = new Label("Connections"); var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory().getCategory()) ? "Connections" : "Scripts";
}, StoreViewState.get().getActiveCategory()));
label.getStyleClass().add("name"); label.getStyleClass().add("name");
var shownList = BindingsHelper.filteredContentBinding( var all = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(), StoreViewState.get().getAllEntries(),
storeEntryWrapper -> {
var cat = DataStorage.get().getStoreCategoryIfPresent(storeEntryWrapper.getEntry().getCategoryUuid()).orElse(null);
var storeRoot = cat != null ? DataStorage.get().getRootCategory(cat) : null;
return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(storeRoot);
},
StoreViewState.get().getActiveCategory());
var shownList = BindingsHelper.filteredContentBinding(
all,
storeEntryWrapper -> { storeEntryWrapper -> {
return storeEntryWrapper.shouldShow( return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue()); StoreViewState.get().getFilterString().getValue());
}, },
StoreViewState.get().getFilterString()); StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, StoreViewState.get().getAllEntries()); var count = new CountComp<>(shownList, all);
var spacer = new Region(); var spacer = new Region();

View file

@ -12,6 +12,7 @@ import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import lombok.Getter; import lombok.Getter;
import java.time.Duration; import java.time.Duration;
@ -34,8 +35,8 @@ public class StoreEntryWrapper {
private final Property<ActionProvider.DefaultDataStoreCallSite<?>> defaultActionProvider; private final Property<ActionProvider.DefaultDataStoreCallSite<?>> defaultActionProvider;
private final BooleanProperty deletable = new SimpleBooleanProperty(); private final BooleanProperty deletable = new SimpleBooleanProperty();
private final BooleanProperty expanded = new SimpleBooleanProperty(); private final BooleanProperty expanded = new SimpleBooleanProperty();
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
private final Property<Object> persistentState = new SimpleObjectProperty<>(); private final Property<Object> persistentState = new SimpleObjectProperty<>();
private final MapProperty<String, Object> cache = new SimpleMapProperty<>(FXCollections.observableHashMap());
public StoreEntryWrapper(DataStoreEntry entry) { public StoreEntryWrapper(DataStoreEntry entry) {
this.entry = entry; this.entry = entry;
@ -144,6 +145,7 @@ public class StoreEntryWrapper {
expanded.setValue(entry.isExpanded()); expanded.setValue(entry.isExpanded());
observing.setValue(entry.isObserving()); observing.setValue(entry.isObserving());
persistentState.setValue(entry.getStorePersistentState()); persistentState.setValue(entry.getStorePersistentState());
cache.putAll(entry.getStoreCache());
inRefresh.setValue(entry.isInRefresh()); inRefresh.setValue(entry.isInRefresh());
deletable.setValue(entry.getConfiguration().isDeletable() deletable.setValue(entry.getConfiguration().isDeletable()

View file

@ -2,8 +2,8 @@ package io.xpipe.app.comp.storage.store;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.StoreCategoryListComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.util.FeatureProvider;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -17,7 +17,7 @@ public class StoreSidebarComp extends SimpleComp {
var sideBar = new VerticalComp(List.of( var sideBar = new VerticalComp(List.of(
new StoreEntryListSideComp(), new StoreEntryListSideComp(),
new StoreSortComp(), new StoreSortComp(),
FeatureProvider.get().organizationComp(), new StoreCategoryListComp(),
Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar"))); Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar")));
sideBar.apply(struc -> struc.get().setFillWidth(true)); sideBar.apply(struc -> struc.get().setFillWidth(true));
sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(2), Priority.ALWAYS)); sideBar.apply(s -> VBox.setVgrow(s.get().getChildren().get(2), Priority.ALWAYS));

View file

@ -48,7 +48,7 @@ public class StoreViewState {
} catch (Exception exception) { } catch (Exception exception) {
tl = new StoreSection(null, FXCollections.emptyObservableList(), FXCollections.emptyObservableList(), 0); tl = new StoreSection(null, FXCollections.emptyObservableList(), FXCollections.emptyObservableList(), 0);
categories.setAll(new StoreCategoryWrapper(DataStorage.get().getAllCategory())); categories.setAll(new StoreCategoryWrapper(DataStorage.get().getAllCategory()));
activeCategory.setValue(getAllCategory()); activeCategory.setValue(getAllConnectionsCategory());
ErrorEvent.fromThrowable(exception).handle(); ErrorEvent.fromThrowable(exception).handle();
} }
topLevelSection = tl; topLevelSection = tl;
@ -58,6 +58,17 @@ public class StoreViewState {
Comparator<StoreCategoryWrapper> comparator = new Comparator<>() { Comparator<StoreCategoryWrapper> comparator = new Comparator<>() {
@Override @Override
public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) { public int compare(StoreCategoryWrapper o1, StoreCategoryWrapper o2) {
var o1Root = o1.getRoot();
var o2Root = o2.getRoot();
if (o1Root.equals(getAllConnectionsCategory().getCategory()) && !o1Root.equals(o2Root)) {
return -1;
}
if (o2Root.equals(getAllConnectionsCategory().getCategory()) && !o1Root.equals(o2Root)) {
return 1;
}
if (o1.getParent() == null && o2.getParent() == null) { if (o1.getParent() == null && o2.getParent() == null) {
return 0; return 0;
} }
@ -81,19 +92,18 @@ public class StoreViewState {
return categories.sorted(comparator); return categories.sorted(comparator);
} }
public StoreCategoryWrapper getAllCategory() { public StoreCategoryWrapper getAllConnectionsCategory() {
return categories.stream() return categories.stream()
.filter(storeCategoryWrapper -> .filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CATEGORY_UUID)) storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
} }
public StoreCategoryWrapper getAllScriptsCategory() {
public StoreCategoryWrapper getScriptsCategory() {
return categories.stream() return categories.stream()
.filter(storeCategoryWrapper -> .filter(storeCategoryWrapper ->
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.SCRIPTS_CATEGORY_UUID)) storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
} }

View file

@ -64,8 +64,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
Property<DataStore> store, Property<DataStore> store,
Predicate<DataStoreProvider> filter, Predicate<DataStoreProvider> filter,
String initialName, String initialName,
boolean exists, boolean staticDisplay boolean exists,
) { boolean staticDisplay) {
this.parent = parent; this.parent = parent;
this.provider = provider; this.provider = provider;
this.store = store; this.store = store;
@ -115,7 +115,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
} }
}); });
}, },
true, true); true,
true);
} }
public static void showCreation(DataStoreProvider selected, Predicate<DataStoreProvider> filter) { public static void showCreation(DataStoreProvider selected, Predicate<DataStoreProvider> filter) {
@ -134,7 +135,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
ErrorEvent.fromThrowable(ex).handle(); ErrorEvent.fromThrowable(ex).handle();
} }
}, },
false, false); false,
false);
} }
public static void show( public static void show(
@ -155,8 +157,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
window -> { window -> {
return new MultiStepComp() { return new MultiStepComp() {
private final GuiDsStoreCreator creator = private final GuiDsStoreCreator creator = new GuiDsStoreCreator(
new GuiDsStoreCreator(this, prop, store, filter, initialName, exists, staticDisplay); this, prop, store, filter, initialName, exists, staticDisplay);
@Override @Override
protected List<Entry> setup() { protected List<Entry> setup() {
@ -227,7 +229,21 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
return null; return null;
} }
return DataStoreEntry.createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name.getValue(), store.getValue()); var testE = DataStoreEntry.createNew(
UUID.randomUUID(),
DataStorage.get().getSelectedCategory().getUuid(),
name.getValue(),
store.getValue());
var parent = provider.getValue().getDisplayParent(testE);
return DataStoreEntry.createNew(
UUID.randomUUID(),
parent != null
? parent.getCategoryUuid()
: DataStorage.get()
.getSelectedCategory()
.getUuid(),
name.getValue(),
store.getValue());
}, },
entry) entry)
.build(); .build();

View file

@ -22,6 +22,10 @@ import java.util.List;
public interface DataStoreProvider { public interface DataStoreProvider {
default boolean canMoveCategories() {
return true;
}
default boolean alwaysShowSummary() { default boolean alwaysShowSummary() {
return false; return false;
} }

View file

@ -39,31 +39,16 @@ import java.util.function.Predicate;
public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp { public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
public static <T extends DataStore> DataStoreChoiceComp<T> other( public static <T extends DataStore> DataStoreChoiceComp<T> other(
Property<DataStoreEntryRef<T>> selected, Class<T> clazz, Predicate<DataStoreEntryRef<T>> filter) { Property<DataStoreEntryRef<T>> selected, Class<T> clazz, Predicate<DataStoreEntryRef<T>> filter, StoreCategoryWrapper initialCategory) {
return new DataStoreChoiceComp<>(Mode.OTHER, null, selected, clazz, filter); return new DataStoreChoiceComp<>(Mode.OTHER, null, selected, clazz, filter, initialCategory);
} }
public static DataStoreChoiceComp<ShellStore> proxy(Property<DataStoreEntryRef<ShellStore>> selected) { public static DataStoreChoiceComp<ShellStore> proxy(Property<DataStoreEntryRef<ShellStore>> selected, StoreCategoryWrapper initialCategory) {
return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, null); return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, null, initialCategory);
} }
public static DataStoreChoiceComp<ShellStore> host(Property<DataStoreEntryRef<ShellStore>> selected) { public static DataStoreChoiceComp<ShellStore> host(Property<DataStoreEntryRef<ShellStore>> selected, StoreCategoryWrapper initialCategory) {
return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, null); return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, null, initialCategory);
}
public static DataStoreChoiceComp<ShellStore> environment(
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
return new DataStoreChoiceComp<>(Mode.HOST, self, selected, ShellStore.class, shellStoreDataStoreEntryRef -> shellStoreDataStoreEntryRef.get().getProvider().canHaveSubShells());
}
public static DataStoreChoiceComp<ShellStore> proxy(
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
return new DataStoreChoiceComp<>(Mode.PROXY, self, selected, ShellStore.class, null);
}
public static DataStoreChoiceComp<ShellStore> host(
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
return new DataStoreChoiceComp<>(Mode.HOST, self, selected, ShellStore.class, null);
} }
public enum Mode { public enum Mode {
@ -77,6 +62,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
private final Property<DataStoreEntryRef<T>> selected; private final Property<DataStoreEntryRef<T>> selected;
private final Class<T> storeClass; private final Class<T> storeClass;
private final Predicate<DataStoreEntryRef<T>> applicableCheck; private final Predicate<DataStoreEntryRef<T>> applicableCheck;
private final StoreCategoryWrapper initialCategory;
private Popover popover; private Popover popover;
@ -84,8 +70,9 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
// Rebuild popover if we have a non-null condition to allow for the content to be updated in case the condition // Rebuild popover if we have a non-null condition to allow for the content to be updated in case the condition
// changed // changed
if (popover == null || applicableCheck != null) { if (popover == null || applicableCheck != null) {
var cur = StoreViewState.get().getActiveCategory().getValue();
var selectedCategory = new SimpleObjectProperty<>( var selectedCategory = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue()); initialCategory != null ? (initialCategory.getRoot().equals(cur.getRoot()) ? cur : initialCategory) : cur);
var filterText = new SimpleStringProperty(); var filterText = new SimpleStringProperty();
popover = new Popover(); popover = new Popover();
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> { Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {

View file

@ -1,6 +1,7 @@
package io.xpipe.app.fxcomps.impl; package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.storage.store.StoreCategoryWrapper;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
@ -19,11 +20,15 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
private final ListProperty<DataStoreEntryRef<T>> selectedList; private final ListProperty<DataStoreEntryRef<T>> selectedList;
private final Class<T> storeClass; private final Class<T> storeClass;
private final Predicate<DataStoreEntryRef<T>> applicableCheck; private final Predicate<DataStoreEntryRef<T>> applicableCheck;
private final StoreCategoryWrapper initialCategory;
public DataStoreListChoiceComp(ListProperty<DataStoreEntryRef<T>> selectedList, Class<T> storeClass, Predicate<DataStoreEntryRef<T>> applicableCheck) { public DataStoreListChoiceComp(ListProperty<DataStoreEntryRef<T>> selectedList, Class<T> storeClass, Predicate<DataStoreEntryRef<T>> applicableCheck,
StoreCategoryWrapper initialCategory
) {
this.selectedList = selectedList; this.selectedList = selectedList;
this.storeClass = storeClass; this.storeClass = storeClass;
this.applicableCheck = applicableCheck; this.applicableCheck = applicableCheck;
this.initialCategory = initialCategory;
} }
@Override @Override
@ -42,7 +47,7 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
return hbox; return hbox;
}).padding(new Insets(0)).apply(struc -> struc.get().setMinHeight(0)).apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5)); }).padding(new Insets(0)).apply(struc -> struc.get().setMinHeight(0)).apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5));
var selected = new SimpleObjectProperty<DataStoreEntryRef<T>>(); var selected = new SimpleObjectProperty<DataStoreEntryRef<T>>();
var add = new DataStoreChoiceComp<T>(DataStoreChoiceComp.Mode.OTHER, null, selected, storeClass, applicableCheck); var add = new DataStoreChoiceComp<T>(DataStoreChoiceComp.Mode.OTHER, null, selected, storeClass, applicableCheck, initialCategory);
selected.addListener((observable, oldValue, newValue) -> { selected.addListener((observable, oldValue, newValue) -> {
if (newValue != null) { if (newValue != null) {
if (!selectedList.contains(newValue) if (!selectedList.contains(newValue)

View file

@ -0,0 +1,162 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.storage.store.StoreCategoryWrapper;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.AppFont;
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.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@EqualsAndHashCode(callSuper = true)
@Value
public class StoreCategoryComp extends SimpleComp {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
StoreCategoryWrapper category;
@Override
protected Region createSimple() {
var i = Bindings.createStringBinding(
() -> {
if (!DataStorage.get().supportsSharing()) {
return "mdal-keyboard_arrow_right";
}
return category.getShare().getValue() ?
"mdi2a-account-convert" : "mdi2a-account-cancel";
},
category.getShare());
var icon = new IconButtonComp(i).apply(struc -> AppFont.small(struc.get())).apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
struc.get().setPadding(new Insets(0, 0, 6, 0));
});
var name = new LazyTextFieldComp(category.nameProperty())
.apply(struc -> {
struc.get().prefWidthProperty().unbind();
struc.get().setPrefWidth(100);
struc.getTextField().minWidthProperty().bind(struc.get().widthProperty());
})
.styleClass("name")
.createRegion();
var showing = new SimpleBooleanProperty();
var settings = new IconButtonComp("mdomz-settings")
.styleClass("settings")
.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> {
var cm = createContextMenu(name);
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = BindingsHelper.filteredContentBinding(
category.getContainedEntries(),
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
var hover = new SimpleBooleanProperty();
var h = new HorizontalComp(List.of(
icon,
Comp.hspacer(4),
Comp.of(() -> name),
Comp.hspacer(),
count.hide(BindingsHelper.persist(hover.or(showing))),
settings.hide(BindingsHelper.persist(hover.not().and(showing.not())))));
h.apply(struc -> hover.bind(struc.get().hoverProperty()));
h.apply(struc -> struc.get().setOnMouseClicked(event -> {
category.select();
event.consume();
}));
h.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 8)));
h.styleClass("category-button");
var l = category.getChildren()
.sorted(Comparator.comparing(
storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
var emptyBinding = Bindings.isEmpty(category.getChildren());
var v = new VerticalComp(List.of(h, Comp.separator().hide(emptyBinding), children.hide(emptyBinding)));
v.styleClass("category");
v.apply(struc -> {
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
struc.get().pseudoClassStateChanged(SELECTED, val.equals(category));
});
});
return v.createRegion();
}
private ContextMenu createContextMenu(Region text) {
var contextMenu = new ContextMenu();
AppFont.normal(contextMenu.getStyleableNode());
var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick"));
newCategory.setOnAction(event -> {
DataStorage.get()
.addStoreCategory(
DataStoreCategory.createNew(category.getCategory().getUuid(), "New category"));
});
contextMenu.getItems().add(newCategory);
var share = new MenuItem();
share.textProperty().bind(Bindings.createStringBinding(() -> {
if (category.getShare().getValue()) {
return AppI18n.get("unshare");
} else {
return AppI18n.get("share");
}
},category.getShare()));
share.graphicProperty().bind(Bindings.createObjectBinding(() -> {
if (category.getShare().getValue()) {
return new FontIcon("mdi2b-block-helper");
} else {
return new FontIcon("mdi2s-share");
}
},category.getShare()));
share.setOnAction(event -> {
category.getShare().setValue(!category.getShare().getValue());
});
contextMenu.getItems().add(share);
var refresh = new MenuItem(AppI18n.get("rename"), new FontIcon("mdal-360"));
refresh.setOnAction(event -> {
text.requestFocus();
});
contextMenu.getItems().add(refresh);
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.setOnAction(event -> {
category.delete();
});
contextMenu.getItems().add(del);
return contextMenu;
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import javafx.scene.layout.Region;
import java.util.List;
public class StoreCategoryListComp extends SimpleComp {
@Override
protected Region createSimple() {
var all = StoreViewState.get().getAllConnectionsCategory();
var scripts = StoreViewState.get().getAllScriptsCategory();
return new VerticalComp(List.of(
new StoreCategoryComp(all),
Comp.vspacer(10),
new StoreCategoryComp(scripts)))
.apply(struc -> struc.get().setFillWidth(true))
.apply(struc -> struc.get().setSpacing(3))
.styleClass("store-category-bar")
.createRegion();
}
}

View file

@ -59,7 +59,7 @@ public class DataStateProviderImpl extends DataStateProvider {
return; return;
} }
var old = entry.get().getStoreCache().put(key, value); entry.get().setStoreCache(key, value);
} }
@Override @Override
@ -73,8 +73,12 @@ public class DataStateProviderImpl extends DataStateProvider {
return def.get(); return def.get();
} }
var result = entry.get().getStoreCache().computeIfAbsent(key, k -> def.get()); var r = entry.get().getStoreCache().get(key);
return c.cast(result); if (r == null) {
r = def .get();
entry.get().setStoreCache(key, r);
}
return c.cast(r);
} }
public boolean isInStorage(DataStore store) { public boolean isInStorage(DataStore store) {

View file

@ -23,8 +23,8 @@ import java.util.stream.Stream;
public abstract class DataStorage { public abstract class DataStorage {
public static final UUID ALL_CATEGORY_UUID = UUID.fromString("bfb0b51a-e7a3-4ce4-8878-8d4cb5828d6c"); public static final UUID ALL_CONNECTIONS_CATEGORY_UUID = UUID.fromString("bfb0b51a-e7a3-4ce4-8878-8d4cb5828d6c");
public static final UUID SCRIPTS_CATEGORY_UUID = UUID.fromString("19024cf9-d192-41a9-88a6-a22694cf716a"); public static final UUID ALL_SCRIPTS_CATEGORY_UUID = UUID.fromString("19024cf9-d192-41a9-88a6-a22694cf716a");
public static final UUID PREDEFINED_SCRIPTS_CATEGORY_UUID = UUID.fromString("5faf1d71-0efc-4293-8b70-299406396973"); public static final UUID PREDEFINED_SCRIPTS_CATEGORY_UUID = UUID.fromString("5faf1d71-0efc-4293-8b70-299406396973");
public static final UUID CUSTOM_SCRIPTS_CATEGORY_UUID = UUID.fromString("d3496db5-b709-41f9-abc0-ee0a660fbab9"); public static final UUID CUSTOM_SCRIPTS_CATEGORY_UUID = UUID.fromString("d3496db5-b709-41f9-abc0-ee0a660fbab9");
public static final UUID DEFAULT_CATEGORY_UUID = UUID.fromString("97458c07-75c0-4f9d-a06e-92d8cdf67c40"); public static final UUID DEFAULT_CATEGORY_UUID = UUID.fromString("97458c07-75c0-4f9d-a06e-92d8cdf67c40");
@ -68,7 +68,7 @@ public abstract class DataStorage {
} }
public DataStoreCategory getAllCategory() { public DataStoreCategory getAllCategory() {
return getStoreCategoryIfPresent(ALL_CATEGORY_UUID).orElseThrow(); return getStoreCategoryIfPresent(ALL_CONNECTIONS_CATEGORY_UUID).orElseThrow();
} }
private static boolean shouldPersist() { private static boolean shouldPersist() {
@ -370,6 +370,17 @@ public abstract class DataStorage {
public abstract boolean supportsSharing(); public abstract boolean supportsSharing();
public DataStoreCategory getRootCategory(DataStoreCategory category) {
DataStoreCategory last = category;
DataStoreCategory p = category;
while ((p = DataStorage.get()
.getStoreCategoryIfPresent(p.getParentCategory())
.orElse(null))
!= null) {
last = p;
}
return last;
}
public Optional<DataStoreCategory> getStoreCategoryIfPresent(UUID uuid) { public Optional<DataStoreCategory> getStoreCategoryIfPresent(UUID uuid) {
if (uuid == null) { if (uuid == null) {
@ -565,7 +576,7 @@ public abstract class DataStorage {
} }
public void deleteStoreCategory(@NonNull DataStoreCategory cat) { public void deleteStoreCategory(@NonNull DataStoreCategory cat) {
if (cat.getUuid().equals(DEFAULT_CATEGORY_UUID) || cat.getUuid().equals(ALL_CATEGORY_UUID)) { if (cat.getUuid().equals(DEFAULT_CATEGORY_UUID) || cat.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID)) {
return; return;
} }

View file

@ -211,6 +211,12 @@ public class DataStoreEntry extends StorageElement {
return new DataStoreEntryRef<T>(this); return new DataStoreEntryRef<T>(this);
} }
public void setStoreCache(String key, Object value) {
if (!Objects.equals(storeCache.put(key, value), value)) {
notifyUpdate();
}
}
public void setStorePersistentState(Object value) { public void setStorePersistentState(Object value) {
var changed = !Objects.equals(storePersistentState, value); var changed = !Objects.equals(storePersistentState, value);
this.storePersistentState = value; this.storePersistentState = value;

View file

@ -150,20 +150,20 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(exception.get()).handle(); ErrorEvent.fromThrowable(exception.get()).handle();
} }
if (getStoreCategoryIfPresent(ALL_CATEGORY_UUID).isEmpty()) { if (getStoreCategoryIfPresent(ALL_CONNECTIONS_CATEGORY_UUID).isEmpty()) {
var cat = DataStoreCategory.createNew(null, ALL_CATEGORY_UUID,"All connections"); var cat = DataStoreCategory.createNew(null, ALL_CONNECTIONS_CATEGORY_UUID, "All connections");
cat.setDirectory(categoriesDir.resolve(ALL_CATEGORY_UUID.toString())); cat.setDirectory(categoriesDir.resolve(ALL_CONNECTIONS_CATEGORY_UUID.toString()));
storeCategories.add(cat); storeCategories.add(cat);
} }
if (getStoreCategoryIfPresent(SCRIPTS_CATEGORY_UUID).isEmpty()) { if (getStoreCategoryIfPresent(ALL_SCRIPTS_CATEGORY_UUID).isEmpty()) {
var cat = DataStoreCategory.createNew(null, SCRIPTS_CATEGORY_UUID,"All scripts"); var cat = DataStoreCategory.createNew(null, ALL_SCRIPTS_CATEGORY_UUID, "All scripts");
cat.setDirectory(categoriesDir.resolve(SCRIPTS_CATEGORY_UUID.toString())); cat.setDirectory(categoriesDir.resolve(ALL_SCRIPTS_CATEGORY_UUID.toString()));
storeCategories.add(cat); storeCategories.add(cat);
} }
if (getStoreCategoryIfPresent(PREDEFINED_SCRIPTS_CATEGORY_UUID).isEmpty()) { if (getStoreCategoryIfPresent(PREDEFINED_SCRIPTS_CATEGORY_UUID).isEmpty()) {
var cat = DataStoreCategory.createNew(SCRIPTS_CATEGORY_UUID, PREDEFINED_SCRIPTS_CATEGORY_UUID,"Predefined"); var cat = DataStoreCategory.createNew(ALL_SCRIPTS_CATEGORY_UUID, PREDEFINED_SCRIPTS_CATEGORY_UUID, "Predefined");
cat.setDirectory(categoriesDir.resolve(PREDEFINED_SCRIPTS_CATEGORY_UUID.toString())); cat.setDirectory(categoriesDir.resolve(PREDEFINED_SCRIPTS_CATEGORY_UUID.toString()));
storeCategories.add(cat); storeCategories.add(cat);
} }
@ -176,7 +176,7 @@ public class StandardStorage extends DataStorage {
Instant.now(), Instant.now(),
Instant.now(), Instant.now(),
true, true,
ALL_CATEGORY_UUID, ALL_CONNECTIONS_CATEGORY_UUID,
StoreSortMode.ALPHABETICAL_ASC, false StoreSortMode.ALPHABETICAL_ASC, false
)); ));
} }
@ -187,9 +187,10 @@ public class StandardStorage extends DataStorage {
if (dataStoreCategory.getParentCategory() != null if (dataStoreCategory.getParentCategory() != null
&& getStoreCategoryIfPresent(dataStoreCategory.getParentCategory()) && getStoreCategoryIfPresent(dataStoreCategory.getParentCategory())
.isEmpty()) { .isEmpty()) {
dataStoreCategory.setParentCategory(ALL_CATEGORY_UUID); dataStoreCategory.setParentCategory(ALL_CONNECTIONS_CATEGORY_UUID);
} else if (dataStoreCategory.getParentCategory() == null && !dataStoreCategory.getUuid().equals(ALL_CATEGORY_UUID) && !dataStoreCategory.getUuid().equals(SCRIPTS_CATEGORY_UUID)) { } else if (dataStoreCategory.getParentCategory() == null && !dataStoreCategory.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID) && !dataStoreCategory.getUuid().equals(
dataStoreCategory.setParentCategory(ALL_CATEGORY_UUID); ALL_SCRIPTS_CATEGORY_UUID)) {
dataStoreCategory.setParentCategory(ALL_CONNECTIONS_CATEGORY_UUID);
} }
}); });

View file

@ -50,7 +50,7 @@ public class DataStoreCategoryChoiceComp extends SimpleComp {
textProperty().unbind(); textProperty().unbind();
if (w != null) { if (w != null) {
textProperty().bind(w.nameProperty()); textProperty().bind(w.nameProperty());
setPadding(new Insets(6, 6, 6, 8 + (indent ? w.getDepth() * 6 : 0))); setPadding(new Insets(6, 6, 6, 8 + (indent ? w.getDepth() * 8 : 0)));
} }
} }
} }

View file

@ -38,8 +38,6 @@ public abstract class FeatureProvider {
public abstract void init(); public abstract void init();
public abstract Comp<?> organizationComp();
public abstract Comp<?> overviewPage(); public abstract Comp<?> overviewPage();
public abstract GitStorageHandler createStorageHandler(); public abstract GitStorageHandler createStorageHandler();

View file

@ -2,6 +2,7 @@ package io.xpipe.app.util;
import io.xpipe.app.comp.base.ListSelectorComp; import io.xpipe.app.comp.base.ListSelectorComp;
import io.xpipe.app.comp.base.MultiStepComp; import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper; import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.ScanProvider; import io.xpipe.app.ext.ScanProvider;
@ -83,11 +84,12 @@ public class ScanAlert {
.name("scanAlertChoiceHeader") .name("scanAlertChoiceHeader")
.description("scanAlertChoiceHeaderDescription") .description("scanAlertChoiceHeaderDescription")
.addComp(new DataStoreChoiceComp<>( .addComp(new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER, DataStoreChoiceComp.Mode.OTHER,
null, null,
entry, entry,
ShellStore.class, ShellStore.class,
store1 -> true) store1 -> true,
StoreViewState.get().getAllConnectionsCategory())
.disable(new SimpleBooleanProperty(initialStore != null))) .disable(new SimpleBooleanProperty(initialStore != null)))
.name("scanAlertHeader") .name("scanAlertHeader")
.description("scanAlertHeaderDescription") .description("scanAlertHeaderDescription")

View file

@ -112,7 +112,7 @@ reportOnGithubDescription=Open a new issue in the GitHub repository
reportErrorDescription=Send an error report with optional user feedback and diagnostics info reportErrorDescription=Send an error report with optional user feedback and diagnostics info
ignoreError=Ignore error ignoreError=Ignore error
ignoreErrorDescription=Ignore this error and continue like nothing happened ignoreErrorDescription=Ignore this error and continue like nothing happened
provideEmail=Email address (optional, in case you want to get notified about fixes) provideEmail=How to contact you (optional, only if you want to get notified about fixes)
additionalErrorInfo=Provide additional information (optional) additionalErrorInfo=Provide additional information (optional)
additionalErrorAttachments=Select attachments (optional) additionalErrorAttachments=Select attachments (optional)
dataHandlingPolicies=Privacy policy dataHandlingPolicies=Privacy policy

View file

@ -84,7 +84,7 @@
.browser .bookmark-list *.scroll-bar:horizontal *.thumb, .browser .bookmark-list *.scroll-bar:horizontal *.thumb,
.browser .bookmark-list *.scroll-bar:horizontal *.increment-button, .browser .bookmark-list *.scroll-bar:horizontal *.increment-button,
.browser .bookmark-list *.scroll-bar:horizontal *.decrement-button, .browser .bookmark-list *.scroll-bar:horizontal *.decrement-button,
.browser .bookmark-list *.scroll-bar:horizontal *.increment-arrow, .browser .bookmark-list *.scroll-bar:horizontal *.increment-arrow,
.browser .bookmark-list *.scroll-bar:horizontal *.decrement-arrow { .browser .bookmark-list *.scroll-bar:horizontal *.decrement-arrow {
-fx-background-color: null; -fx-background-color: null;
-fx-background-radius: 0; -fx-background-radius: 0;

View file

@ -5,7 +5,7 @@ import io.xpipe.core.store.DataStore;
public interface GroupStore<T extends DataStore> extends DataStore { public interface GroupStore<T extends DataStore> extends DataStore {
DataStoreEntryRef<T> getParent(); DataStoreEntryRef<? extends T> getParent();
@Override @Override
default void checkComplete() throws Exception { default void checkComplete() throws Exception {

View file

@ -0,0 +1,16 @@
package io.xpipe.ext.base;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.DataStore;
import java.util.UUID;
public interface SelfReferentialStore extends DataStore {
default DataStoreEntry getSelfEntry() {
return DataStorage.get().getStoreEntries().stream().filter(dataStoreEntry -> dataStoreEntry.getStore() == this).findFirst().orElseGet(() -> {
return DataStoreEntry.createNew(UUID.randomUUID(),DataStorage.DEFAULT_CATEGORY_UUID, "Invalid", this);
});
}
}

View file

@ -1,50 +0,0 @@
package io.xpipe.ext.base.script;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.Validators;
import io.xpipe.core.process.ShellControl;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.Objects;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("multiScript")
public class MultiScriptStore extends ScriptStore {
@Override
public String prepareDumbScript(ShellControl shellControl) {
return getEffectiveScripts().stream().map(scriptStore -> {
return ((ScriptStore) scriptStore.getStore()).prepareDumbScript(shellControl);
}).filter(
Objects::nonNull).findFirst().orElse(null);
}
@Override
public String prepareTerminalScript(ShellControl shellControl) {
return getEffectiveScripts().stream().map(scriptStore -> {
return ((ScriptStore) scriptStore.getStore()).prepareDumbScript(shellControl);
}).filter(
Objects::nonNull).findFirst().orElse(null);
}
@Override
public void checkComplete() throws Exception {
if (scripts != null) {
Validators.contentNonNull(scripts);
for (var script : scripts) {
script.getStore().checkComplete();
}
}
}
@Override
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
return scripts != null ? scripts.stream().filter(scriptStore -> scriptStore != null).toList() : List.of();
}
}

View file

@ -1,117 +0,0 @@
package io.xpipe.ext.base.script;
import io.xpipe.app.comp.storage.store.DenseStoreEntryComp;
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
import io.xpipe.app.comp.storage.store.StoreSection;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.fxcomps.impl.DataStoreListChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.Identifiers;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
public class MultiScriptStoreProvider implements DataStoreProvider {
@Override
public Comp<?> customEntryComp(StoreSection s, boolean preferLarge) {
return new DenseStoreEntryComp(s.getWrapper(),true, null);
}
@Override
public boolean alwaysShowSummary() {
return true;
}
@Override
public boolean shouldHaveChildren() {
return false;
}
@Override
public boolean shouldEdit() {
return true;
}
@Override
public boolean isShareable() {
return true;
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.SCRIPT;
}
@Override
public String getId() {
return "multiScript";
}
@SneakyThrows
@Override
public String getDisplayIconFileName(DataStore store) {
return "proc:shellEnvironment_icon.svg";
}
@SneakyThrows
@Override
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
MultiScriptStore st = store.getValue().asNeeded();
var group = new SimpleObjectProperty<>(st.getGroup());
var others = new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>(st.getEffectiveScripts())));
return new OptionsBuilder()
.name("scriptGroup")
.description("scriptGroupDescription")
.addComp(DataStoreChoiceComp.other(group, ScriptGroupStore.class, null), group)
.name("snippets")
.description("snippetsDependenciesDescription")
.addComp(new DataStoreListChoiceComp<>(others, ScriptStore.class, scriptStore -> !scriptStore.get().equals(entry) && others.stream().noneMatch(scriptStoreDataStoreEntryRef -> scriptStoreDataStoreEntryRef.getStore().equals(scriptStore))), others)
.nonEmpty()
.bind(
() -> {
return MultiScriptStore.builder().group(group.get()).scripts(others.get()).description(st.getDescription()).build();
},
store)
.buildDialog();
}
@Override
public ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
MultiScriptStore st = wrapper.getEntry().getStore().asNeeded();
return new SimpleStringProperty(st.getDescription());
}
@Override
public DataStoreEntry getDisplayParent(DataStoreEntry store) {
MultiScriptStore st = store.getStore().asNeeded();
return st.getGroup().get();
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(MultiScriptStore.class);
}
@Override
public DataStore defaultStore() {
return MultiScriptStore.builder().scripts(List.of()).build();
}
@Override
public List<String> getPossibleNames() {
return Identifiers.get("multiScript");
}
}

View file

@ -7,7 +7,7 @@ import lombok.Setter;
@Getter @Getter
public enum PredefinedScriptGroup { public enum PredefinedScriptGroup {
CLINK("Clink", null), CLINK("Clink", null),
STARSHIP("Starship", "Scripts to enable the starship shell extension"); STARSHIP("Starship", "Sets up and enables the starship shell prompt");
private final String name; private final String name;
private final String description; private final String description;

View file

@ -1,24 +1,40 @@
package io.xpipe.ext.base.script; package io.xpipe.ext.base.script;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.base.GroupStore; import io.xpipe.ext.base.GroupStore;
import io.xpipe.ext.base.SelfReferentialStore;
import lombok.Getter; import lombok.Getter;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.Set;
@Getter @Getter
@SuperBuilder @SuperBuilder
@Jacksonized @Jacksonized
@JsonTypeName("scriptGroup") @JsonTypeName("scriptGroup")
public class ScriptGroupStore extends JacksonizedValue implements GroupStore<DataStore> { public class ScriptGroupStore extends ScriptStore implements GroupStore<ScriptStore>, SelfReferentialStore {
private final String description;
@Override @Override
public DataStoreEntryRef<DataStore> getParent() { public DataStoreEntryRef<? extends ScriptStore> getParent() {
return null; return group;
}
@Override
public List<SimpleScriptStore> getFlattenedScripts(Set<SimpleScriptStore> seen) {
return getEffectiveScripts().stream().map(scriptStoreDataStoreEntryRef -> {
return scriptStoreDataStoreEntryRef.getStore().getFlattenedScripts(seen);
}).flatMap(List::stream).toList();
}
@Override
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
var self = getSelfEntry();
return DataStorage.get().getStoreChildren(self, true).stream()
.map(dataStoreEntry -> dataStoreEntry.<ScriptStore>ref())
.toList();
} }
} }

View file

@ -4,17 +4,58 @@ import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.storage.store.DenseStoreEntryComp; import io.xpipe.app.comp.storage.store.DenseStoreEntryComp;
import io.xpipe.app.comp.storage.store.StoreEntryWrapper; import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
import io.xpipe.app.comp.storage.store.StoreSection; import io.xpipe.app.comp.storage.store.StoreSection;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.SneakyThrows;
import java.util.List; import java.util.List;
public class ScriptGroupStoreProvider implements DataStoreProvider { public class ScriptGroupStoreProvider implements DataStoreProvider {
@SneakyThrows
@Override
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
ScriptGroupStore st = store.getValue().asNeeded();
var group = new SimpleObjectProperty<>(st.getGroup());
Property<String> description = new SimpleObjectProperty<>(st.getDescription());
return new OptionsBuilder()
.name("description")
.description("descriptionDescription")
.addString(description)
.name("scriptGroup")
.description("scriptGroupDescription")
.addComp(
new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER, null, group, ScriptGroupStore.class, ref->! ref.getEntry().equals(entry), StoreViewState.get().getAllScriptsCategory()),
group)
.nonNull()
.bind(
() -> {
return ScriptGroupStore.builder()
.group(group.get())
.description(st.getDescription())
.build();
},
store)
.buildDialog();
}
@Override
public DataStore defaultStore() {
return ScriptGroupStore.builder().build();
}
@Override @Override
public Comp<?> stateDisplay(StoreEntryWrapper w) { public Comp<?> stateDisplay(StoreEntryWrapper w) {
return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));

View file

@ -13,8 +13,10 @@ import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuperBuilder @SuperBuilder
@ -22,19 +24,22 @@ import java.util.stream.Collectors;
@AllArgsConstructor @AllArgsConstructor
public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore<ScriptStore.State> { public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore<ScriptStore.State> {
public static ShellControl controlWithDefaultScripts(ShellControl pc) { public static ShellControl controlWithDefaultScripts(ShellControl pc) {
return controlWithScripts(pc,getDefaultScripts());
}
public static ShellControl controlWithScripts(ShellControl pc, List<DataStoreEntryRef<ScriptStore>> refs) {
pc.onInit(shellControl -> { pc.onInit(shellControl -> {
var scripts = getDefaultScripts().stream() var scripts = flatten(refs).stream()
.map(simpleScriptStore -> simpleScriptStore.getStore().prepareDumbScript(shellControl)) .map(simpleScriptStore -> simpleScriptStore.prepareDumbScript(shellControl))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
if (!scripts.isBlank()) { if (!scripts.isBlank()) {
shellControl.executeSimpleBooleanCommand(scripts); shellControl.executeSimpleBooleanCommand(scripts);
} }
var terminalCommands = getDefaultScripts().stream() var terminalCommands = flatten(refs).stream()
.map(simpleScriptStore -> simpleScriptStore.getStore().prepareTerminalScript(shellControl)) .map(simpleScriptStore -> simpleScriptStore.prepareTerminalScript(shellControl))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
if (!terminalCommands.isBlank()) { if (!terminalCommands.isBlank()) {
@ -44,14 +49,21 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
return pc; return pc;
} }
public static List<DataStoreEntryRef<ScriptStore>> getDefaultScripts() { private static List<DataStoreEntryRef<ScriptStore>> getDefaultScripts() {
var list = DataStorage.get().getStoreEntries().stream() return DataStorage.get().getStoreEntries().stream()
.filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore .filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore
&& scriptStore.getState().isDefault()) && scriptStore.getState().isDefault())
.map(e -> e.<ScriptStore>ref()) .map(e -> e.<ScriptStore>ref())
.toList(); .toList();
// TODO: Make unique }
return list;
public static List<SimpleScriptStore> flatten(List<DataStoreEntryRef<ScriptStore>> scripts) {
var seen = new HashSet<SimpleScriptStore>();
return scripts.stream()
.map(scriptStoreDataStoreEntryRef ->
scriptStoreDataStoreEntryRef.getStore().getFlattenedScripts(seen))
.flatMap(List::stream)
.toList();
} }
protected final DataStoreEntryRef<ScriptGroupStore> group; protected final DataStoreEntryRef<ScriptGroupStore> group;
@ -83,9 +95,11 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
} }
} }
public abstract String prepareDumbScript(ShellControl shellControl); public List<SimpleScriptStore> getFlattenedScripts() {
return getFlattenedScripts(new HashSet<>());
}
public abstract String prepareTerminalScript(ShellControl shellControl); protected abstract List<SimpleScriptStore> getFlattenedScripts(Set<SimpleScriptStore> seen);
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts(); public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
} }

View file

@ -11,10 +11,9 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.Set;
import java.util.stream.Stream;
@SuperBuilder @SuperBuilder
@Getter @Getter
@ -22,37 +21,47 @@ import java.util.function.BiFunction;
@JsonTypeName("script") @JsonTypeName("script")
public class SimpleScriptStore extends ScriptStore { public class SimpleScriptStore extends ScriptStore {
@Override
public String prepareDumbScript(ShellControl shellControl) { public String prepareDumbScript(ShellControl shellControl) {
return assemble(shellControl, ExecutionType.DUMB_ONLY, ScriptStore::prepareDumbScript); return assemble(shellControl, ExecutionType.DUMB_ONLY);
} }
@Override
public String prepareTerminalScript(ShellControl shellControl) { public String prepareTerminalScript(ShellControl shellControl) {
return assemble(shellControl, ExecutionType.TERMINAL_ONLY, ScriptStore::prepareTerminalScript); return assemble(shellControl, ExecutionType.TERMINAL_ONLY);
} }
private String assemble(ShellControl shellControl, ExecutionType type, BiFunction<ScriptStore, ShellControl, String> function) { private String assemble(
var list = new ArrayList<String>(); ShellControl shellControl, ExecutionType type) {
scripts.forEach(scriptStoreDataStoreEntryRef -> { if ((executionType == type || executionType == ExecutionType.BOTH)
var s = function.apply(scriptStoreDataStoreEntryRef.getStore(), shellControl); && minimumDialect.isCompatibleTo(shellControl.getShellDialect())) {
if (s != null) {
list.add(s);
}
});
if ((executionType == type || executionType == ExecutionType.BOTH) && minimumDialect.isCompatibleTo(shellControl.getShellDialect())) {
var script = ScriptHelper.createExecScript(minimumDialect, shellControl, commands); var script = ScriptHelper.createExecScript(minimumDialect, shellControl, commands);
list.add(shellControl.getShellDialect().sourceScriptCommand(shellControl, script)); return shellControl.getShellDialect().sourceScriptCommand(shellControl, script);
} }
var cmd = String.join("\n", list); return null;
return cmd.isEmpty() ? null : cmd;
} }
@Override @Override
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() { public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
return scripts != null ? scripts.stream().filter(scriptStore -> scriptStore != null).toList() : List.of(); return scripts != null
? scripts.stream().filter(scriptStore -> scriptStore != null).toList()
: List.of();
}
@Override
public List<SimpleScriptStore> getFlattenedScripts(Set<SimpleScriptStore> seen) {
var isLoop = seen.contains(this);
seen.add(this);
return Stream.concat(
getEffectiveScripts().stream()
.map(scriptStoreDataStoreEntryRef -> {
return scriptStoreDataStoreEntryRef.getStore().getFlattenedScripts(seen).stream()
.filter(simpleScriptStore -> !seen.contains(simpleScriptStore))
.peek(simpleScriptStore -> seen.add(simpleScriptStore))
.toList();
})
.flatMap(List::stream),
isLoop ? Stream.of() : Stream.of(this))
.toList();
} }
@Getter @Getter

View file

@ -7,6 +7,7 @@ import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.storage.store.DenseStoreEntryComp; import io.xpipe.app.comp.storage.store.DenseStoreEntryComp;
import io.xpipe.app.comp.storage.store.StoreEntryWrapper; import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
import io.xpipe.app.comp.storage.store.StoreSection; import io.xpipe.app.comp.storage.store.StoreSection;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.AppExtensionManager; import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.ext.GuiDialog;
@ -50,11 +51,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
return new DenseStoreEntryComp(sec.getWrapper(), true, dropdown); return new DenseStoreEntryComp(sec.getWrapper(), true, dropdown);
} }
@Override
public boolean alwaysShowSummary() {
return true;
}
@Override @Override
public boolean shouldHaveChildren() { public boolean shouldHaveChildren() {
return false; return false;
@ -132,7 +128,8 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
new DataStoreListChoiceComp<>( new DataStoreListChoiceComp<>(
others, others,
ScriptStore.class, ScriptStore.class,
scriptStore -> !scriptStore.get().equals(entry) && !others.contains(scriptStore)), scriptStore -> !scriptStore.get().equals(entry) && !others.contains(scriptStore), StoreViewState.get().getAllScriptsCategory()
),
others) others)
.name("minimumShellDialect") .name("minimumShellDialect")
.description("minimumShellDialectDescription") .description("minimumShellDialectDescription")
@ -160,7 +157,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
.description("scriptGroupDescription") .description("scriptGroupDescription")
.addComp( .addComp(
new DataStoreChoiceComp<>( new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER, null, group, ScriptGroupStore.class, null), DataStoreChoiceComp.Mode.OTHER, null, group, ScriptGroupStore.class, null, StoreViewState.get().getAllScriptsCategory()),
group) group)
.nonNull() .nonNull()
.bind( .bind(
@ -180,31 +177,30 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
} }
@Override @Override
public String summaryString(StoreEntryWrapper wrapper) { public boolean canMoveCategories() {
SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded(); return false;
return (scriptStore.isRequiresElevation() ? "Elevated " : "")
+ (scriptStore.getMinimumDialect() != null
? scriptStore.getMinimumDialect().getDisplayName() + " "
: "")
+ (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY
? "Terminal"
: scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.DUMB_ONLY
? "Background"
: "")
+ " Snippet";
} }
@Override @Override
public ObservableValue<String> informationString(StoreEntryWrapper wrapper) { public ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded(); SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded();
return new SimpleStringProperty(scriptStore.getDescription()); return new SimpleStringProperty((scriptStore.isRequiresElevation() ? "Elevated " : "")
+ (scriptStore.getMinimumDialect() != null
? scriptStore.getMinimumDialect().getDisplayName() + " "
: "")
+ (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY
? "Terminal"
: scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.DUMB_ONLY
? "Background"
: "")
+ " Snippet");
} }
@Override @Override
public void storageInit() throws Exception { public void storageInit() throws Exception {
var cat = DataStorage.get() var cat = DataStorage.get()
.addStoreCategoryIfNotPresent(DataStoreCategory.createNew( .addStoreCategoryIfNotPresent(DataStoreCategory.createNew(
DataStorage.SCRIPTS_CATEGORY_UUID, DataStorage.CUSTOM_SCRIPTS_CATEGORY_UUID, "My scripts")); DataStorage.ALL_SCRIPTS_CATEGORY_UUID, DataStorage.CUSTOM_SCRIPTS_CATEGORY_UUID, "My scripts"));
DataStorage.get() DataStorage.get()
.addStoreEntryIfNotPresent(DataStoreEntry.createNew( .addStoreEntryIfNotPresent(DataStoreEntry.createNew(
UUID.fromString("a9945ad2-db61-4304-97d7-5dc4330691a7"), UUID.fromString("a9945ad2-db61-4304-97d7-5dc4330691a7"),

View file

@ -24,7 +24,9 @@ newDirectory=New directory
copyShareLink=Copy link copyShareLink=Copy link
selectStore=Select Store selectStore=Select Store
saveSource=Save for later saveSource=Save for later
deleteChildren=Remove children execute=Execute
deleteChildren=Remove all children
descriptionDescription=Give this group an optional description
selectSource=Select Source selectSource=Select Source
commandLineRead=Update commandLineRead=Update
commandLineWrite=Write commandLineWrite=Write