Rework wsl and k8s system

This commit is contained in:
crschnick 2023-06-27 00:04:02 +00:00
parent 0b6aee858c
commit 9321af9998
26 changed files with 711 additions and 175 deletions

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.StackPane;
@ -27,8 +28,6 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
var loading = new RingProgressIndicator(0, false);
loading.setProgress(-1);
loading.setPrefWidth(50);
loading.setPrefHeight(50);
var loadingBg = new StackPane(loading);
loadingBg.getStyleClass().add("loading-comp");
@ -69,7 +68,14 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
};
PlatformThread.sync(showLoading).addListener(listener);
var stack = new StackPane(compStruc.get(), loadingBg);
var r = compStruc.get();
var stack = new StackPane(r, loadingBg);
loading.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
return Math.min(r.getHeight() - 20, 50);
}, r.heightProperty()));
loading.prefHeightProperty().bind(loading.prefWidthProperty());
return new SimpleCompStructure<>(stack);
}
}

View file

@ -0,0 +1,35 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.ToggleSwitch;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
public class NamedToggleComp extends SimpleComp {
private final BooleanProperty selected;
private final ObservableValue<String> name;
public NamedToggleComp(BooleanProperty selected, ObservableValue<String> name) {
this.selected = selected;
this.name = name;
}
@Override
protected Region createSimple() {
var s = new ToggleSwitch();
s.setSelected(selected.getValue());
s.selectedProperty().addListener((observable, oldValue, newValue) -> {
selected.set(newValue);
});
selected.addListener((observable, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
s.setSelected(newValue);
});
});
s.textProperty().bind(PlatformThread.sync(name));
return s;
}
}

View file

@ -50,7 +50,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true);
comboBox.setAccessibleNames(dataStoreProvider -> dataStoreProvider.getDisplayName());
getProviders().stream()
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.shouldShow())
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.canManuallyCreate())
.forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("data-source-type");

View file

@ -7,7 +7,6 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.DesktopHelper;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ContextMenu;
@ -63,7 +62,6 @@ public class SourceEntryContextMenu<S extends CompStructure<?>> extends ContextM
var validate = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
validate.setOnAction(event -> {
DataStorage.get().refreshAsync(entry.getEntry(), true);
});
cm.getItems().add(validate);

View file

@ -0,0 +1,109 @@
package io.xpipe.app.comp.storage.store;
import atlantafx.base.controls.Spacer;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import lombok.SneakyThrows;
public class DenseStoreEntryComp extends StoreEntryComp {
private final boolean showIcon;
private final Comp<?> content;
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
super(entry);
this.showIcon = showIcon;
this.content = content;
}
protected Region createContent() {
var name = createName().createRegion();
var size = createInformation();
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
AppFont.small(date);
date.getStyleClass().add("date");
var grid = new GridPane();
if (showIcon) {
var storeIcon = createIcon(30, 25);
grid.getColumnConstraints().add(new ColumnConstraints(45));
grid.add(storeIcon, 0, 0);
GridPane.setHalignment(storeIcon, HPos.CENTER);
} else {
grid.add(new Region(), 0, 0);
grid.getColumnConstraints().add(new ColumnConstraints(5));
}
var fill = new ColumnConstraints();
fill.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(new ColumnConstraints(450), fill);
grid.add(name, 1, 0);
var c = content != null ? content.createRegion() : new Region();
grid.add(c, 2, 0);
GridPane.setHalignment(c, HPos.CENTER);
grid.add(createButtonBar().createRegion(), 3, 0, 1, 1);
GrowAugment.create(true, false).augment(grid);
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-grid");
applyState(grid);
var button = new JFXButton();
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.getStyleClass().add("condensed-store-entry-comp");
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
HBox.setHgrow(button, Priority.ALWAYS);
new ContextMenuAugment<>(() -> DenseStoreEntryComp.this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
return new HBox(button, new Spacer(25));
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
}
}

View file

@ -0,0 +1,304 @@
package io.xpipe.app.comp.storage.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
public class StandardStoreEntryComp extends SimpleComp {
public static Comp<?> customSection(StoreEntryWrapper e) {
var prov = e.getEntry().getProvider();
if (prov != null) {
return prov.customDisplay(e);
} else {
return new StandardStoreEntryComp(e);
}
}
private static final double NAME_WIDTH = 0.30;
private static final double STORE_TYPE_WIDTH = 0.08;
private static final double DETAILS_WIDTH = 0.52;
private static final double BUTTONS_WIDTH = 0.1;
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
private final StoreEntryWrapper entry;
public StandardStoreEntryComp(StoreEntryWrapper entry) {
this.entry = entry;
}
private Label createInformation() {
var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
information.getStyleClass().add("information");
AppFont.header(information);
return information;
}
private Label createSummary() {
var summary = new Label();
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
summary.getStyleClass().add("summary");
AppFont.small(summary);
return summary;
}
private void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
switch (val) {
case LOAD_FAILED -> {
node.pseudoClassStateChanged(FAILED, true);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
case INCOMPLETE -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, true);
}
default -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
}
});
}
private Comp<?> createName() {
var name = new LabelComp(entry.nameProperty())
.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
name.apply(s -> AppFont.header(s.get()));
return name;
}
private Node createIcon() {
var img = entry.isDisabled()
? "disabled_icon.png"
: entry.getEntry()
.getProvider()
.getDisplayIconFileName(entry.getEntry().getStore());
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
var storeIcon = imageComp.createRegion();
storeIcon.getStyleClass().add("icon");
if (entry.getState().getValue().isUsable()) {
new FancyTooltipAugment<>(new SimpleStringProperty(
entry.getEntry().getProvider().getDisplayName()))
.augment(storeIcon);
}
return storeIcon;
}
protected Region createContent() {
var name = createName().createRegion();
var size = createInformation();
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
AppFont.small(date);
date.getStyleClass().add("date");
var grid = new GridPane();
var storeIcon = createIcon();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
createShareConstraint(grid, DETAILS_WIDTH), createShareConstraint(grid, BUTTONS_WIDTH));
grid.add(storeIcon, 0, 0, 1, 2);
grid.add(name, 1, 0);
grid.add(date, 1, 1);
grid.add(createSummary(), 2, 1);
grid.add(createInformation(), 2, 0);
grid.add(createButtonBar().createRegion(), 3, 0, 1, 2);
grid.setVgap(5);
GridPane.setHalignment(storeIcon, HPos.CENTER);
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-grid");
applyState(grid);
var button = new JFXButton();
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
new ContextMenuAugment<>(() -> StandardStoreEntryComp.this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
return button;
}
protected Comp<?> createButtonBar() {
var list = new ArrayList<Comp<?>>();
for (var p : entry.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
if (!actionProvider.isMajor()
|| p.getKey().equals(entry.getDefaultActionProvider().getValue())) {
continue;
}
var button = new IconButtonComp(
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
action.execute();
});
});
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
button.hide(Bindings.not(p.getValue()));
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
button.disable(Bindings.not(p.getValue()));
}
list.add(button);
}
var settingsButton = createSettingsButton();
list.add(settingsButton);
return new HorizontalComp(list)
.apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT))
.apply(struc -> {
for (Node child : struc.get().getChildren()) {
((Region) child)
.prefWidthProperty()
.bind((struc.get().heightProperty().divide(1.7)));
((Region) child).prefHeightProperty().bind((struc.get().heightProperty()));
}
});
}
protected Comp<?> createSettingsButton() {
var settingsButton = new IconButtonComp("mdomz-settings");
settingsButton.styleClass("settings");
settingsButton.accessibleText("Settings");
settingsButton.apply(new ContextMenuAugment<>(
event -> event.getButton() == MouseButton.PRIMARY, () -> StandardStoreEntryComp.this.createContextMenu()));
settingsButton.apply(GrowAugment.create(false, true));
settingsButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
settingsButton.apply(new FancyTooltipAugment<>("more"));
return settingsButton;
}
protected ContextMenu createContextMenu() {
var contextMenu = new ContextMenu();
AppFont.normal(contextMenu.getStyleableNode());
for (var p : entry.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
if (actionProvider.isMajor()) {
continue;
}
var name = actionProvider.getName(entry.getEntry().getStore().asNeeded());
var icon = actionProvider.getIcon(entry.getEntry().getStore().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
action.execute();
});
});
item.textProperty().bind(name);
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
item.visibleProperty().bind(p.getValue());
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
item.disableProperty().bind(Bindings.not(p.getValue()));
}
contextMenu.getItems().add(item);
}
if (entry.getActionProviders().size() > 0) {
contextMenu.getItems().add(new SeparatorMenuItem());
}
if (AppPrefs.get().developerMode().getValue()) {
var browse = new MenuItem(AppI18n.get("browse"), new FontIcon("mdi2f-folder-open-outline"));
browse.setOnAction(
event -> DesktopHelper.browsePath(entry.getEntry().getDirectory()));
contextMenu.getItems().add(browse);
}
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
refresh.disableProperty().bind(entry.getRefreshable().not());
refresh.setOnAction(event -> {
DataStorage.get().refreshAsync(entry.getEntry(), true);
});
contextMenu.getItems().add(refresh);
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(entry.getDeletable().not());
del.setOnAction(event -> entry.delete());
contextMenu.getItems().add(del);
return contextMenu;
}
protected ColumnConstraints createShareConstraint(Region r, double share) {
var cc = new ColumnConstraints();
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
return cc;
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
}
}

View file

@ -1,54 +1,59 @@
package io.xpipe.app.comp.storage.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FixedHierarchyStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
public class StoreEntryComp extends SimpleComp {
public abstract class StoreEntryComp extends SimpleComp {
private static final double NAME_WIDTH = 0.30;
private static final double STORE_TYPE_WIDTH = 0.08;
private static final double DETAILS_WIDTH = 0.52;
private static final double BUTTONS_WIDTH = 0.1;
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
private final StoreEntryWrapper entry;
public static Comp<?> customSection(StoreEntryWrapper e) {
var prov = e.getEntry().getProvider();
if (prov != null) {
return prov.customDisplay(e);
} else {
return new StandardStoreEntryComp(e);
}
}
public static final double NAME_WIDTH = 0.30;
public static final double STORE_TYPE_WIDTH = 0.08;
public static final double DETAILS_WIDTH = 0.52;
public static final double BUTTONS_WIDTH = 0.1;
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
protected final StoreEntryWrapper entry;
public StoreEntryComp(StoreEntryWrapper entry) {
this.entry = entry;
}
private Label createInformation() {
protected Label createInformation() {
var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
information.getStyleClass().add("information");
@ -56,7 +61,7 @@ public class StoreEntryComp extends SimpleComp {
return information;
}
private Label createSummary() {
protected Label createSummary() {
var summary = new Label();
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
summary.getStyleClass().add("summary");
@ -64,7 +69,7 @@ public class StoreEntryComp extends SimpleComp {
return summary;
}
private void applyState(Node node) {
protected void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
switch (val) {
case LOAD_FAILED -> {
@ -83,21 +88,41 @@ public class StoreEntryComp extends SimpleComp {
});
}
private Comp<?> createName() {
var name = new LabelComp(entry.nameProperty())
.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
protected Comp<?> createName() {
var filtered = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
other -> other.getEntry().getState().isUsable()
&& entry.getEntry()
.getStore()
.equals(other.getEntry()
.getProvider()
.getLogicalParent(other.getEntry().getStore())));
LabelComp name = new LabelComp(Bindings.createStringBinding(
() -> {
return entry.getName()
+ (entry.getInformation().get() != null
? " [" + entry.getInformation().get() + "]"
: "")
+ (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore
? " (" + filtered.size() + ")"
: "");
},
entry.nameProperty(),
entry.getInformation(),
filtered));
name.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
name.apply(s -> AppFont.header(s.get()));
return name;
}
private Node createIcon() {
protected Node createIcon(int w, int h) {
var img = entry.isDisabled()
? "disabled_icon.png"
: entry.getEntry()
.getProvider()
.getDisplayIconFileName(entry.getEntry().getStore());
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), w, h);
var storeIcon = imageComp.createRegion();
storeIcon.getStyleClass().add("icon");
if (entry.getState().getValue().isUsable()) {
@ -108,68 +133,7 @@ public class StoreEntryComp extends SimpleComp {
return storeIcon;
}
protected Region createContent() {
var name = createName().createRegion();
var size = createInformation();
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
AppFont.small(date);
date.getStyleClass().add("date");
var grid = new GridPane();
var storeIcon = createIcon();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
createShareConstraint(grid, DETAILS_WIDTH), createShareConstraint(grid, BUTTONS_WIDTH));
grid.add(storeIcon, 0, 0, 1, 2);
grid.add(name, 1, 0);
grid.add(date, 1, 1);
grid.add(createSummary(), 2, 1);
grid.add(createInformation(), 2, 0);
grid.add(createButtonBar().createRegion(), 3, 0, 1, 2);
grid.setVgap(5);
GridPane.setHalignment(storeIcon, HPos.CENTER);
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-grid");
applyState(grid);
var button = new JFXButton();
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
new ContextMenuAugment<>(() -> StoreEntryComp.this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
return button;
}
private Comp<?> createButtonBar() {
protected Comp<?> createButtonBar() {
var list = new ArrayList<Comp<?>>();
for (var p : entry.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
@ -198,33 +162,35 @@ public class StoreEntryComp extends SimpleComp {
var settingsButton = createSettingsButton();
list.add(settingsButton);
if (list.size() > 1) {
list.get(0).styleClass(Styles.LEFT_PILL);
for (int i = 1; i < list.size() - 1; i++) {
list.get(i).styleClass(Styles.CENTER_PILL);
}
list.get(list.size() - 1).styleClass(Styles.RIGHT_PILL);
}
list.forEach(comp -> {
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
});
return new HorizontalComp(list)
.apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT))
.apply(struc -> {
for (Node child : struc.get().getChildren()) {
((Region) child)
.prefWidthProperty()
.bind((struc.get().heightProperty().divide(1.7)));
((Region) child).prefHeightProperty().bind((struc.get().heightProperty()));
}
});
struc.get().setAlignment(Pos.CENTER_RIGHT);
struc.get().setPadding(new Insets(5));
})
.styleClass("button-bar");
}
private Comp<?> createSettingsButton() {
protected Comp<?> createSettingsButton() {
var settingsButton = new IconButtonComp("mdomz-settings");
settingsButton.styleClass("settings");
settingsButton.accessibleText("Settings");
settingsButton.apply(new ContextMenuAugment<>(
event -> event.getButton() == MouseButton.PRIMARY, () -> StoreEntryComp.this.createContextMenu()));
settingsButton.apply(GrowAugment.create(false, true));
settingsButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
settingsButton.apply(new FancyTooltipAugment<>("more"));
return settingsButton;
}
private ContextMenu createContextMenu() {
protected ContextMenu createContextMenu() {
var contextMenu = new ContextMenu();
AppFont.normal(contextMenu.getStyleableNode());
@ -279,17 +245,9 @@ public class StoreEntryComp extends SimpleComp {
return contextMenu;
}
private ColumnConstraints createShareConstraint(Region r, double share) {
protected ColumnConstraints createShareConstraint(Region r, double share) {
var cc = new ColumnConstraints();
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
return cc;
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
}
}

View file

@ -23,7 +23,7 @@ public class StoreEntryListComp extends SimpleComp {
.getFilterString()
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> {
return new StoreEntrySection(e);
return StoreSection.customSection(e);
});
return content.styleClass("store-list-comp").styleClass(Styles.STRIPED);
}

View file

@ -14,17 +14,17 @@ import javafx.scene.paint.Color;
import java.util.List;
public class StoreEntrySection extends Comp<CompStructure<VBox>> {
public class StoreEntrySectionComp extends Comp<CompStructure<VBox>> {
private final StoreSection section;
public StoreEntrySection(StoreSection section) {
public StoreEntrySectionComp(StoreSection section) {
this.section = section;
}
@Override
public CompStructure<VBox> createBase() {
var root = new StoreEntryComp(section.getWrapper()).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
var root = StandardStoreEntryComp.customSection(section.getWrapper()).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
var button = new IconButtonComp(
Bindings.createStringBinding(
() -> section.getWrapper().getExpanded().get()
@ -51,7 +51,7 @@ public class StoreEntrySection extends Comp<CompStructure<VBox>> {
.getFilterString()
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
var content = new ListBoxViewComp<>(shown, all, (StoreSection e) -> {
return new StoreEntrySection(e).apply(GrowAugment.create(true, false));
return StoreSection.customSection(e).apply(GrowAugment.create(true, false));
})
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
.apply(struc -> struc.get().backgroundProperty().set(Background.fill(Color.color(0, 0, 0, 0.01))));
@ -62,7 +62,8 @@ public class StoreEntrySection extends Comp<CompStructure<VBox>> {
return padding;
});
return new VerticalComp(List.of(
new HorizontalComp(topEntryList),
new HorizontalComp(topEntryList)
.apply(struc -> struc.get().setFillHeight(true)),
new HorizontalComp(List.of(spacer, content))
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(

View file

@ -8,6 +8,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.FixedHierarchyStore;
import javafx.beans.property.*;
import lombok.Getter;
@ -94,10 +95,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
disabled.setValue(entry.isDisabled());
state.setValue(entry.getState());
expanded.setValue(entry.isExpanded());
information.setValue(
entry.getInformation() != null
? entry.getInformation()
: entry.isDisabled() ? null : entry.getProvider().getDisplayName());
information.setValue(entry.getInformation());
loading.setValue(entry.getState() == DataStoreEntry.State.VALIDATING);
if (entry.getState().isUsable()) {
@ -178,6 +176,8 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
if (found != null) {
entry.updateLastUsed();
found.createAction(entry.getStore().asNeeded()).execute();
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
DataStorage.get().refreshChildren(entry);
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
@ -14,6 +15,15 @@ import java.util.Comparator;
@Value
public class StoreSection implements StorageFilter.Filterable {
public static Comp<?> customSection(StoreSection e) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customContainer(e);
} else {
return new StoreEntrySectionComp(e);
}
}
StoreEntryWrapper wrapper;
ObservableList<StoreSection> children;
@ -36,7 +46,7 @@ public class StoreSection implements StorageFilter.Filterable {
var parent = section.getWrapper()
.getEntry()
.getProvider()
.getParent(section.getWrapper().getEntry().getStore());
.getLogicalParent(section.getWrapper().getEntry().getStore());
return parent == null
|| (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
});
@ -56,7 +66,7 @@ public class StoreSection implements StorageFilter.Filterable {
.getStore()
.equals(other.getEntry()
.getProvider()
.getParent(other.getEntry().getStore())));
.getLogicalParent(other.getEntry().getStore())));
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
return new StoreSection(e, ordered);

View file

@ -15,7 +15,7 @@ public class ListStoresExchangeImpl extends ListStoresExchange
public Response handleRequest(BeaconHandler handler, Request msg) {
DataStorage s = DataStorage.get();
var e = s.getStoreEntries().stream()
.filter(entry -> !entry.isDisabled() && entry.getProvider().shouldShow())
.filter(entry -> !entry.isDisabled() && entry.getProvider().canManuallyCreate())
.sorted(Comparator.comparing(dataStoreEntry -> dataStoreEntry.getLastUsed()))
.map(col -> StoreListEntry.builder()
.name(col.getName())

View file

@ -24,7 +24,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
.map(p -> ProviderEntry.builder()
.id(p.getId())
.description(p.getDisplayDescription())
.hidden(!p.shouldShow())
.hidden(!p.canManuallyCreate())
.build())
.toList()));

View file

@ -1,13 +1,14 @@
package io.xpipe.app.ext;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.comp.storage.store.StandardStoreEntryComp;
import io.xpipe.app.comp.storage.store.StoreEntrySectionComp;
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
import io.xpipe.app.comp.storage.store.StoreSection;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.store.*;
import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
@ -31,6 +32,14 @@ public interface DataStoreProvider {
}
}
default Comp<?> customDisplay(StoreEntryWrapper w) {
return new StandardStoreEntryComp(w);
}
default Comp<?> customContainer(StoreSection section) {
return new StoreEntrySectionComp(section);
}
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
var content = Bindings.createStringBinding(
() -> {
@ -77,10 +86,14 @@ public interface DataStoreProvider {
return DisplayCategory.OTHER;
}
default DataStore getParent(DataStore store) {
default DataStore getLogicalParent(DataStore store) {
return null;
}
default DataStore getDisplayParent(DataStore store) {
return getLogicalParent(store);
}
default GuiDialog guiDialog(Property<DataStore> store) {
return null;
}
@ -129,7 +142,13 @@ public interface DataStoreProvider {
return null;
}
DataStore defaultStore();
default boolean requiresFrequentRefresh() {
return getStoreClasses().stream().anyMatch(aClass -> FixedHierarchyStore.class.isAssignableFrom(aClass));
}
default DataStore defaultStore() {
return null;
}
List<String> getPossibleNames();
@ -139,7 +158,7 @@ public interface DataStoreProvider {
List<Class<?>> getStoreClasses();
default boolean shouldShow() {
default boolean canManuallyCreate() {
return true;
}

View file

@ -1,6 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import com.jfoenix.controls.JFXButton;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
@ -9,9 +9,10 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.scene.control.Button;
import org.kordamp.ikonli.javafx.FontIcon;
public class IconButtonComp extends Comp<CompStructure<JFXButton>> {
public class IconButtonComp extends Comp<CompStructure<Button>> {
private final ObservableValue<String> icon;
private final Runnable listener;
@ -30,8 +31,9 @@ public class IconButtonComp extends Comp<CompStructure<JFXButton>> {
}
@Override
public CompStructure<JFXButton> createBase() {
var button = new JFXButton();
public CompStructure<Button> createBase() {
var button = new Button();
button.getStyleClass().add(Styles.FLAT);
var fi = new FontIcon(icon.getValue());
fi.setFocusTraversable(false);

View file

@ -97,19 +97,23 @@ public abstract class DataStorage {
}
public synchronized void refreshChildren(DataStoreEntry e) {
refreshChildren(e, List.of());
}
public synchronized void refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
if (!(e.getStore() instanceof FixedHierarchyStore)) {
return;
}
try {
var newChildren = ((FixedHierarchyStore) e.getStore()).listChildren();
deleteChildren(e, true);
newChildren.forEach((key, value) -> {
try {
addStoreEntry(key, value);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
oldChildren.stream().filter(entry -> !newChildren.containsValue(entry.getStore())).forEach(entry -> {
deleteChildren(entry, true);
deleteStoreEntry(entry);
});
newChildren.entrySet().stream().filter(entry -> oldChildren.stream().noneMatch(old -> old.getStore().equals(entry.getValue()))).forEach(entry -> {
addStoreEntryIfNotPresent(entry.getKey(), entry.getValue());
});
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
@ -133,8 +137,8 @@ public abstract class DataStorage {
return false;
}
var parent = other.getProvider().getParent(other.getStore());
return entry.getStore().equals(parent);
var parent = other.getProvider().getLogicalParent(other.getStore());
return Objects.equals(entry.getStore(), parent);
})
.toList());
@ -386,11 +390,28 @@ public abstract class DataStorage {
latest = e;
}
public void refreshAsync(StorageElement element, boolean deep) {
public void setAndRefreshAsync(DataStoreEntry entry, DataStore s) {
ThreadHelper.runAsync(() -> {
var old = entry.getStore();
var children = getStoreChildren(entry, false);
try {
entry.setStoreInternal(s);
entry.refresh(true);
// Update old children
children.forEach(entry1 -> propagateUpdate(entry1));
DataStorage.get().refreshChildren(entry, children);
} catch (Exception e) {
entry.setStoreInternal(old);
entry.simpleRefresh();
}
});
}
public void refreshAsync(DataStoreEntry element, boolean deep) {
ThreadHelper.runAsync(() -> {
try {
element.refresh(deep);
propagateUpdate();
propagateUpdate(element);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).reportable(false).handle();
}
@ -398,14 +419,11 @@ public abstract class DataStorage {
});
}
private void propagateUpdate() {
for (DataStoreEntry dataStoreEntry : getStoreEntries()) {
dataStoreEntry.simpleRefresh();
}
for (var e : getSourceEntries()) {
e.simpleRefresh();
}
void propagateUpdate(DataStoreEntry origin) {
getStoreChildren(origin, false).forEach(entry -> {
entry.simpleRefresh();
propagateUpdate(entry);
});
}
public void addStoreEntry(@NonNull DataStoreEntry e) {
@ -417,19 +435,21 @@ public abstract class DataStorage {
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
}
propagateUpdate();
propagateUpdate(e);
save();
this.listeners.forEach(l -> l.onStoreAdd(e));
}
public void addStoreEntryIfNotPresent(@NonNull String name, DataStore store) {
if (getStoreEntryIfPresent(store).isPresent()) {
return;
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull String name, DataStore store) {
var found = getStoreEntryIfPresent(store);
if (found.isPresent()) {
return found.get();
}
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
addStoreEntry(e);
return e;
}
public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) {
@ -446,7 +466,7 @@ public abstract class DataStorage {
synchronized (this) {
this.storeEntries.remove(store);
}
propagateUpdate();
propagateUpdate(store);
save();
this.listeners.forEach(l -> l.onStoreRemove(store));
}

View file

@ -10,7 +10,6 @@ import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedHierarchyStore;
import io.xpipe.core.util.JacksonMapper;
import lombok.*;
import lombok.experimental.NonFinal;
@ -204,6 +203,13 @@ public class DataStoreEntry extends StorageElement {
simpleRefresh();
}
void setStoreInternal(DataStore store) {
this.store = store;
this.storeNode = DataStorageWriter.storeToNode(store);
lastModified = Instant.now();
dirty = true;
}
/*
TODO: Implement singular change functions
*/
@ -244,11 +250,6 @@ public class DataStoreEntry extends StorageElement {
state = State.VALIDATING;
listeners.forEach(l -> l.onUpdate());
store.validate();
if (store instanceof FixedHierarchyStore) {
DataStorage.get().refreshChildren(this);
}
state = State.COMPLETE_AND_VALID;
information = getProvider().queryInformationString(getStore(), 50);
dirty = true;

View file

@ -40,6 +40,7 @@ open module io.xpipe.app {
exports io.xpipe.app.browser.action;
exports io.xpipe.app.browser;
exports io.xpipe.app.browser.icon;
exports io.xpipe.app.comp.storage.store;
requires com.sun.jna;
requires com.sun.jna.platform;

View file

@ -27,6 +27,10 @@
-fx-padding: 6px 6px 6px 0;
}
.condensed-store-entry-comp {
-fx-padding: 1px 6px 1px 0;
}
.store-entry-comp:hover {
-fx-background-color: -color-neutral-muted;
}
@ -35,6 +39,10 @@
-fx-background-color: -color-neutral-muted;
}
.store-entry-comp .button-bar .button {
-fx-padding: 8px;
}

View file

@ -17,7 +17,7 @@ import java.util.List;
public class FileStoreProvider implements DataStoreProvider {
@Override
public boolean shouldShow() {
public boolean canManuallyCreate() {
return false;
}

View file

@ -19,7 +19,7 @@ import java.util.List;
public class InMemoryStoreProvider implements DataStoreProvider {
@Override
public boolean shouldShow() {
public boolean canManuallyCreate() {
return false;
}

View file

@ -11,7 +11,7 @@ import java.util.List;
public class InternalStreamProvider implements DataStoreProvider {
@Override
public boolean shouldShow() {
public boolean canManuallyCreate() {
return false;
}

View file

@ -42,7 +42,7 @@ public class SinkDrainStoreProvider implements DataStoreProvider {
}
@Override
public boolean shouldShow() {
public boolean canManuallyCreate() {
return false;
}

View file

@ -0,0 +1,64 @@
package io.xpipe.ext.base.actions;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.DataStore;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class RefreshStoreAction implements ActionProvider {
@Value
static class Action implements ActionProvider.Action {
DataStoreEntry store;
@Override
public boolean requiresJavaFXPlatform() {
return false;
}
@Override
public void execute() throws Exception {
store.refresh(true);
}
}
@Override
public ActionProvider.DataStoreCallSite<?> getDataStoreCallSite() {
return new ActionProvider.DataStoreCallSite<>() {
@Override
public boolean isMajor() {
return true;
}
@Override
public ActiveType activeType() {
return ActiveType.ALWAYS_ENABLE;
}
@Override
public ActionProvider.Action createAction(DataStore store) {
return new Action(DataStorage.get().getStoreEntry(store));
}
@Override
public Class<DataStore> getApplicableClass() {
return DataStore.class;
}
@Override
public ObservableValue<String> getName(DataStore store) {
return AppI18n.observable("base.refresh");
}
@Override
public String getIcon(DataStore store) {
return "mdal-edit";
}
};
}
}

View file

@ -12,7 +12,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class RefreshAction implements LeafAction {
public class RefreshDirectoryAction implements LeafAction {
public String getId() {
return "refresh";

View file

@ -31,7 +31,7 @@ open module io.xpipe.ext.base {
FollowLinkAction,
BackAction,
ForwardAction,
RefreshAction,
RefreshDirectoryAction,
OpenFileDefaultAction,
OpenFileWithAction,
OpenDirectoryAction,