More fixes

This commit is contained in:
crschnick 2023-07-02 15:31:46 +00:00
parent 0b6aee858c
commit 8ca763f185
146 changed files with 1102 additions and 6976 deletions

View file

@ -2,25 +2,16 @@ package io.xpipe.api.impl;
import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.DataTable;
import io.xpipe.api.connector.XPipeApiConnection;
import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.typed.TypedAbstractReader;
import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTableImpl extends DataSourceImpl implements DataTable {
@ -34,9 +25,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
}
public Stream<TupleNode> stream() {
var iterator = new TableIterator();
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
.onClose(iterator::finish);
return Stream.of();
}
@Override
@ -52,71 +41,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
@Override
public ArrayNode read(int maxRows) {
List<DataStructureNode> nodes = new ArrayList<>();
XPipeApiConnection.execute(con -> {
var req = QueryTableDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxRows(maxRows)
.build();
con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> {
var r = new TypedDataStreamParser(res.getDataType());
r.parseStructures(in, TypedDataStructureNodeReader.of(res.getDataType()), nodes::add);
});
});
return ArrayNode.of(nodes);
}
@Override
public Iterator<TupleNode> iterator() {
return new TableIterator();
}
private class TableIterator implements Iterator<TupleNode> {
private final BeaconConnection connection;
private final TypedDataStreamParser parser;
private final TypedAbstractReader nodeReader;
private TupleNode node;
{
connection = XPipeApiConnection.open();
var req = QueryTableDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxRows(Integer.MAX_VALUE)
.build();
connection.sendRequest(req);
QueryTableDataExchange.Response response = connection.receiveResponse();
nodeReader = TypedDataStructureNodeReader.of(response.getDataType());
parser = new TypedDataStreamParser(response.getDataType());
connection.receiveBody();
}
private void finish() {
connection.close();
}
@Override
public boolean hasNext() {
connection.checkClosed();
try {
node = (TupleNode) parser.parseStructure(connection.getInputStream(), nodeReader);
} catch (IOException e) {
throw new BeaconException(e);
return new Iterator<TupleNode>() {
@Override
public boolean hasNext() {
return false;
}
if (node == null) {
// finish();
@Override
public TupleNode next() {
return null;
}
return node != null;
}
@Override
public TupleNode next() {
connection.checkClosed();
return node;
}
};
}
}

View file

@ -2,25 +2,12 @@ package io.xpipe.api.impl;
import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.DataText;
import io.xpipe.api.connector.XPipeApiConnection;
import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTextImpl extends DataSourceImpl implements DataText {
@ -53,47 +40,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
@Override
public Stream<String> lines() {
var iterator = new Iterator<String>() {
private final BeaconConnection connection;
private final BufferedReader reader;
private String nextValue;
{
connection = XPipeApiConnection.open();
var req = QueryTextDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxLines(-1)
.build();
connection.sendRequest(req);
connection.receiveResponse();
reader = new BufferedReader(new InputStreamReader(connection.receiveBody(), StandardCharsets.UTF_8));
}
private void close() {
connection.close();
}
@Override
public boolean hasNext() {
connection.checkClosed();
try {
nextValue = reader.readLine();
} catch (IOException e) {
throw new BeaconException(e);
}
return nextValue != null;
}
@Override
public String next() {
return nextValue;
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
.onClose(iterator::close);
return Stream.of();
}
@Override

View file

@ -11,6 +11,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedHierarchyStore;
import io.xpipe.core.store.ShellStore;
import javafx.application.Platform;
import javafx.beans.property.*;
@ -130,11 +131,15 @@ final class BrowserBookmarkList extends SimpleComp {
}
ThreadHelper.runFailableAsync(() -> {
BusyProperty.execute(busy, () -> {
getItem().refreshIfNeeded();
});
if (getItem().getEntry().getStore() instanceof ShellStore fileSystem) {
BusyProperty.execute(busy, () -> {
getItem().refreshIfNeeded();
});
model.openFileSystemAsync(null, fileSystem, null, busy);
} else if (getItem().getEntry().getStore() instanceof FixedHierarchyStore) {
BusyProperty.execute(busy, () -> {
getItem().refreshWithChildren();
});
}
});
event.consume();
@ -170,10 +175,14 @@ final class BrowserBookmarkList extends SimpleComp {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
// Don't set image as that would trigger image comp update
// and cells are emptied on each change, leading to unnecessary changes
// img.set(null);
setGraphic(null);
// Use opacity instead of visibility as visibility is kinda bugged with web views
setOpacity(0.0);
setFocusTraversable(false);
setAccessibleText(null);
} else {
@ -190,7 +199,7 @@ final class BrowserBookmarkList extends SimpleComp {
img.set(item.getEntry()
.getProvider()
.getDisplayIconFileName(item.getEntry().getStore()));
setGraphic(imageView);
setOpacity(1.0);
setFocusTraversable(true);
setAccessibleText(
item.getName() + " " + item.getEntry().getProvider().getDisplayName());

View file

@ -6,7 +6,6 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
@ -133,7 +132,7 @@ public final class OpenFileSystemModel {
&& fileSystem.getShell().isPresent()) {
var directory = currentPath.get();
var name = adjustedPath + " - "
+ XPipeDaemon.getInstance().getStoreName(store).orElse("?");
+ DataStorage.get().getStoreDisplayName(store).orElse("?");
ThreadHelper.runFailableAsync(() -> {
if (ShellDialects.ALL.stream()
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
@ -379,8 +378,7 @@ public final class OpenFileSystemModel {
var command = s.control()
.initWith(connection.getShellDialect().getCdCommand(directory))
.prepareTerminalOpen(directory + " - "
+ XPipeDaemon.getInstance()
.getStoreName(store)
+ DataStorage.get().getStoreDisplayName(store)
.orElse("?"));
TerminalHelper.open(directory, command);
}

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -18,6 +19,9 @@ import java.util.function.Function;
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
private final ObservableList<T> shown;
private final ObservableList<T> all;
private final Function<T, Comp<?>> compFunction;
@ -32,30 +36,35 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
public CompStructure<ScrollPane> createBase() {
Map<T, Region> cache = new HashMap<>();
VBox listView = new VBox();
listView.setFocusTraversable(false);
VBox vbox = new VBox();
vbox.getStyleClass().add("content");
vbox.setFocusTraversable(false);
refresh(listView, shown, cache, false);
listView.requestLayout();
refresh(vbox, shown, all, cache, false);
vbox.requestLayout();
shown.addListener((ListChangeListener<? super T>) (c) -> {
refresh(listView, c.getList(), cache, true);
refresh(vbox, c.getList(), all, cache, true);
});
all.addListener((ListChangeListener<? super T>) c -> {
cache.keySet().retainAll(c.getList());
});
var scroll = new ScrollPane(listView);
var scroll = new ScrollPane(vbox);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToWidth(true);
scroll.getStyleClass().add("list-box-view-comp");
return new SimpleCompStructure<>(scroll);
}
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
private void refresh(VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
Runnable update = () -> {
var newShown = c.stream()
// Clear cache of unused values
cache.keySet().removeIf(t -> !all.contains(t));
var newShown = shown.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, compFunction.apply(v).createRegion());
@ -65,6 +74,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
})
.toList();
for (int i = 0; i < newShown.size(); i++) {
var r = newShown.get(i);
r.pseudoClassStateChanged(ODD, false);
r.pseudoClassStateChanged(EVEN, false);
r.pseudoClassStateChanged(i % 2 == 0 ? EVEN : ODD, true);
}
if (!listView.getChildren().equals(newShown)) {
listView.getChildren().setAll(newShown);
listView.layout();

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

@ -0,0 +1,75 @@
package io.xpipe.app.comp.base;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.javafx.StackedFontIcon;
public class SystemStateComp extends SimpleComp {
public SystemStateComp(ObservableValue<String> name, ObservableValue<State> state) {
this.name = name;
this.state = state;
}
public static enum State {
FAILURE,
SUCCESS,
OTHER
}
private final ObservableValue<String> name;
private final ObservableValue<State> state;
@Override
protected Region createSimple() {
var icon = PlatformThread.sync(Bindings.createStringBinding(
() -> {
return state.getValue() == State.FAILURE
? "mdi2l-lightning-bolt"
: state.getValue() == State.SUCCESS ? "mdal-check" : "mdsmz-remove";
},
state));
var fi = new FontIcon();
fi.getStyleClass().add("inner-icon");
SimpleChangeListener.apply(icon, val -> fi.setIconLiteral(val));
var border = new FontIcon("mdi2c-circle-outline");
border.getStyleClass().add("outer-icon");
border.setOpacity(0.5);
var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
var failure = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
var other = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
var pane = new StackedFontIcon();
pane.getChildren().addAll(fi, border);
pane.setAlignment(Pos.CENTER);
var dataClass1 = """
.stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 22px;
}
.stacked-ikonli-font-icon > .inner-icon {
-fx-icon-size: 12px;
}
""";
pane.getStylesheets().add(Styles.toDataURI(dataClass1));
SimpleChangeListener.apply(PlatformThread.sync(state), val -> {
pane.getStylesheets().removeAll(success, failure, other);
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other);
});
new FancyTooltipAugment<>(PlatformThread.sync(name)).augment(pane);
return pane;
}
}

View file

@ -1,101 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.util.CustomComboBoxBuilder;
import javafx.beans.property.Property;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
import java.util.function.Predicate;
public class DataSourceTargetChoiceComp extends Comp<CompStructure<ComboBox<Node>>> {
private final Property<DataSourceTarget> selectedApplication;
private final List<DataSourceTarget> all;
private DataSourceTargetChoiceComp(Property<DataSourceTarget> selectedApplication, List<DataSourceTarget> all) {
this.selectedApplication = selectedApplication;
this.all = all;
}
public static DataSourceTargetChoiceComp create(
Property<DataSourceTarget> selectedApplication, Predicate<DataSourceTarget> filter) {
selectedApplication.addListener((observable, oldValue, val) -> {
AppCache.update("application-last-used", val != null ? val.getId() : null);
});
var all = DataSourceTarget.getAll().stream().filter(filter).toList();
if (selectedApplication.getValue() == null) {
String selectedId = AppCache.get("application-last-used", String.class, () -> null);
var selectedProvider = selectedId != null
? DataSourceTarget.byId(selectedId).filter(filter).orElse(null)
: null;
selectedApplication.setValue(selectedProvider);
}
return new DataSourceTargetChoiceComp(selectedApplication, all);
}
private String getIconCode(DataSourceTarget p) {
return p.getGraphicIcon() != null
? p.getGraphicIcon()
: p.getCategory().equals(DataSourceTarget.Category.PROGRAMMING_LANGUAGE)
? "mdi2c-code-tags"
: "mdral-indeterminate_check_box";
}
private Region createLabel(DataSourceTarget p) {
var g = new FontIcon(getIconCode(p));
var l = new Label(p.getName().getValue(), g);
l.setAlignment(Pos.CENTER);
g.iconColorProperty().bind(l.textFillProperty());
return l;
}
@Override
public CompStructure<ComboBox<Node>> createBase() {
var addMoreLabel = new Label(AppI18n.get("addMore"), new FontIcon("mdmz-plus"));
var builder =
new CustomComboBoxBuilder<>(selectedApplication, app -> createLabel(app), new Label(""), v -> true);
builder.setAccessibleNames(
dataSourceTarget -> dataSourceTarget.getName().getValue());
// builder.addFilter((v, s) -> v.getName().getValue().toLowerCase().contains(s));
builder.addHeader(AppI18n.get("programmingLanguages"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.PROGRAMMING_LANGUAGE))
.forEach(builder::add);
builder.addHeader(AppI18n.get("applications"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.APPLICATION))
.forEach(builder::add);
builder.addHeader(AppI18n.get("other"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.OTHER))
.forEach(builder::add);
// builder.addSeparator();
// builder.addAction(addMoreLabel, () -> {
//
// });
var cb = builder.build();
cb.getStyleClass().add("application-choice-comp");
cb.setMaxWidth(2000);
return new SimpleCompStructure<>(cb);
}
}

View file

@ -1,61 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.source.CollectionReadConnection;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.ArrayList;
public class DsCollectionComp extends Comp<CompStructure<TreeView<String>>> {
private final ObservableValue<CollectionReadConnection> con;
private final ObservableValue<String> value;
public DsCollectionComp(ObservableValue<CollectionReadConnection> con) {
this.con = con;
this.value = new SimpleObjectProperty<>("/");
}
private TreeItem<String> createTree() {
var c = new ArrayList<TreeItem<String>>();
if (con.getValue() != null) {
try {
con.getValue().listEntries().forEach(e -> {
// var item = new TreeItem<String>(e.getFileName());
// c.add(item);
});
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
var ar = new TreeItem<>(value.getValue());
ar.getChildren().setAll(c);
return ar;
}
private void setupListener(TreeView<String> tv) {
ChangeListener<CollectionReadConnection> listener = (c, o, n) -> {
var nt = createTree();
tv.setRoot(nt);
};
con.addListener(listener);
listener.changed(con, null, con.getValue());
}
@Override
public CompStructure<TreeView<String>> createBase() {
var table = new TreeView<String>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
}

View file

@ -1,203 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.DynamicOptionsComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.source.DataSourceId;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.List;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@AllArgsConstructor
@Getter
public class DsDataTransferComp extends SimpleComp {
private final Property<DataSourceEntry> dataSourceEntry;
Property<DataSourceTarget> selectedTarget = new SimpleObjectProperty<>();
Property<DataSourceTarget.InstructionsDisplay> selectedDisplay = new SimpleObjectProperty<>();
List<DataSourceTarget> excludedTargets = new ArrayList<>();
public DsDataTransferComp selectApplication(DataSourceTarget t) {
selectedTarget.setValue(t);
return this;
}
public DsDataTransferComp exclude(DataSourceTarget t) {
excludedTargets.add(t);
return this;
}
public static void showPipeWindow(DataSourceEntry e) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
AppWindowHelper.sideWindow(
AppI18n.get("pipeDataSource"),
window -> {
var ms = new DsDataTransferComp(new SimpleObjectProperty<>(e))
.exclude(DataSourceTarget.byId("base.saveSource")
.orElseThrow());
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();
}
@Override
public boolean canContinue() {
var selected = ms.selectedTarget.getValue();
if (selected == null) {
return false;
}
var validator = ms.selectedDisplay
.getValue()
.getValidator();
if (validator == null) {
return true;
}
return validator.validate();
}
}));
}
@Override
protected void finish() {
var onFinish = ms.getSelectedDisplay()
.getValue()
.getOnFinish();
if (onFinish != null) {
ThreadHelper.runAsync(() -> {
try (var busy = new BusyProperty(loading)) {
onFinish.run();
PlatformThread.runLaterIfNeeded(() -> window.close());
}
});
}
}
};
return multi.apply(s -> {
SimpleChangeListener.apply(ms.getSelectedTarget(), (c) -> {
if (c != null && c.getAccessType() == DataSourceTarget.AccessType.PASSIVE) {
((Region) s.get().getChildren().get(2)).setMaxHeight(0);
((Region) s.get().getChildren().get(2)).setMinHeight(0);
s.get().getChildren().get(2).setVisible(false);
} else {
((Region) s.get().getChildren().get(2)).setMaxHeight(Region.USE_PREF_SIZE);
((Region) s.get().getChildren().get(2)).setMinHeight(Region.USE_PREF_SIZE);
s.get().getChildren().get(2).setVisible(true);
}
});
s.get().setPrefWidth(600);
s.get().setPrefHeight(700);
AppFont.medium(s.get());
});
},
false,
loading)
.show();
});
}
@Override
public Region createSimple() {
ObservableValue<DataSourceId> id = Bindings.createObjectBinding(
() -> {
if (!DataStorage.get().getSourceEntries().contains(dataSourceEntry.getValue())) {
return null;
}
return DataStorage.get().getId(dataSourceEntry.getValue());
},
dataSourceEntry);
var chooser = DataSourceTargetChoiceComp.create(
selectedTarget,
a -> !excludedTargets.contains(a)
&& a.isApplicable(dataSourceEntry.getValue().getSource())
&& a.createRetrievalInstructions(
dataSourceEntry.getValue().getSource(), id)
!= null);
var setupGuideButton = new ButtonComp(
AppI18n.observable("setupGuide"), new FontIcon("mdoal-integration_instructions"), () -> {
Hyperlinks.open(selectedTarget.getValue().getSetupGuideURL());
})
.apply(s -> s.get()
.visibleProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return selectedTarget.getValue() != null
&& selectedTarget.getValue().getSetupGuideURL() != null;
},
selectedTarget)));
var top = new HorizontalComp(List.<Comp<?>>of(
chooser.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)), setupGuideButton))
.apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
struc.get().setSpacing(12);
struc.get().getStyleClass().add("top");
});
// setupGuideButton.prefHeightProperty().bind(chooserR.heightProperty());
var content = new VBox(
new DynamicOptionsComp(List.of(new DynamicOptionsComp.Entry(null, null, top)), false).createRegion(),
new Region());
SimpleChangeListener.apply(selectedTarget, c -> {
if (selectedTarget.getValue() == null) {
content.getChildren().set(1, new Region());
selectedDisplay.setValue(null);
return;
}
var instructions = selectedTarget
.getValue()
.createRetrievalInstructions(dataSourceEntry.getValue().getSource(), id);
content.getChildren().set(1, instructions.getRegion());
VBox.setVgrow(instructions.getRegion(), Priority.ALWAYS);
selectedDisplay.setValue(instructions);
});
content.setSpacing(15);
var r = content;
r.getStyleClass().add("data-source-retrieve");
return r;
}
}

View file

@ -1,88 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.*;
import io.xpipe.core.source.DataSourceType;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import lombok.Getter;
import net.synedra.validatorfx.Check;
import java.util.List;
public class DsProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>>> implements Validatable {
private final DataSourceProvider.Category type;
private final Property<DataSourceProvider<?>> provider;
@Getter
private final Validator validator = new SimpleValidator();
private final Check check;
private final DataSourceType filter;
public DsProviderChoiceComp(
DataSourceProvider.Category type, Property<DataSourceProvider<?>> provider, DataSourceType filter) {
this.type = type;
this.provider = provider;
check = Validator.nonNull(validator, AppI18n.observable("provider"), provider);
this.filter = filter;
}
private Region createDefaultNode() {
return switch (type) {
case STREAM -> JfxHelper.createNamedEntry(
AppI18n.get("anyStream"), AppI18n.get("anyStreamDescription"), "file_icon.png");
case DATABASE -> JfxHelper.createNamedEntry(
AppI18n.get("selectQueryType"), AppI18n.get("selectQueryTypeDescription"), "db_icon.png");
};
}
private List<DataSourceProvider<?>> getProviders() {
return switch (type) {
case STREAM -> DataSourceProviders.getAll().stream()
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get()
|| p.getCategory() == DataSourceProvider.Category.STREAM)
.filter(p -> p.shouldShow(filter))
.toList();
case DATABASE -> DataSourceProviders.getAll().stream()
.filter(p -> p.getCategory() == DataSourceProvider.Category.DATABASE)
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.shouldShow(filter))
.toList();
};
}
private Region createGraphic(DataSourceProvider<?> provider) {
if (provider == null) {
return createDefaultNode();
}
var graphic = provider.getDisplayIconFileName();
return JfxHelper.createNamedEntry(provider.getDisplayName(), provider.getDisplayDescription(), graphic);
}
@Override
public CompStructure<ComboBox<Node>> createBase() {
var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true);
comboBox.setAccessibleNames(
dataSourceProvider -> dataSourceProvider != null ? dataSourceProvider.getDisplayName() : null);
comboBox.add(null);
comboBox.addSeparator();
comboBox.addFilter((v, s) -> v.getDisplayName().toLowerCase().contains(s.toLowerCase()));
getProviders().forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
check.decorates(cb);
cb.getStyleClass().add("data-source-type");
cb.getStyleClass().add("choice-comp");
return new SimpleCompStructure<>(cb);
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
import java.util.HexFormat;
public class DsRawComp extends Comp<CompStructure<TextArea>> {
private final ObservableValue<byte[]> value;
public DsRawComp(ObservableValue<byte[]> value) {
this.value = value;
}
private void setupListener(TextArea ta) {
var format = HexFormat.of().withDelimiter(" ").withUpperCase();
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
ta.textProperty().setValue(format.formatHex(val));
});
}
@Override
public CompStructure<TextArea> createBase() {
var ta = new TextArea();
ta.setWrapText(true);
setupListener(ta);
return new SimpleCompStructure<>(ta);
}
}

View file

@ -1,44 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
public class DsStorageGroupSelector extends SimpleComp {
private final Property<DataSourceCollection> selected;
public DsStorageGroupSelector(Property<DataSourceCollection> selected) {
this.selected = selected;
}
private static Region createGraphic(DataSourceCollection group) {
if (group == null) {
return new Label("<>");
}
var l = new Label(group.getName());
return l;
}
@Override
protected ComboBox<Node> createSimple() {
var comboBox = new CustomComboBoxBuilder<>(
selected, DsStorageGroupSelector::createGraphic, createGraphic(null), v -> true);
comboBox.setAccessibleNames(dataSourceCollection -> dataSourceCollection.getName());
DataStorage.get().getSourceCollections().stream()
.filter(dataSourceCollection ->
!dataSourceCollection.equals(DataStorage.get().getInternalCollection()))
.forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("storage-group-selector");
return cb;
}
}

View file

@ -1,75 +0,0 @@
package io.xpipe.app.comp.source;
import com.jfoenix.controls.JFXTextField;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.core.source.DataSourceId;
import javafx.beans.property.Property;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.util.List;
public class DsStorageTargetComp extends SimpleComp {
private final Property<DataSourceEntry> dataSourceEntry;
private final Property<DataSourceCollection> storageGroup;
private final Property<Boolean> nameValid;
public DsStorageTargetComp(
Property<DataSourceEntry> dataSourceEntry,
Property<DataSourceCollection> storageGroup,
Property<Boolean> nameValid) {
this.dataSourceEntry = dataSourceEntry;
this.storageGroup = storageGroup;
this.nameValid = nameValid;
}
@Override
protected Region createSimple() {
var type = new DataSourceTypeComp(
dataSourceEntry.getValue().getDataSourceType(),
dataSourceEntry.getValue().getSource().getFlow());
type.apply(struc -> struc.get().prefWidthProperty().bind(struc.get().prefHeightProperty()));
type.apply(struc -> struc.get().setPrefHeight(60));
var storageGroupSelector = new DsStorageGroupSelector(storageGroup).apply(s -> {
s.get().setMaxWidth(1000);
HBox.setHgrow(s.get(), Priority.ALWAYS);
});
var splitter = Comp.of(() -> new Label("" + DataSourceId.SEPARATOR)).apply(s -> {});
var name = Comp.of(() -> {
var nameField = new JFXTextField(dataSourceEntry.getValue().getName());
dataSourceEntry.addListener((c, o, n) -> {
nameField.setText(n.getName());
nameValid.setValue(n.getName().trim().length() > 0);
});
nameField.textProperty().addListener((c, o, n) -> {
dataSourceEntry.getValue().setName(n);
});
return nameField;
})
.apply(s -> HBox.setHgrow(s.get(), Priority.ALWAYS));
var right = new HorizontalComp(List.of(storageGroupSelector, splitter, name))
.apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
HBox.setHgrow(struc.get(), Priority.ALWAYS);
})
.styleClass("data-source-id");
return new HorizontalComp(List.of(type, right))
.apply(s -> s.get().setFillHeight(true))
.styleClass("data-source-preview")
.createRegion();
}
}

View file

@ -1,66 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.core.data.node.DataStructureNode;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class DsStructureComp extends Comp<CompStructure<TreeView<String>>> {
private final ObservableValue<DataStructureNode> value;
public DsStructureComp(ObservableValue<DataStructureNode> value) {
this.value = value;
}
private TreeItem<String> createTree(DataStructureNode n, AtomicInteger counter, int max) {
if (n.isArray()) {
var c = new ArrayList<TreeItem<String>>();
for (int i = 0; i < Math.min(n.size(), max - counter.get()); i++) {
var item = createTree(n.at(i), counter, max);
item.setValue("[" + i + "] = " + item.getValue());
c.add(item);
}
var ar = new TreeItem<>("[" + n.size() + "... ]");
ar.getChildren().setAll(c);
return ar;
} else if (n.isTuple()) {
var c = new ArrayList<TreeItem<String>>();
for (int i = 0; i < Math.min(n.size(), max - counter.get()); i++) {
var item = createTree(n.at(i), counter, max);
var key = n.asTuple().getKeyNames().get(i);
item.setValue((key != null ? key : "" + i) + " = " + item.getValue());
c.add(item);
}
var ar = new TreeItem<>("( " + n.size() + "... )");
ar.getChildren().setAll(c);
return ar;
} else {
var ar = new TreeItem<>(n.asValue().asString());
return ar;
}
}
private void setupListener(TreeView<String> tv) {
ChangeListener<DataStructureNode> listener = (c, o, n) -> {
var nt = createTree(n, new AtomicInteger(0), 100);
tv.setRoot(nt);
};
value.addListener(listener);
listener.changed(value, null, value.getValue());
}
@Override
public CompStructure<TreeView<String>> createBase() {
var table = new TreeView<String>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
}

View file

@ -1,93 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.type.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.util.ArrayList;
import java.util.Stack;
public class DsTableComp extends Comp<CompStructure<TableView<DsTableComp.RowWrapper>>> {
private final ObservableValue<ArrayNode> value;
public DsTableComp(ObservableValue<ArrayNode> value) {
this.value = value;
}
private TupleType determineDataType(ArrayNode table) {
if (table == null || table.size() == 0) {
return TupleType.empty();
}
var first = table.at(0);
return (TupleType) first.determineDataType();
}
private void setupListener(TableView<RowWrapper> table) {
SimpleChangeListener.apply(PlatformThread.sync(value), n -> {
table.getItems().clear();
table.getColumns().clear();
var t = determineDataType(n);
var stack = new Stack<ObservableList<TableColumn<RowWrapper, ?>>>();
stack.push(table.getColumns());
t.visit(DataTypeVisitors.table(
tupleName -> {
var current = stack.peek();
stack.push(current.get(current.size() - 1).getColumns());
},
stack::pop,
(name, pointer) -> {
TableColumn<RowWrapper, String> col = new TableColumn<>(name);
col.setCellValueFactory(cellData -> {
var node = pointer.get(n.at(cellData.getValue().rowIndex()));
return new SimpleStringProperty(nodeToString(node));
});
var current = stack.peek();
current.add(col);
}));
var list = new ArrayList<RowWrapper>(n.size());
for (int i = 0; i < n.size(); i++) {
list.add(new RowWrapper(n, i));
}
table.setItems(FXCollections.observableList(list));
});
}
private String nodeToString(DataStructureNode node) {
if (node.isValue()) {
return node.asString();
}
if (node.isArray()) {
return "[...]";
}
if (node.isTuple()) {
return "{...}";
}
return null;
}
@Override
public CompStructure<TableView<RowWrapper>> createBase() {
var table = new TableView<RowWrapper>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
public record RowWrapper(ArrayNode table, int rowIndex) {}
}

View file

@ -1,49 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.core.source.TableMapping;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public class DsTableMappingComp extends SimpleComp {
ObservableValue<TableMapping> mapping;
public DsTableMappingComp(ObservableValue<TableMapping> mapping) {
this.mapping = PlatformThread.sync(mapping);
}
@Override
protected Region createSimple() {
var grid = new GridPane();
grid.getStyleClass().add("table-mapping-comp");
SimpleChangeListener.apply(mapping, val -> {
for (int i = 0; i < val.getInputType().getSize(); i++) {
var input = new LabelComp(val.getInputType().getNames().get(i));
grid.add(input.createRegion(), 0, i);
grid.add(new LabelComp("->").createRegion(), 1, i);
var map = val.map(i).orElse(-1);
var output =
new LabelComp(map != -1 ? val.getOutputType().getNames().get(map) : AppI18n.get("discarded"));
grid.add(output.createRegion(), 2, i);
if (i % 2 != 0) {
grid.getChildren().stream().skip((i * 3L)).forEach(node -> node.getStyleClass()
.add("odd"));
}
}
});
return grid;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
import java.util.List;
public class DsTextComp extends Comp<CompStructure<TextArea>> {
private final ObservableValue<List<String>> value;
public DsTextComp(ObservableValue<List<String>> value) {
this.value = value;
}
private void setupListener(TextArea ta) {
ChangeListener<List<String>> listener = (c, o, n) -> {
ta.textProperty().setValue(String.join("\n", n));
};
value.addListener(listener);
listener.changed(value, null, value.getValue());
}
@Override
public CompStructure<TextArea> createBase() {
var ta = new TextArea();
setupListener(ta);
return new SimpleCompStructure<>(ta);
}
}

View file

@ -1,83 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.Arrays;
public class DsTypeChoiceComp extends Comp<CompStructure<StackPane>> {
private final ObservableValue<? extends DataSource<?>> baseSource;
private final ObservableValue<DataSourceProvider<?>> provider;
private final Property<DataSourceType> selectedType;
public DsTypeChoiceComp(
ObservableValue<? extends DataSource<?>> baseSource,
ObservableValue<DataSourceProvider<?>> provider,
Property<DataSourceType> selectedType) {
this.baseSource = baseSource;
this.provider = provider;
this.selectedType = selectedType;
}
private Region createLabel(DataSourceType p) {
var l = new Label(AppI18n.get(p.name().toLowerCase()), new FontIcon(DataSourceTypeComp.ICONS.get(p)));
l.setAlignment(Pos.CENTER);
return l;
}
@Override
public CompStructure<StackPane> createBase() {
var sp = new StackPane();
Runnable update = () -> {
sp.getChildren().clear();
if (provider.getValue() == null || baseSource.getValue() == null) {
return;
}
var builder = new CustomComboBoxBuilder<>(selectedType, app -> createLabel(app), new Label(""), v -> true);
builder.setAccessibleNames(dataSourceType -> dataSourceType.toString());
builder.add(provider.getValue().getPrimaryType());
var list = Arrays.stream(DataSourceType.values())
.filter(t -> t != provider.getValue().getPrimaryType()
&& provider.getValue()
.supportsConversion(baseSource.getValue().asNeeded(), t))
.toList();
if (list.size() == 0) {
return;
}
list.forEach(t -> builder.add(t));
var cb = builder.build();
cb.getStyleClass().add("data-source-type-choice-comp");
sp.getChildren().add(cb);
};
baseSource.addListener((c, o, n) -> {
update.run();
});
provider.addListener((c, o, n) -> {
update.run();
});
update.run();
return new SimpleCompStructure<>(sp);
}
}

View file

@ -1,278 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.source.*;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GuiDsConfigStep extends MultiStepComp.Step<CompStructure<?>> {
private final Property<? extends DataStore> input;
private final ObservableValue<? extends DataSource<?>> baseSource;
private final Property<? extends DataSource<?>> currentSource;
private final Property<DataSourceProvider<?>> provider;
private final Property<DataSourceType> type;
private final Map<DataSourceType, Comp<?>> previewComps;
private final BooleanProperty loading;
public GuiDsConfigStep(
Property<DataSourceProvider<?>> provider,
Property<? extends DataStore> input,
Property<? extends DataSource<?>> baseSource,
Property<? extends DataSource<?>> currentSource,
Property<DataSourceType> type,
BooleanProperty loading) {
this.input = input;
this.baseSource = baseSource;
this.currentSource = currentSource;
this.type = type;
this.provider = provider;
this.loading = loading;
this.previewComps = new HashMap<>();
Arrays.stream(DataSourceType.values()).forEach(t -> previewComps.put(t, createPreviewComp(t)));
}
@Override
public CompStructure<?> createBase() {
var hide = Bindings.createBooleanBinding(
() -> {
return currentSource.getValue() != null
&& currentSource.getValue().getFlow() == DataFlow.OUTPUT;
},
currentSource);
var top = new HorizontalComp(List.of(createTypeSelectorComp(), Comp.of(Region::new), createRefreshComp()))
.apply(s -> {
HBox.setHgrow(s.get().getChildren().get(1), Priority.ALWAYS);
s.get().setAlignment(Pos.CENTER);
s.get().setSpacing(7);
})
.hide(hide);
var preview = Bindings.createObjectBinding(
() -> {
return previewComps.get(type.getValue()).hide(hide);
},
type);
var layout = new VerticalComp(List.of(top, preview.getValue(), Comp.of(this::createConfigOptions)));
layout.apply(vbox -> {
vbox.get().setAlignment(Pos.CENTER);
VBox.setVgrow(vbox.get().getChildren().get(1), Priority.ALWAYS);
AppFont.small(vbox.get());
currentSource.addListener((c, o, n) -> {
vbox.get().getChildren().set(1, preview.getValue().createRegion());
VBox.setVgrow(vbox.get().getChildren().get(1), Priority.ALWAYS);
});
provider.addListener((c, o, n) -> {
vbox.get().getChildren().set(2, createConfigOptions());
});
})
.styleClass("data-source-config");
provider.addListener((c, o, n) -> {});
return layout.createStructure();
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Region createConfigOptions() {
if (currentSource.getValue() == null || provider.getValue() == null) {
return new Region();
}
Region r = null;
try {
r = ((DataSourceProvider<T>) provider.getValue()).configGui((Property<T>) baseSource, false);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
return r != null ? r : new Region();
}
private Comp<?> createReportComp() {
return new IconButtonComp("mdi2a-alert-circle-outline", () -> {});
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Comp<?> createRefreshComp() {
return new IconButtonComp("mdmz-refresh", () -> {
T src = currentSource.getValue() != null
? currentSource.getValue().asNeeded()
: null;
currentSource.setValue(null);
((Property<T>) currentSource).setValue(src);
})
.shortcut(new KeyCodeCombination(KeyCode.F5));
}
private Comp<?> createTypeSelectorComp() {
return new DsTypeChoiceComp(baseSource, provider, type);
}
private Comp<?> createPreviewComp(DataSourceType t) {
return switch (t) {
case TABLE -> createTablePreviewComp();
case STRUCTURE -> createStructurePreviewComp();
case TEXT -> createTextPreviewComp();
case RAW -> createRawPreviewComp();
case COLLECTION -> createCollectionPreviewComp();
};
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends TextDataSource<DI>> Comp<?> createTextPreviewComp() {
var text = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.TEXT) {
return List.of("");
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.lines().limit(10).toList();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return List.of("");
}
},
currentSource);
return new DsTextComp(text);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends StructureDataSource<DI>> Comp<?> createStructurePreviewComp() {
var structure = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.STRUCTURE) {
return TupleNode.builder().build();
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.read();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return TupleNode.builder().build();
}
},
currentSource);
return new DsStructureComp(structure);
}
@Override
public void onInit() {
// DataSource<?> src = currentSource.getValue() != null ? currentSource.getValue().asNeeded() : null;
// currentSource.setValue(null);
// currentSource.setValue(src != null ? src.asNeeded() : null);
}
private Comp<?> createTablePreviewComp() {
var table = new SimpleObjectProperty<>(ArrayNode.of());
currentSource.addListener((c, o, val) -> {
ThreadHelper.runAsync(() -> {
if (val == null
|| type.getValue() != DataSourceType.TABLE
|| !val.getFlow().hasInput()) {
return;
}
try (var ignored = new BusyProperty(loading);
var con = (TableReadConnection) val.openReadConnection()) {
con.init();
table.set(con.readRows(50));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
table.set(ArrayNode.of());
}
});
});
return new DsTableComp(table);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends RawDataSource<DI>> Comp<?> createRawPreviewComp() {
var bytes = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.RAW) {
return new byte[0];
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.readBytes(1000);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return new byte[0];
}
},
currentSource);
return new DsRawComp(bytes);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends CollectionDataSource<DI>> Comp<?> createCollectionPreviewComp() {
var con = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.COLLECTION) {
return null;
}
/*
TODO: Fix
*/
try {
return ((DS) currentSource.getValue()).openReadConnection();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return null;
}
},
currentSource,
input);
con.addListener((c, o, n) -> {
if (o == null) {
return;
}
try {
o.close();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
return new DsCollectionComp(con);
}
}

View file

@ -1,294 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.scene.control.Alert;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public class GuiDsCreatorMultiStep<DI extends DataStore, DS extends DataSource<DI>> extends MultiStepComp {
private final Stage window;
private final DataSourceEntry editing;
private final DataSourceCollection targetGroup;
private final Property<DataSourceType> dataSourceType;
private final DataSourceProvider.Category category;
private final Property<DataSourceProvider<?>> provider;
private final Property<DI> store;
private final ObjectProperty<DS> baseSource;
private final ObjectProperty<DS> source;
private final BooleanProperty loading = new SimpleBooleanProperty();
private final State state;
private GuiDsCreatorMultiStep(
Stage window,
DataSourceEntry editing,
DataSourceCollection targetGroup,
DataSourceProvider.Category category,
DataSourceProvider<?> provider,
DI store,
DS source,
State state) {
this.window = window;
this.editing = editing;
this.targetGroup = targetGroup;
this.category = category;
this.provider = new SimpleObjectProperty<>(provider);
this.store = new SimpleObjectProperty<>(store);
this.dataSourceType = new SimpleObjectProperty<>(provider != null ? provider.getPrimaryType() : null);
this.baseSource = new SimpleObjectProperty<>(source);
this.source = new SimpleObjectProperty<>(source);
this.state = state;
addListeners();
this.apply(r -> {
r.get().setPrefWidth(AppFont.em(30));
r.get().setPrefHeight(AppFont.em(35));
});
}
public static void showCreation(DataSourceProvider.Category category, DataSourceCollection sourceCollection) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
var stage = AppWindowHelper.sideWindow(
AppI18n.get("newDataSource"),
window -> {
var ms = new GuiDsCreatorMultiStep<>(
window, null, sourceCollection, category, null, null, null, State.CREATE);
loading.bind(ms.loading);
window.setOnCloseRequest(e -> {
if (ms.state == State.CREATE && ms.source.getValue() != null) {
e.consume();
showCloseConfirmAlert(ms, window);
}
});
return ms;
},
false,
loading);
stage.show();
});
}
public static void showEdit(DataSourceEntry entry) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
var stage = AppWindowHelper.sideWindow(
AppI18n.get("editDataSource"),
window -> {
var ms = new GuiDsCreatorMultiStep<>(
window,
entry,
DataStorage.get()
.getCollectionForSourceEntry(entry)
.orElse(null),
entry.getProvider().getCategory(),
entry.getProvider(),
entry.getStore().asNeeded(),
entry.getSource().asNeeded(),
State.EDIT);
loading.bind(ms.loading);
return ms.apply(struc -> ms.next());
},
false,
loading);
stage.show();
});
}
public static Future<Boolean> showForStore(
DataSourceProvider.Category category, DataStore store, DataSourceCollection sourceCollection) {
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
var provider = DataSourceProviders.byPreferredStore(store, null);
Platform.runLater(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("newDataSource"),
window -> {
var gui = new GuiDsCreatorMultiStep<>(
window,
null,
sourceCollection,
category,
provider.orElse(null),
store,
null,
State.CREATE);
gui.completedProperty().addListener((c, o, n) -> {
if (n) {
completableFuture.complete(true);
}
});
window.setOnCloseRequest(e -> {
if (gui.state == State.CREATE && gui.source.getValue() != null) {
e.consume();
showCloseConfirmAlert(gui, window);
}
});
return gui;
},
false,
null);
stage.show();
stage.setOnHiding(e -> {
completableFuture.complete(false);
});
});
return completableFuture;
}
private static void showCloseConfirmAlert(GuiDsCreatorMultiStep<?, ?> ms, Stage s) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmDsCreationAbortTitle"));
alert.setHeaderText(AppI18n.get("confirmDsCreationAbortHeader"));
alert.setContentText(AppI18n.get("confirmDsCreationAbortContent"));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
s.close();
});
}
@SuppressWarnings("unchecked")
private void addListeners() {
this.provider.addListener((c, o, n) -> {
if (n == null) {
this.dataSourceType.setValue(null);
return;
}
if (baseSource.getValue() != null
&& !n.getSourceClass().equals(baseSource.get().getClass())) {
this.baseSource.setValue(null);
}
this.dataSourceType.setValue(n.getPrimaryType());
});
this.store.addListener((c, o, n) -> {
if (n == null) {
return;
}
if (this.provider.getValue() == null) {
this.provider.setValue(
DataSourceProviders.byPreferredStore(n, null).orElse(null));
if (this.provider.getValue() != null) {
try {
this.baseSource.set((DS) provider.getValue().createDefaultSource(n));
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
}
});
this.baseSource.addListener((c, o, n) -> {
if (n == null) {
this.source.set(null);
return;
}
try {
var converted = dataSourceType.getValue() != provider.getValue().getPrimaryType()
? (provider.getValue()).convert(n.asNeeded(), dataSourceType.getValue())
: n;
source.setValue((DS) converted);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
this.dataSourceType.addListener((c, o, n) -> {
if (n == null || source.get() == null) {
return;
}
if (n == this.provider.getValue().getPrimaryType()) {
this.source.set(baseSource.getValue());
return;
}
try {
var conv = this.provider.getValue().convert(baseSource.get().asNeeded(), n);
this.source.set(conv.asNeeded());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
}
@Override
protected Region createStepOverview(Region content) {
var r = super.createStepOverview(content);
AppFont.small(r);
return r;
}
@Override
protected Region createStepNavigation() {
var r = super.createStepNavigation();
AppFont.small(r);
return r;
}
@Override
protected List<Entry> setup() {
var list = new ArrayList<Entry>();
list.add(new Entry(AppI18n.observable("selectInput"), createInputStep()));
list.add(new Entry(
AppI18n.observable("configure"),
new GuiDsConfigStep(provider, store, baseSource, source, dataSourceType, loading)));
switch (state) {
case EDIT -> {}
case CREATE -> {
list.add(new Entry(
AppI18n.observable("target"), new GuiDsCreatorTransferStep(targetGroup, store, source)));
}
}
return list;
}
@SuppressWarnings("unchecked")
private MultiStepComp.Step<?> createInputStep() {
return new GuiDsStoreSelectStep(
this, provider, (ObjectProperty<DataStore>) store, category, baseSource, loading);
}
@Override
protected void finish() {
switch (state) {
case EDIT -> {
editing.setSource(source.get());
}
case CREATE -> {}
}
window.close();
}
public enum State {
EDIT,
CREATE
}
}

View file

@ -1,72 +0,0 @@
package io.xpipe.app.comp.source;
import com.jfoenix.controls.JFXCheckBox;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Label;
import java.util.ArrayList;
import java.util.List;
public class GuiDsCreatorSaveStep extends MultiStepComp.Step<CompStructure<?>> {
private final Property<DataSourceCollection> storageGroup;
private final Property<DataSourceEntry> dataSourceEntry;
private final Property<Boolean> nameValid = new SimpleObjectProperty<>(true);
private final Property<Boolean> storeForLaterUse = new SimpleBooleanProperty(true);
public GuiDsCreatorSaveStep(
Property<DataSourceCollection> storageGroup, Property<DataSourceEntry> dataSourceEntry) {
this.storageGroup = storageGroup;
this.dataSourceEntry = dataSourceEntry;
}
@Override
public CompStructure<?> createBase() {
var storeSwitch = Comp.of(() -> {
var cb = new JFXCheckBox();
cb.selectedProperty().bindBidirectional(storeForLaterUse);
var label = new Label(AppI18n.get("storeForLaterUse"));
label.setGraphic(cb);
return label;
});
var storeSettings = new VerticalComp(List.of(new DsStorageTargetComp(dataSourceEntry, storageGroup, nameValid)))
.apply(struc -> {
var elems = new ArrayList<>(struc.get().getChildren());
if (!storeForLaterUse.getValue()) {
struc.get().getChildren().clear();
}
storeForLaterUse.addListener((c, o, n) -> {
if (n) {
struc.get().getChildren().addAll(elems);
} else {
struc.get().getChildren().clear();
}
});
})
.styleClass("store-options");
var vert = new VerticalComp(List.of(storeSwitch, storeSettings));
vert.styleClass("data-source-save-step");
vert.apply(r -> AppFont.small(r.get()));
return vert.createStructure();
}
@Override
public boolean canContinue() {
return nameValid.getValue();
}
}

View file

@ -1,112 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.List;
import java.util.UUID;
public class GuiDsCreatorTransferStep extends MultiStepComp.Step<CompStructure<?>> {
private final DataSourceCollection targetGroup;
private final Property<? extends DataStore> store;
private final ObjectProperty<? extends DataSource<?>> source;
private final Property<DataSourceEntry> entry = new SimpleObjectProperty<>();
private DsDataTransferComp comp;
public GuiDsCreatorTransferStep(
DataSourceCollection targetGroup,
Property<? extends DataStore> store,
ObjectProperty<? extends DataSource<?>> source) {
this.targetGroup = targetGroup;
this.store = store;
this.source = source;
entry.bind(Bindings.createObjectBinding(
() -> {
if (this.store.getValue() == null || this.source.get() == null) {
return null;
}
var name = DataStorage.get()
.createUniqueSourceEntryName(DataStorage.get().getInternalCollection(), source.get());
var entry = DataSourceEntry.createNew(UUID.randomUUID(), name, this.source.get());
return entry;
},
this.store,
this.source));
}
@Override
public CompStructure<?> createBase() {
comp = new DsDataTransferComp(entry)
.selectApplication(
targetGroup != null
? DataSourceTarget.byId("base.saveSource").orElseThrow()
: null);
var vert = new VerticalComp(List.of(comp.apply(s -> VBox.setVgrow(s.get(), Priority.ALWAYS))));
vert.styleClass("data-source-finish-step");
vert.apply(r -> AppFont.small(r.get()));
return Comp.derive(vert, vBox -> {
var r = new ScrollPane(vBox);
r.setFitToWidth(true);
return r;
})
.createStructure();
}
@Override
public void onInit() {
var e = entry.getValue();
DataStorage.get().add(e, DataStorage.get().getInternalCollection());
}
@Override
public void onBack() {
var e = entry.getValue();
DataStorage.get().deleteSourceEntry(e);
}
@Override
public void onContinue() {
var onFinish = comp.getSelectedDisplay().getValue().getOnFinish();
if (onFinish != null) {
onFinish.run();
}
var e = entry.getValue();
DataStorage.get().deleteSourceEntry(e);
}
@Override
public boolean canContinue() {
var selected = comp.getSelectedTarget().getValue();
if (selected == null) {
return false;
}
var validator = comp.getSelectedDisplay().getValue().getValidator();
if (validator == null) {
return true;
}
return validator.validate();
}
}

View file

@ -1,123 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.source.store.DsDbStoreChooserComp;
import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.scene.control.Separator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.List;
public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? extends Region>> {
private final MultiStepComp parent;
private final Property<DataSourceProvider<?>> provider;
private final Property<DataStore> input;
private final DataSourceProvider.Category category;
private final ObjectProperty<? extends DataSource<?>> baseSource;
private final BooleanProperty loading;
public GuiDsStoreSelectStep(
MultiStepComp parent,
Property<DataSourceProvider<?>> provider,
Property<DataStore> input,
DataSourceProvider.Category category,
ObjectProperty<? extends DataSource<?>> baseSource,
BooleanProperty loading) {
this.parent = parent;
this.provider = provider;
this.input = input;
this.category = category;
this.baseSource = baseSource;
this.loading = loading;
}
private Region createLayout() {
var layout = new BorderPane();
var providerChoice = new DsProviderChoiceComp(category, provider, null);
providerChoice.apply(GrowAugment.create(true, false));
layout.setCenter(createCategoryChooserComp());
var top = new VBox(providerChoice.createRegion(), new Separator());
top.getStyleClass().add("top");
layout.setTop(top);
layout.getStyleClass().add("data-input-creation-step");
return layout;
}
private Region createCategoryChooserComp() {
if (category == DataSourceProvider.Category.STREAM) {
return new DsStreamStoreChoiceComp(input, provider, true, true, DsStreamStoreChoiceComp.Mode.OPEN)
.createRegion();
}
if (category == DataSourceProvider.Category.DATABASE) {
return new DsDbStoreChooserComp(input, provider).createRegion();
}
throw new AssertionError();
}
@Override
public CompStructure<? extends Region> createBase() {
// var bgImg = AppImages.image("plus_bg.jpg");
// var background = new BackgroundImageComp(bgImg)
// .apply(struc -> struc.get().setOpacity(0.1));
var layered = new StackComp(List.of(Comp.of(this::createLayout)));
return layered.createStructure();
}
@Override
public void onContinue() {}
@Override
public boolean canContinue() {
if (input.getValue() == null || provider.getValue() == null) {
return false;
}
if (baseSource.getValue() != null) {
return true;
}
ThreadHelper.runAsync(() -> {
try (var ignored = new BusyProperty(loading)) {
var n = this.input.getValue();
var ds = this.provider.getValue().createDefaultSource(n);
if (ds == null) {
TrackEvent.warn("Default data source is null");
return;
}
PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(ds.asNeeded());
parent.next();
});
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(null);
});
}
});
return false;
}
}

View file

@ -1,107 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.source.TableMapping;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Value
@EqualsAndHashCode(callSuper = true)
public class GuiDsTableMappingConfirmation extends SimpleComp {
ObservableValue<TableMapping> mapping;
public GuiDsTableMappingConfirmation(ObservableValue<TableMapping> mapping) {
this.mapping = PlatformThread.sync(mapping);
}
public static boolean showWindowAndWait(SourceEntryWrapper source, TableMapping mapping) {
var latch = new CountDownLatch(1);
var confirmed = new AtomicBoolean();
AppWindowHelper.showAndWaitForWindow(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("confirmTableMappingTitle"),
window -> {
var ms = new GuiDsTableMappingConfirmation(new SimpleObjectProperty<>(mapping));
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();
}
}));
}
@Override
protected void finish() {
confirmed.set(true);
window.close();
}
};
return multi.apply(s -> {
s.get().setPrefWidth(400);
s.get().setPrefHeight(500);
AppFont.medium(s.get());
});
},
false,
null);
stage.setOnHidden(event -> latch.countDown());
return stage;
});
return confirmed.get();
}
@Override
protected Region createSimple() {
var header = new LabelComp(AppI18n.observable("confirmTableMapping"))
.apply(struc -> struc.get().setWrapText(true));
var content = Comp.derive(new DsTableMappingComp(mapping), region -> {
var box = new HBox(region);
box.setAlignment(Pos.CENTER);
box.getStyleClass().add("grid-container");
return box;
})
.apply(struc -> AppFont.normal(struc.get()));
var changeNotice = new LabelComp(AppI18n.observable("changeTableMapping"))
.apply(struc -> struc.get().setWrapText(true));
var changeButton = Comp.of(() -> {
var hl = new Hyperlink("Customizing Data Flows");
hl.setOnAction(e -> {});
hl.setMaxWidth(250);
return hl;
});
return new VerticalComp(List.of(
header,
content,
Comp.of(() -> new Separator(Orientation.HORIZONTAL)),
changeNotice,
changeButton))
.styleClass("table-mapping-confirmation-comp")
.createRegion();
}
}

View file

@ -1,146 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.ListViewComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.SimpleValidator;
import io.xpipe.app.util.Validatable;
import io.xpipe.app.util.Validator;
import io.xpipe.core.source.DataSource;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.Getter;
import net.synedra.validatorfx.Check;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class NamedSourceChoiceComp extends SimpleComp implements Validatable {
private final ObservableValue<Predicate<DataSource<?>>> filter;
private final DataSourceProvider.Category category;
private final Property<? extends DataSource<?>> selected;
private final StringProperty filterString = new SimpleStringProperty();
@Getter
private final Validator validator = new SimpleValidator();
private final Check check;
public NamedSourceChoiceComp(
ObservableValue<Predicate<DataSource<?>>> filter,
Property<? extends DataSource<?>> selected,
DataSourceProvider.Category category) {
this.filter = filter;
this.selected = selected;
this.category = category;
check = Validator.nonNull(validator, AppI18n.observable("source"), selected);
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> void setUpListener(ObservableValue<T> prop) {
prop.addListener((c, o, n) -> {
((Property<T>) selected).setValue(n);
});
}
private <T extends DataSource<?>> void refreshShown(ObservableList<T> list, ObservableList<T> shown) {
var filtered = list.filtered(source -> {
if (!filter.getValue().test(source)) {
return false;
}
var e = DataStorage.get().getSourceEntry(source).orElseThrow();
return filterString.get() == null
|| e.getName().toLowerCase().contains(filterString.get().toLowerCase());
});
shown.removeIf(store -> !filtered.contains(store));
filtered.forEach(store -> {
if (!shown.contains(store)) {
shown.add(store);
}
});
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Region create() {
var list = FXCollections.observableList(DataStorage.get().getSourceCollections().stream()
.map(dataSourceCollection -> dataSourceCollection.getEntries())
.flatMap(Collection::stream)
.filter(entry -> entry.getState().isUsable())
.map(DataSourceEntry::getSource)
.map(source -> (T) source)
.toList());
var shown = FXCollections.<T>observableArrayList();
refreshShown(list, shown);
filter.addListener((observable, oldValue, newValue) -> {
refreshShown(list, shown);
});
filterString.addListener((observable, oldValue, newValue) -> {
refreshShown(list, shown);
});
var prop = new SimpleObjectProperty<T>();
setUpListener(prop);
var filterComp = new FilterComp(filterString).hide(Bindings.greaterThan(5, Bindings.size(shown)));
var view = new ListViewComp<>(shown, list, prop, (T s) -> {
var e = DataStorage.get().getSourceEntry(s).orElseThrow();
var provider = e.getProvider();
var graphic = provider.getDisplayIconFileName();
var top = String.format("%s (%s)", e.getName(), provider.getDisplayName());
var bottom = DataStoreProviders.byStore(e.getStore()).toSummaryString(e.getStore(), 100);
var el = JfxHelper.createNamedEntry(top, bottom, graphic);
VBox.setVgrow(el, Priority.ALWAYS);
return Comp.of(() -> el);
})
.apply(struc -> {
struc.get().setMaxHeight(3500);
check.decorates(struc.get());
});
var box = new VerticalComp(List.of(filterComp, view));
var text = new LabelComp(AppI18n.observable("noMatchingSourceFound"))
.apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS));
var notice = new VerticalComp(List.of(text))
.apply(struc -> {
struc.get().setSpacing(10);
struc.get().setAlignment(Pos.CENTER);
})
.hide(BindingsHelper.persist(Bindings.notEqual(0, Bindings.size(shown))));
return new StackComp(List.of(box, notice))
.styleClass("named-source-choice")
.createRegion();
}
@Override
protected Region createSimple() {
return create();
}
}

View file

@ -1,197 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import com.jfoenix.controls.JFXTextField;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
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.SimpleCompStructure;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.TrackEvent;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.effect.Glow;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.File;
public class SourceCollectionComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceCollectionComp(SourceCollectionWrapper group) {
this.group = group;
}
@Override
protected Region createSimple() {
var r = createContent();
var sp = new StackPane(r);
sp.setAlignment(Pos.CENTER);
sp.setOnMouseClicked(e -> {
if (e.getButton() != MouseButton.PRIMARY) {
return;
}
TrackEvent.withDebug("Storage group clicked")
.tag("uuid", group.getCollection().getUuid().toString())
.tag("name", group.getName())
.build()
.handle();
// StorageViewState.get().selectedGroupProperty().set(group);
e.consume();
});
setupDragAndDrop(sp);
return sp;
}
private void setupDragAndDrop(Region r) {
r.setOnDragOver(event -> {
// Moving storage entries
if (event.getGestureSource() != null
&& event.getGestureSource() != r
&& event.getSource() instanceof Node) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
r.setEffect(new Glow(0.5));
}
// Files from the outside
else if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.COPY);
r.setEffect(new Glow(0.5));
}
event.consume();
});
r.setOnDragExited(event -> {
r.setEffect(null);
event.consume();
});
r.setOnDragDropped(event -> {
// Moving storage entries
if (event.getGestureSource() != null
&& event.getGestureSource() != r
&& event.getGestureSource() instanceof Node n) {
var entry = n.getProperties().get("entry");
if (entry != null) {
var cast = (SourceEntryWrapper) entry;
cast.moveTo(this.group);
}
}
// Files from the outside
else if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.setDropCompleted(true);
Dragboard db = event.getDragboard();
db.getFiles().stream().map(File::toPath).forEach(group::dropFile);
}
event.consume();
});
}
private Label createDate() {
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(group.lastAccessProperty())));
date.getStyleClass().add("date");
return date;
}
private Region createContent() {
Region nameR;
if (!group.isRenameable()) {
Region textFieldR = new JFXTextField(group.getName());
textFieldR.setDisable(true);
var tempNote = Comp.of(() -> {
var infoIcon = new FontIcon("mdi2i-information-outline");
infoIcon.setOpacity(0.75);
return new StackPane(infoIcon);
})
.apply(new FancyTooltipAugment<>(AppI18n.observable("temporaryCollectionNote")))
.createRegion();
var label = new Label(group.getName(), tempNote);
label.getStyleClass().add("temp");
label.setAlignment(Pos.CENTER);
label.setContentDisplay(ContentDisplay.RIGHT);
nameR = new HBox(label);
} else {
var text = new LazyTextFieldComp(group.nameProperty());
nameR = text.createRegion();
}
var options = new IconButtonComp("mdomz-settings");
var cm = new SourceCollectionContextMenu<>(true, group, nameR);
options.apply(new SourceCollectionContextMenu<>(true, group, nameR))
.apply(r -> {
AppFont.setSize(r.get(), -1);
r.get().setPadding(new Insets(3, 5, 3, 5));
})
.apply(new FancyTooltipAugment<>("collectionOptions"));
var count = new CountComp<>(
SourceCollectionViewState.get().getFilteredEntries(this.group), this.group.entriesProperty());
var spacer = new Region();
var optionsR = options.createRegion();
var top = new HBox(nameR, optionsR);
HBox.setHgrow(nameR, Priority.ALWAYS);
top.setSpacing(8);
var countR = count.createRegion();
countR.prefWidthProperty().bind(optionsR.widthProperty());
var bottom = new HBox(createDate(), spacer, countR);
bottom.setAlignment(Pos.CENTER);
bottom.setSpacing(8);
HBox.setHgrow(spacer, Priority.ALWAYS);
var right = new VBox(top, bottom);
right.setSpacing(8);
AppFont.header(top);
AppFont.small(bottom);
var svgContent = Bindings.createObjectBinding(
() -> {
if (SourceCollectionViewState.get().getSelectedGroup() == group) {
return "folder_open.svg";
} else {
return "folder_closed.svg";
}
},
SourceCollectionViewState.get().selectedGroupProperty());
var svg = new PrettyImageComp(svgContent, 55, 55).createRegion();
svg.getStyleClass().add("icon");
if (group.isInternal()) {
svg.setOpacity(0.3);
}
var hbox = new HBox(svg, right);
HBox.setHgrow(right, Priority.ALWAYS);
hbox.setAlignment(Pos.CENTER);
// svg.prefHeightProperty().bind(right.heightProperty());
// svg.prefWidthProperty().bind(right.heightProperty());
hbox.setSpacing(5);
hbox.getStyleClass().add("storage-group-entry");
cm = new SourceCollectionContextMenu<>(false, group, nameR);
cm.augment(new SimpleCompStructure<>(hbox));
hbox.setMinWidth(0);
return hbox;
}
}

View file

@ -1,105 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.DesktopHelper;
import javafx.scene.control.Alert;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceCollectionContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
public SourceCollectionContextMenu(
boolean showOnPrimaryButton, SourceCollectionWrapper group, Region renameTextField) {
super(() -> createContextMenu(group, renameTextField));
}
private static void onDelete(SourceCollectionWrapper group) {
if (group.getEntries().size() > 0) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
alert.setHeaderText(AppI18n.get("confirmCollectionDeletionHeader", group.getName()));
alert.setContentText(AppI18n.get(
"confirmCollectionDeletionContent",
group.getEntries().size()));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
group.delete();
});
} else {
group.delete();
}
}
private static void onClean(SourceCollectionWrapper group) {
if (group.getEntries().size() > 0) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
alert.setHeaderText(AppI18n.get("confirmCollectionDeletionHeader", group.getName()));
alert.setContentText(AppI18n.get(
"confirmCollectionDeletionContent",
group.getEntries().size()));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
group.clean();
});
} else {
group.clean();
}
}
protected static ContextMenu createContextMenu(SourceCollectionWrapper group, Region renameTextField) {
var cm = new ContextMenu();
var name = new MenuItem(group.getName());
name.setDisable(true);
name.getStyleClass().add("header-menu-item");
cm.getItems().add(name);
cm.getItems().add(new SeparatorMenuItem());
{
var properties = new MenuItem(AppI18n.get("properties"), new FontIcon("mdi2a-application-cog"));
properties.setOnAction(e -> {});
// cm.getItems().add(properties);
}
if (group.isRenameable()) {
var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdal-edit"));
rename.setOnAction(e -> {
renameTextField.requestFocus();
});
cm.getItems().add(rename);
}
if (AppPrefs.get().developerMode().getValue()) {
var openDir = new MenuItem(AppI18n.get("openDir"), new FontIcon("mdal-edit"));
openDir.setOnAction(e -> {
DesktopHelper.browseFileInDirectory(group.getCollection().getDirectory());
});
cm.getItems().add(openDir);
}
if (group.isDeleteable()) {
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
onDelete(group);
});
cm.getItems().add(del);
} else {
var del = new MenuItem(AppI18n.get("clean"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
onClean(group);
});
cm.getItems().add(del);
}
return cm;
}
}

View file

@ -1,81 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.core.source.DataSourceType;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceCollectionEmptyIntroComp extends SimpleComp {
@Override
protected Region createSimple() {
var title = new Label(AppI18n.get("dataSourceIntroTitle"));
AppFont.setSize(title, 7);
title.getStyleClass().add("title-header");
var descFi = new FontIcon("mdi2i-information-outline");
var introDesc = new Label(AppI18n.get("dataSourceIntroDescription"));
introDesc.heightProperty().addListener((c, o, n) -> {
descFi.iconSizeProperty().set(n.intValue());
});
var tableFi = new DataSourceTypeComp(DataSourceType.TABLE, null).createRegion();
var table = new Label(AppI18n.get("dataSourceIntroTable"), tableFi);
tableFi.prefWidthProperty().bind(table.heightProperty());
tableFi.prefHeightProperty().bind(table.heightProperty());
var structureFi = new DataSourceTypeComp(DataSourceType.STRUCTURE, null).createRegion();
var structure = new Label(AppI18n.get("dataSourceIntroStructure"), structureFi);
structureFi.prefWidthProperty().bind(structure.heightProperty());
structureFi.prefHeightProperty().bind(structure.heightProperty());
var textFi = new DataSourceTypeComp(DataSourceType.TEXT, null).createRegion();
var text = new Label(AppI18n.get("dataSourceIntroText"), textFi);
textFi.prefWidthProperty().bind(text.heightProperty());
textFi.prefHeightProperty().bind(text.heightProperty());
var binaryFi = new DataSourceTypeComp(DataSourceType.RAW, null).createRegion();
var binary = new Label(AppI18n.get("dataSourceIntroBinary"), binaryFi);
binaryFi.prefWidthProperty().bind(binary.heightProperty());
binaryFi.prefHeightProperty().bind(binary.heightProperty());
var collectionFi = new DataSourceTypeComp(DataSourceType.COLLECTION, null).createRegion();
var collection = new Label(AppI18n.get("dataSourceIntroCollection"), collectionFi);
collectionFi.prefWidthProperty().bind(collection.heightProperty());
collectionFi.prefHeightProperty().bind(collection.heightProperty());
var v = new VBox(
title,
introDesc,
new Separator(Orientation.HORIZONTAL),
table,
new Separator(Orientation.HORIZONTAL),
structure,
new Separator(Orientation.HORIZONTAL),
text,
new Separator(Orientation.HORIZONTAL),
binary,
new Separator(Orientation.HORIZONTAL),
collection);
v.setMinWidth(Region.USE_PREF_SIZE);
v.setMaxWidth(Region.USE_PREF_SIZE);
v.setMinHeight(Region.USE_PREF_SIZE);
v.setMaxHeight(Region.USE_PREF_SIZE);
v.setSpacing(10);
v.getStyleClass().add("intro");
var sp = new StackPane(v);
sp.setAlignment(Pos.CENTER);
return sp;
}
}

View file

@ -1,60 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
public class SourceCollectionFilterBarComp extends SimpleComp {
private Region createGroupListHeader() {
var label = new Label("Collections");
label.getStyleClass().add("name");
var count = new CountComp<>(
SourceCollectionViewState.get().getShownGroups(),
SourceCollectionViewState.get().getAllGroups());
var newFolder = new IconButtonComp("mdi2f-folder-plus-outline", () -> {
SourceCollectionViewState.get().addNewCollection();
})
.shortcut(new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addCollectionFolder"));
var spacer = new Region();
var topBar = new HBox(label, count.createRegion(), spacer, newFolder.createRegion());
AppFont.header(topBar);
topBar.setAlignment(Pos.CENTER);
HBox.setHgrow(spacer, Priority.ALWAYS);
topBar.getStyleClass().add("top");
return topBar;
}
private Region createGroupListFilter() {
var filter = new FilterComp(SourceCollectionViewState.get().getFilter().filterProperty());
filter.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
s.getText().requestFocus();
});
var r = new StackPane(filter.createRegion());
r.setAlignment(Pos.CENTER);
r.getStyleClass().add("filter-bar");
AppFont.medium(r);
return r;
}
@Override
public Region createSimple() {
var content = new VBox(createGroupListHeader(), createGroupListFilter());
content.getStyleClass().add("bar");
content.getStyleClass().add("collections-bar");
return content;
}
}

View file

@ -1,106 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.storage.source.SourceEntryListComp;
import io.xpipe.app.comp.storage.source.SourceEntryListHeaderComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.List;
public class SourceCollectionLayoutComp extends SimpleComp {
private Comp<?> createEntries(SourceCollectionWrapper group, Region groupHeader) {
var entryList = new SourceEntryListComp(group);
var entriesHeader = new SourceEntryListHeaderComp(group);
entriesHeader.apply(r -> r.get().minHeightProperty().bind(groupHeader.heightProperty()));
var entriesHeaderWrapped = Comp.derive(entriesHeader, r -> {
var sp = new StackPane(r);
sp.setPadding(new Insets(0, 5, 5, 5));
return sp;
});
var list = new ArrayList<Comp<?>>(List.of(entriesHeaderWrapped));
list.add(entryList);
entryList.apply(s -> VBox.setVgrow(s.get(), Priority.ALWAYS));
return new VerticalComp(list);
}
private Comp<?> createCollectionList() {
var listComp = new SourceCollectionListComp();
listComp.apply(s -> s.get().setPrefHeight(Region.USE_COMPUTED_SIZE));
return listComp;
}
private Comp<?> createFiller() {
var filler = Comp.of(() -> new Region());
filler.styleClass("bar");
filler.styleClass("filler-bar");
var button = new ButtonComp(
AppI18n.observable("addCollection"), new FontIcon("mdi2f-folder-plus-outline"), () -> {
SourceCollectionViewState.get().addNewCollection();
})
.apply(new FancyTooltipAugment<>("addCollectionFolder"));
button.styleClass("intro-add-collection-button");
var pane = Comp.derive(button, r -> {
var sp = new StackPane(r);
sp.setAlignment(Pos.CENTER);
sp.setPickOnBounds(false);
return sp;
});
pane.apply(r -> {
r.get().visibleProperty().bind(SourceCollectionViewState.get().getStorageEmpty());
r.get()
.mouseTransparentProperty()
.bind(BindingsHelper.persist(
Bindings.not(SourceCollectionViewState.get().getStorageEmpty())));
});
var stack = new StackComp(List.of(filler, pane));
stack.apply(s -> {
s.get().setMinHeight(0);
s.get().setPrefHeight(0);
});
return stack;
}
@Override
protected Region createSimple() {
var listComp = createCollectionList();
var r = new BorderPane();
var listR = listComp.createRegion();
var groupHeader = new SourceCollectionFilterBarComp().createRegion();
var filler = createFiller().createRegion();
var groups = new VBox(groupHeader, listR);
groups.getStyleClass().add("sidebar");
VBox.setVgrow(filler, Priority.SOMETIMES);
VBox.setVgrow(listR, Priority.SOMETIMES);
r.setLeft(groups);
Runnable update = () -> {
r.setCenter(createEntries(SourceCollectionViewState.get().getSelectedGroup(), groupHeader)
.createRegion());
};
update.run();
SourceCollectionViewState.get().selectedGroupProperty().addListener((c, o, n) -> {
PlatformThread.runLaterIfNeeded(update);
});
return r;
}
}

View file

@ -1,17 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.ListViewComp;
public class SourceCollectionListComp extends ListViewComp<SourceCollectionWrapper> {
public SourceCollectionListComp() {
super(
SourceCollectionViewState.get().getShownGroups(),
SourceCollectionViewState.get().getAllGroups(),
SourceCollectionViewState.get().selectedGroupProperty(),
SourceCollectionComp::new);
styleClass("storage-group-list-comp");
styleClass("bar");
apply(s -> s.get().layout());
}
}

View file

@ -1,66 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import java.time.Instant;
import java.util.Comparator;
public interface SourceCollectionSortMode {
SourceCollectionSortMode ALPHABETICAL_DESC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "alphabetical-desc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, String>comparing(
e -> e.getName().getValue())
.reversed();
}
};
SourceCollectionSortMode ALPHABETICAL_ASC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "alphabetical-asc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, String>comparing(
e -> e.getName().getValue());
}
};
SourceCollectionSortMode DATE_DESC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "date-desc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, Instant>comparing(
e -> e.getLastUsed().getValue())
.reversed();
}
};
SourceCollectionSortMode DATE_ASC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "date-asc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.comparing(e -> e.getLastUsed().getValue());
}
};
String getId();
Comparator<SourceEntryWrapper> comparator();
}

View file

@ -1,222 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.time.Instant;
import java.util.Comparator;
import java.util.concurrent.CopyOnWriteArrayList;
public class SourceCollectionViewState {
private static SourceCollectionViewState INSTANCE;
private final StorageFilter filter = new StorageFilter();
private final ObservableList<SourceCollectionWrapper> allGroups =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableList<SourceCollectionWrapper> shownGroups =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final SimpleObjectProperty<SourceCollectionWrapper> selectedGroup = new SimpleObjectProperty<>();
private final SimpleObjectProperty<SourceCollectionSortMode> sortMode = new SimpleObjectProperty<>();
private final ObservableList<SourceEntryWrapper> allEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableList<SourceEntryWrapper> shownEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableBooleanValue storageEmpty =
BindingsHelper.persist(Bindings.size(allGroups).isEqualTo(0));
private SourceCollectionViewState() {
addCollectionListChangeListeners();
addEntryListListeners();
addSortModeListeners();
}
public static void init() {
INSTANCE = new SourceCollectionViewState();
}
public static void reset() {
INSTANCE = null;
}
public static SourceCollectionViewState get() {
return INSTANCE;
}
private void addSortModeListeners() {
ChangeListener<SourceCollectionSortMode> listener = (observable1, oldValue1, newValue1) -> {
sortMode.set(newValue1);
};
selectedGroup.addListener((observable, oldValue, newValue) -> {
sortMode.set(newValue != null ? newValue.getSortMode() : null);
if (newValue != null) {
newValue.sortModeProperty().addListener(listener);
}
if (oldValue != null) {
oldValue.sortModeProperty().removeListener(listener);
}
});
}
public void addNewCollection() {
PlatformThread.runLaterIfNeeded(() -> {
var col = DataSourceCollection.createNew(AppI18n.get("newCollection"));
DataStorage.get().addCollection(col);
allGroups.stream()
.filter(g -> g.getCollection().equals(col))
.findAny()
.ifPresent(selectedGroup::set);
});
}
public ObservableList<SourceEntryWrapper> getAllEntries() {
return allEntries;
}
public ObservableList<SourceEntryWrapper> getShownEntries() {
return shownEntries;
}
public ObservableBooleanValue getStorageEmpty() {
return storageEmpty;
}
public SourceCollectionWrapper getSelectedGroup() {
return selectedGroup.get();
}
public SimpleObjectProperty<SourceCollectionWrapper> selectedGroupProperty() {
return selectedGroup;
}
private void addCollectionListChangeListeners() {
allGroups.setAll(filter(FXCollections.observableList(DataStorage.get().getSourceCollections().stream()
.map(SourceCollectionWrapper::new)
.toList())));
filter.createFilterBinding(
filter(allGroups),
shownGroups,
new SimpleObjectProperty<>(
Comparator.<SourceCollectionWrapper, Instant>comparing(e -> e.getLastAccess())
.reversed()));
DataStorage.get().addListener(new StorageListener() {
@Override
public void onStoreAdd(DataStoreEntry entry) {}
@Override
public void onStoreRemove(DataStoreEntry entry) {}
@Override
public void onCollectionAdd(DataSourceCollection collection) {
PlatformThread.runLaterIfNeeded(() -> {
var sg = new SourceCollectionWrapper(collection);
allGroups.add(sg);
});
}
@Override
public void onCollectionRemove(DataSourceCollection collection) {
PlatformThread.runLaterIfNeeded(() -> {
allGroups.removeIf(g -> g.getCollection().equals(collection));
});
}
});
shownGroups.addListener((ListChangeListener<? super SourceCollectionWrapper>) (c) -> {
if (selectedGroup.get() != null && !shownGroups.contains(selectedGroup.get())) {
selectedGroup.set(null);
}
});
shownGroups.addListener((ListChangeListener<? super SourceCollectionWrapper>) c -> {
if (c.getList().size() == 1) {
selectedGroup.set(c.getList().get(0));
}
});
}
private ObservableList<SourceCollectionWrapper> filter(ObservableList<SourceCollectionWrapper> list) {
return list.filtered(storeEntryWrapper -> {
if (AppPrefs.get().developerMode().getValue()
&& AppPrefs.get().developerShowHiddenEntries().get()) {
return true;
} else {
return !storeEntryWrapper.isInternal();
}
});
}
public SourceCollectionWrapper getGroup(SourceEntryWrapper e) {
return allGroups.stream()
.filter(g -> g.getEntries().contains(e))
.findFirst()
.orElseThrow();
}
public ObservableList<SourceEntryWrapper> getFilteredEntries(SourceCollectionWrapper g) {
var filtered = FXCollections.<SourceEntryWrapper>observableArrayList();
filter.createFilterBinding(
g.entriesProperty(),
filtered,
new SimpleObjectProperty<>(Comparator.<SourceEntryWrapper, Instant>comparing(
e -> e.getEntry().getLastAccess())
.reversed()));
return filtered;
}
private void addEntryListListeners() {
filter.createFilterBinding(
allEntries,
shownEntries,
Bindings.createObjectBinding(
() -> {
return sortMode.getValue() != null
? sortMode.getValue().comparator()
: Comparator.<SourceEntryWrapper>comparingInt(o -> o.hashCode());
},
sortMode));
selectedGroup.addListener((c, o, n) -> {
if (o != null) {
Bindings.unbindContent(allEntries, o.getEntries());
}
if (n != null) {
Bindings.bindContent(allEntries, n.getEntries());
}
});
}
public ObservableList<SourceCollectionWrapper> getShownGroups() {
return shownGroups;
}
public ObservableList<SourceCollectionWrapper> getAllGroups() {
return allGroups;
}
public StorageFilter getFilter() {
return filter;
}
}

View file

@ -1,164 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.source.SourceEntryDisplayMode;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.storage.CollectionListener;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.impl.FileStore;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SourceCollectionWrapper implements StorageFilter.Filterable {
private final Property<String> name;
private final IntegerProperty size;
private final ListProperty<SourceEntryWrapper> entries;
private final DataSourceCollection collection;
private final Property<Instant> lastAccess;
private final Property<SourceCollectionSortMode> sortMode =
new SimpleObjectProperty<>(SourceCollectionSortMode.DATE_DESC);
private final Property<SourceEntryDisplayMode> displayMode =
new SimpleObjectProperty<>(SourceEntryDisplayMode.LIST);
public SourceCollectionWrapper(DataSourceCollection collection) {
this.collection = collection;
this.entries = new SimpleListProperty<>(FXCollections.observableList(collection.getEntries().stream()
.map(SourceEntryWrapper::new)
.collect(Collectors.toCollection(ArrayList::new))));
this.size = new SimpleIntegerProperty(collection.getEntries().size());
this.name = new SimpleStringProperty(collection.getName());
this.lastAccess = new SimpleObjectProperty<>(collection.getLastAccess().minus(Duration.ofMillis(500)));
setupListeners();
}
public ReadOnlyBooleanProperty emptyProperty() {
return entries.emptyProperty();
}
public boolean isDeleteable() {
return !isInternal();
}
public boolean isRenameable() {
return !isInternal();
}
public boolean isInternal() {
return collection.equals(DataStorage.get().getInternalCollection());
}
public void dropFile(Path file) {
var store = FileStore.local(file);
GuiDsCreatorMultiStep.showForStore(DataSourceProvider.Category.STREAM, store, this.getCollection());
}
public void delete() {
DataStorage.get().deleteCollection(this.collection);
}
public void clean() {
var entries = List.copyOf(collection.getEntries());
entries.forEach(e -> DataStorage.get().deleteSourceEntry(e));
}
private void setupListeners() {
name.addListener((c, o, n) -> {
collection.setName(n);
});
collection.addListener(new CollectionListener() {
@Override
public void onUpdate() {
lastAccess.setValue(collection.getLastAccess().minus(Duration.ofMillis(500)));
name.setValue(collection.getName());
}
@Override
public void onEntryAdd(DataSourceEntry entry) {
var e = new SourceEntryWrapper(entry);
entries.add(e);
}
@Override
public void onEntryRemove(DataSourceEntry entry) {
entries.removeIf(e -> e.getEntry().equals(entry));
}
});
}
public DataSourceCollection getCollection() {
return collection;
}
public String getName() {
return name.getValue();
}
public Property<String> nameProperty() {
return name;
}
public int getSize() {
return size.get();
}
public IntegerProperty sizeProperty() {
return size;
}
public ObservableList<SourceEntryWrapper> getEntries() {
return entries.get();
}
public ListProperty<SourceEntryWrapper> entriesProperty() {
return entries;
}
@Override
public boolean shouldShow(String filter) {
if (isInternal()) {
// return getEntries().stream().anyMatch(e -> e.shouldShow(filter));
}
return getName().toLowerCase().contains(filter.toLowerCase())
|| entries.stream().anyMatch(e -> e.shouldShow(filter));
}
public Instant getLastAccess() {
return lastAccess.getValue();
}
public Property<Instant> lastAccessProperty() {
return lastAccess;
}
public SourceCollectionSortMode getSortMode() {
return sortMode.getValue();
}
public Property<SourceCollectionSortMode> sortModeProperty() {
return sortMode;
}
public SourceEntryDisplayMode getDisplayMode() {
return displayMode.getValue();
}
public Property<SourceEntryDisplayMode> displayModeProperty() {
return displayMode;
}
}

View file

@ -1,224 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.comp.source.DsDataTransferComp;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.core.store.DataFlow;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
import lombok.SneakyThrows;
public class SourceEntryComp extends SimpleComp {
private static final double SOURCE_TYPE_WIDTH = 0.09;
private static final double NAME_WIDTH = 0.3;
private static final double DETAILS_WIDTH = 0.43;
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
private static Image DND_IMAGE = null;
private final SourceEntryWrapper entry;
public SourceEntryComp(SourceEntryWrapper entry) {
this.entry = entry;
}
private Label createSize() {
var size = new Label();
size.textProperty().bind(PlatformThread.sync(entry.getStoreSummary()));
size.getStyleClass().add("size");
AppFont.small(size);
return size;
}
private LazyTextFieldComp createName() {
var name = new LazyTextFieldComp(entry.getName());
name.apply(s -> AppFont.header(s.get()));
return name;
}
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);
}
}
});
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
}
protected Region createContent() {
var name = createName().createRegion();
var size = createSize();
var img = entry.getState().getValue() == DataSourceEntry.State.LOAD_FAILED
? "disabled_icon.png"
: entry.getEntry().getProvider().getDisplayIconFileName();
var storeIcon = new PrettyImageComp(new SimpleStringProperty(img), 60, 50).createRegion();
var desc = new LabelComp(entry.getInformation()).createRegion();
desc.getStyleClass().add("description");
AppFont.header(desc);
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.getLastUsed())));
date.getStyleClass().add("date");
AppFont.small(date);
var grid = new GridPane();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, SOURCE_TYPE_WIDTH),
createShareConstraint(grid, NAME_WIDTH),
new ColumnConstraints(-1));
var typeLogo = new DataSourceTypeComp(
entry.getEntry().getDataSourceType(),
entry.getDataFlow().getValue())
.createRegion();
typeLogo.maxWidthProperty().bind(typeLogo.heightProperty());
grid.add(typeLogo, 0, 0, 1, 2);
GridPane.setHalignment(typeLogo, HPos.CENTER);
grid.add(name, 1, 0);
grid.add(date, 1, 1);
grid.add(storeIcon, 2, 0, 1, 2);
grid.add(size, 3, 1);
grid.add(desc, 3, 0);
grid.setVgap(5);
AppFont.small(size);
AppFont.small(date);
grid.prefHeightProperty()
.bind(Bindings.createDoubleBinding(
() -> {
return name.getHeight() + date.getHeight() + 5;
},
name.heightProperty(),
date.heightProperty()));
grid.getStyleClass().add("content");
grid.setMaxHeight(100);
grid.setHgap(8);
var buttons = new HBox();
buttons.setFillHeight(true);
buttons.getChildren().add(createPipeButton().createRegion());
// buttons.getChildren().add(createUpdateButton().createRegion());
buttons.getChildren().add(createSettingsButton(name).createRegion());
buttons.setMinWidth(Region.USE_PREF_SIZE);
var hbox = new HBox(grid, buttons);
hbox.getStyleClass().add("storage-entry-comp");
HBox.setHgrow(grid, Priority.ALWAYS);
buttons.prefHeightProperty().bind(hbox.heightProperty());
hbox.getProperties().put("entry", this.entry);
hbox.setOnDragDetected(e -> {
if (!entry.getUsable().get()) {
return;
}
if (DND_IMAGE == null) {
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "img/file_drag_icon.png")
.orElseThrow();
DND_IMAGE = new Image(url.toString(), 80, 80, true, false);
}
Dragboard db = hbox.startDragAndDrop(TransferMode.MOVE);
var cc = new ClipboardContent();
cc.putString("");
db.setContent(cc);
db.setDragView(DND_IMAGE, 30, 60);
e.consume();
});
applyState(hbox);
return hbox;
}
private Comp<?> createSettingsButton(Region nameField) {
var settingsButton = new IconButtonComp("mdi2v-view-headline");
settingsButton.styleClass("settings");
settingsButton.apply(new SourceEntryContextMenu<>(true, entry, nameField));
settingsButton.apply(GrowAugment.create(false, true));
settingsButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
settingsButton.apply(new FancyTooltipAugment<>("entrySettings"));
return settingsButton;
}
private Comp<?> createPipeButton() {
var pipeButton = new IconButtonComp("mdi2p-pipe-disconnected", () -> {
DsDataTransferComp.showPipeWindow(this.entry.getEntry());
});
pipeButton.styleClass("retrieve");
pipeButton.apply(GrowAugment.create(false, true));
pipeButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
var disabled = Bindings.createBooleanBinding(
() -> {
if (entry.getDataFlow().getValue() == null) {
return true;
}
return entry.getDataFlow().getValue() == DataFlow.OUTPUT
|| entry.getDataFlow().getValue() == DataFlow.TRANSFORMER;
},
entry.getDataFlow());
pipeButton.disable(disabled).apply(s -> s.get());
pipeButton.apply(new FancyTooltipAugment<>("retrieve"));
return pipeButton;
}
private ColumnConstraints createShareConstraint(Region r, double share) {
var cc = new ColumnConstraints();
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
cc.setMaxWidth(750 * share);
return cc;
}
}

View file

@ -1,93 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
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;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceEntryContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
public SourceEntryContextMenu(boolean showOnPrimaryButton, SourceEntryWrapper entry, Region renameTextField) {
super(() -> createContextMenu(entry, renameTextField));
}
protected static ContextMenu createContextMenu(SourceEntryWrapper entry, Region renameTextField) {
var cm = new ContextMenu();
AppFont.normal(cm.getStyleableNode());
for (var actionProvider : entry.getActionProviders()) {
var c = actionProvider.getDataSourceCallSite();
var name = c.getName(entry.getEntry().getSource().asNeeded());
var icon = c.getIcon(entry.getEntry().getSource().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
event.consume();
try {
var action = c.createAction(entry.getEntry().getSource().asNeeded());
action.execute();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
item.textProperty().bind(name);
// item.setDisable(!entry.getState().getValue().isUsable());
cm.getItems().add(item);
// actionProvider.applyToRegion(entry.getEntry().getStore().asNeeded(), region);
}
if (entry.getActionProviders().size() > 0) {
cm.getItems().add(new SeparatorMenuItem());
}
var properties = new MenuItem(AppI18n.get("properties"), new FontIcon("mdi2a-application-cog"));
properties.setOnAction(e -> {});
// cm.getItems().add(properties);
var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdi2r-rename-box"));
rename.setOnAction(e -> {
renameTextField.requestFocus();
});
cm.getItems().add(rename);
var validate = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
validate.setOnAction(event -> {
DataStorage.get().refreshAsync(entry.getEntry(), true);
});
cm.getItems().add(validate);
var edit = new MenuItem(AppI18n.get("edit"), new FontIcon("mdal-edit"));
edit.setOnAction(event -> entry.editDialog());
edit.disableProperty().bind(Bindings.equal(DataSourceEntry.State.LOAD_FAILED, entry.getState()));
cm.getItems().add(edit);
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
entry.delete();
});
cm.getItems().add(del);
if (AppPrefs.get().developerMode().getValue()) {
cm.getItems().add(new SeparatorMenuItem());
var openDir = new MenuItem(AppI18n.get("browseInternal"), new FontIcon("mdi2f-folder-open-outline"));
openDir.setOnAction(e -> {
DesktopHelper.browsePath(entry.getEntry().getDirectory());
});
cm.getItems().add(openDir);
}
return cm;
}
}

View file

@ -1,44 +0,0 @@
package io.xpipe.app.comp.storage.source;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.List;
import java.util.stream.Collectors;
public interface SourceEntryDisplayMode {
SourceEntryDisplayMode LIST = new ListMode();
SourceEntryDisplayMode TILES = new ListMode();
Region create(List<SourceEntryWrapper> entries);
class ListMode implements SourceEntryDisplayMode {
private static final double SOURCE_TYPE_WIDTH = 0.15;
private static final double NAME_WIDTH = 0.4;
private static final double STORE_TYPE_WIDTH = 0.1;
private static final double DETAILS_WIDTH = 0.35;
@Override
public Region create(List<SourceEntryWrapper> entries) {
VBox content = new VBox();
Runnable updateList = () -> {
var nw = entries.stream()
.map(v -> {
return new SourceEntryComp(v).createRegion();
})
.collect(Collectors.toList());
content.getChildren().setAll(nw);
};
updateList.run();
content.setFillWidth(true);
content.setSpacing(5);
content.getStyleClass().add("content");
content.getStyleClass().add("list-mode");
return content;
}
}
}

View file

@ -1,70 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.FileDropOverlayComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.storage.collection.SourceCollectionEmptyIntroComp;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import javafx.application.Platform;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.ListChangeListener;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import java.util.List;
import java.util.Map;
public class SourceEntryListComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceEntryListComp(SourceCollectionWrapper group) {
this.group = group;
}
@SuppressWarnings("unchecked")
private Region createList() {
if (group == null) {
return null;
}
var content =
group.getDisplayMode().create(SourceCollectionViewState.get().getShownEntries());
var cp = new ScrollPane(content);
cp.setFitToWidth(true);
content.getStyleClass().add("content-pane");
cp.getStyleClass().add("storage-entry-list-comp");
SourceCollectionViewState.get().getShownEntries().addListener((ListChangeListener<? super SourceEntryWrapper>)
(c) -> {
Platform.runLater(() -> {
cp.setContent(group.getDisplayMode().create((List<SourceEntryWrapper>) c.getList()));
});
});
return cp;
}
@Override
protected Region createSimple() {
Map<Comp<?>, ObservableBooleanValue> map;
if (group == null) {
map = Map.of(
new SourceStorageEmptyIntroComp(),
SourceCollectionViewState.get().getStorageEmpty());
} else {
map = Map.of(
Comp.of(() -> createList()),
group.emptyProperty().not(),
new SourceCollectionEmptyIntroComp(),
group.emptyProperty());
}
var overlay = new FileDropOverlayComp<>(new MultiContentComp(map), files -> {
files.forEach(group::dropFile);
});
return overlay.createRegion();
}
}

View file

@ -1,295 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.collection.SourceCollectionSortMode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class SourceEntryListHeaderComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceEntryListHeaderComp(SourceCollectionWrapper group) {
this.group = group;
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
group.sortModeProperty());
var alphabetical = new IconButtonComp(icon, () -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_DESC);
} else if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_ASC);
} else {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC
|| group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
group.sortModeProperty()));
});
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
group.sortModeProperty());
var date = new IconButtonComp(icon, () -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_DESC);
} else if (group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_ASC);
} else {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC
|| group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
group.sortModeProperty()));
});
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
private Comp<?> createListDisplayModeButton() {
var list = new IconButtonComp("mdi2f-format-list-bulleted-type", () -> {
group.displayModeProperty().setValue(SourceEntryDisplayMode.LIST);
});
list.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getDisplayMode() == SourceEntryDisplayMode.LIST) {
return 1.0;
}
return 0.4;
},
group.displayModeProperty()));
});
list.apply(new FancyTooltipAugment<>("displayList"));
return list;
}
private Comp<?> createTilesDisplayModeButton() {
var tiles = new IconButtonComp("mdal-apps", () -> {
group.displayModeProperty().setValue(SourceEntryDisplayMode.TILES);
});
tiles.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getDisplayMode() == SourceEntryDisplayMode.TILES) {
return 1.0;
}
return 0.4;
},
group.displayModeProperty()));
});
tiles.apply(new FancyTooltipAugment<>("displayTiles"));
return tiles;
}
private Comp<?> createSortButtonBar() {
return new HorizontalComp(List.of(createDateSortButton(), createAlphabeticalSortButton()));
}
private Comp<?> createDisplayModeButtonBar() {
return new HorizontalComp(List.of(createListDisplayModeButton(), createTilesDisplayModeButton()));
}
private Comp<?> createRightButtons() {
var v = new VerticalComp(List.of(
createDisplayModeButtonBar().apply(struc -> struc.get().setVisible(false)),
Comp.of(() -> {
return new StackPane(new Separator(Orientation.HORIZONTAL));
}),
createSortButtonBar()));
v.apply(r -> {
var sep = r.get().getChildren().get(1);
VBox.setVgrow(sep, Priority.ALWAYS);
})
.apply(s -> {
s.get()
.visibleProperty()
.bind(BindingsHelper.persist(Bindings.greaterThan(
Bindings.size(
SourceCollectionViewState.get().getAllEntries()),
0)));
});
return v;
}
@Override
protected Region createSimple() {
var label = new Label(AppI18n.get("none"));
if (SourceCollectionViewState.get().getSelectedGroup() != null) {
label.textProperty()
.bind(SourceCollectionViewState.get().getSelectedGroup().nameProperty());
}
label.getStyleClass().add("name");
SourceCollectionViewState.get().selectedGroupProperty().addListener((c, o, n) -> {
if (n != null) {
label.textProperty().bind(n.nameProperty());
}
});
var count = new CountComp<>(
SourceCollectionViewState.get().getShownEntries(),
SourceCollectionViewState.get().getAllEntries());
var close = new IconButtonComp("mdi2a-arrow-collapse-left", () -> SourceCollectionViewState.get()
.selectedGroupProperty()
.set(null))
.createRegion();
AppFont.medium(close);
var leftSep = new StackPane(new Separator(Orientation.HORIZONTAL));
var top = new HBox(label);
if (group != null) {
top.getChildren().add(0, close);
top.getChildren().addAll(count.createRegion());
}
top.setAlignment(Pos.CENTER_LEFT);
top.setSpacing(3);
var left = new VBox(top, leftSep, createActionsButtonBar().createRegion());
VBox.setVgrow(leftSep, Priority.ALWAYS);
var rspacer = new Region();
HBox.setHgrow(rspacer, Priority.ALWAYS);
var topBar = new HBox(left, rspacer);
if (group != null) {
var right = createRightButtons().createRegion();
topBar.getChildren().addAll(right);
}
topBar.setFillHeight(true);
topBar.setSpacing(13);
topBar.getStyleClass().add("top");
topBar.setAlignment(Pos.CENTER);
AppFont.header(topBar);
topBar.getStyleClass().add("bar");
topBar.getStyleClass().add("entry-bar");
return topBar;
}
private Comp<?> createActionsButtonBar() {
var newFile = new ButtonComp(
AppI18n.observable(group != null ? "addStream" : "pipeStream"),
new FontIcon("mdi2f-file-plus-outline"),
() -> {
var selected = SourceCollectionViewState.get()
.selectedGroupProperty()
.get();
GuiDsCreatorMultiStep.showCreation(
DataSourceProvider.Category.STREAM,
selected != null ? selected.getCollection() : null);
})
.shortcut(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addStreamDataSource"));
var newDb = new ButtonComp(
AppI18n.observable(group != null ? "addDatabase" : "pipeDatabase"),
new FontIcon("mdi2d-database-plus-outline"),
() -> {
var selected = SourceCollectionViewState.get()
.selectedGroupProperty()
.get();
GuiDsCreatorMultiStep.showCreation(
DataSourceProvider.Category.DATABASE,
selected != null ? selected.getCollection() : null);
})
.shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addDatabaseDataSource"));
// var newStructure = new IconButton("mdi2b-beaker-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.STRUCTURE);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addStructureDataSource")));
//
// var newText = new IconButton("mdi2t-text-box-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.TEXT);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addTextDataSource")));
//
// var newBinary = new IconButton("mdi2c-card-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.RAW);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addBinaryDataSource")));
//
// var newCollection = new IconButton("mdi2b-briefcase-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.COLLECTION);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addCollectionDataSource")));
var spaceOr = new Region();
spaceOr.setPrefWidth(12);
var box = new HorizontalComp(List.of(newFile, Comp.of(() -> spaceOr), newDb));
box.apply(s -> AppFont.normal(s.get()));
return box;
}
}

View file

@ -1,122 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataFlow;
import javafx.beans.property.*;
import lombok.Value;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Value
public class SourceEntryWrapper implements StorageFilter.Filterable {
DataSourceEntry entry;
StringProperty name = new SimpleStringProperty();
BooleanProperty usable = new SimpleBooleanProperty();
StringProperty information = new SimpleStringProperty();
StringProperty storeSummary = new SimpleStringProperty();
Property<Instant> lastUsed = new SimpleObjectProperty<>();
Property<DataFlow> dataFlow = new SimpleObjectProperty<>();
ObjectProperty<DataSourceEntry.State> state = new SimpleObjectProperty<>();
BooleanProperty loading = new SimpleBooleanProperty();
List<ActionProvider> actionProviders = new ArrayList<>();
public SourceEntryWrapper(DataSourceEntry entry) {
this.entry = entry;
entry.addListener(() -> PlatformThread.runLaterIfNeeded(() -> {
update();
}));
update();
name.addListener((c, o, n) -> {
if (!entry.getName().equals(n)) {
entry.setName(n);
}
});
}
public void moveTo(SourceCollectionWrapper newGroup) {
var old = SourceCollectionViewState.get().getGroup(this);
old.getCollection().removeEntry(this.entry);
newGroup.getCollection().addEntry(this.entry);
}
public void editDialog() {
if (!DataStorage.get().getSourceEntries().contains(entry)) {
return;
}
GuiDsCreatorMultiStep.showEdit(getEntry());
}
public void delete() {
if (!DataStorage.get().getSourceEntries().contains(entry)) {
return;
}
DataStorage.get().deleteSourceEntry(entry);
}
private <T extends DataSource<?>> void update() {
// Avoid reupdating name when changed from the name property!
if (!entry.getName().equals(name.getValue())) {
name.set(entry.getName());
}
lastUsed.setValue(entry.getLastUsed());
state.setValue(entry.getState());
usable.setValue(entry.getState().isUsable());
dataFlow.setValue(entry.getSource() != null ? entry.getSource().getFlow() : null);
storeSummary.setValue(
entry.getState().isUsable()
? DataStoreProviders.byStore(entry.getStore()).toSummaryString(entry.getStore(), 50)
: null);
information.setValue(
entry.getState() != DataSourceEntry.State.LOAD_FAILED
? entry.getInformation() != null
? entry.getInformation()
: entry.getProvider().getDisplayName()
: AppI18n.get("failedToLoad"));
loading.setValue(entry.getState() == null || entry.getState() == DataSourceEntry.State.VALIDATING);
actionProviders.clear();
actionProviders.addAll(ActionProvider.ALL.stream()
.filter(p -> {
try {
if (!entry.getState().isUsable()) {
return false;
}
var c = p.getDataSourceCallSite();
if (c == null) {
return false;
}
return c.getApplicableClass()
.isAssignableFrom(entry.getSource().getClass())
&& c.isApplicable(entry.getSource().asNeeded());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList());
}
@Override
public boolean shouldShow(String filter) {
return getName().get().toLowerCase().contains(filter.toLowerCase());
}
}

View file

@ -1,77 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.util.Hyperlinks;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceStorageEmptyIntroComp extends SimpleComp {
@Override
public Region createSimple() {
var title = new Label(AppI18n.get("introTitle"));
AppFont.setSize(title, 7);
title.getStyleClass().add("title-header");
var descFi = new FontIcon("mdi2i-information-outline");
var introDesc = new Label(AppI18n.get("introDescription"));
introDesc.heightProperty().addListener((c, o, n) -> {
descFi.iconSizeProperty().set(n.intValue());
});
var fi = new FontIcon("mdi2f-folder-plus-outline");
var addCollection = new Label(AppI18n.get("introCollection"), fi);
addCollection.heightProperty().addListener((c, o, n) -> {
fi.iconSizeProperty().set(n.intValue());
});
var pipeFi = new FontIcon("mdi2p-pipe-disconnected");
var pipe = new Label(AppI18n.get("introPipe"), pipeFi);
pipe.heightProperty().addListener((c, o, n) -> {
pipeFi.iconSizeProperty().set(n.intValue());
});
var dfi = new FontIcon("mdi2b-book-open-variant");
var documentation = new Label(AppI18n.get("introDocumentation"), dfi);
documentation.heightProperty().addListener((c, o, n) -> {
dfi.iconSizeProperty().set(n.intValue());
});
var docLink = new Hyperlink(Hyperlinks.DOCUMENTATION);
docLink.setOnAction(e -> {
Hyperlinks.open(Hyperlinks.DOCUMENTATION);
});
var docLinkPane = new StackPane(docLink);
docLinkPane.setAlignment(Pos.CENTER);
var v = new VBox(
title,
introDesc,
new Separator(Orientation.HORIZONTAL),
addCollection,
new Separator(Orientation.HORIZONTAL),
pipe,
new Separator(Orientation.HORIZONTAL),
documentation,
docLinkPane);
v.setMinWidth(Region.USE_PREF_SIZE);
v.setMaxWidth(Region.USE_PREF_SIZE);
v.setMinHeight(Region.USE_PREF_SIZE);
v.setMaxHeight(Region.USE_PREF_SIZE);
v.setSpacing(10);
v.getStyleClass().add("intro");
var sp = new StackPane(v);
sp.setAlignment(Pos.CENTER);
return sp;
}
}

View file

@ -0,0 +1,70 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.layout.*;
public class DenseStoreEntryComp extends StoreEntryComp {
private final boolean showIcon;
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
super(entry, content);
this.showIcon = showIcon;
}
protected Region createContent() {
var name = createName().createRegion();
var grid = new GridPane();
grid.setHgap(8);
if (showIcon) {
var storeIcon = createIcon(30, 25);
grid.getColumnConstraints().add(new ColumnConstraints(30));
grid.add(storeIcon, 0, 0);
GridPane.setHalignment(storeIcon, HPos.CENTER);
} else {
grid.add(new Region(), 0, 0);
grid.getColumnConstraints().add(new ColumnConstraints(0));
}
var custom = new ColumnConstraints(content != null ? 300 : 0);
custom.setHalignment(HPos.RIGHT);
custom.setMinWidth(Region.USE_PREF_SIZE);
custom.setMaxWidth(Region.USE_PREF_SIZE);
var info = new ColumnConstraints(content != null ? 300 : 600);
info.setHalignment(HPos.LEFT);
info.setMinWidth(Region.USE_PREF_SIZE);
info.setMaxWidth(Region.USE_PREF_SIZE);
var nameCC = new ColumnConstraints();
nameCC.setMinWidth(100);
nameCC.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(nameCC);
grid.add(name, 1, 0);
grid.add(createInformation(), 2, 0);
grid.getColumnConstraints().addAll(info, custom);
var cr = content != null ? content.createRegion() : new Region();
var bb = createButtonBar().createRegion();
var controls = new HBox(cr, bb);
controls.setFillHeight(true);
controls.setAlignment(Pos.CENTER_RIGHT);
controls.setSpacing(10);
HBox.setHgrow(cr, Priority.ALWAYS);
grid.add(controls, 3, 0);
GrowAugment.create(true, false).augment(grid);
grid.getStyleClass().add("store-entry-grid");
grid.getStyleClass().add("dense");
applyState(grid);
return grid;
}
}

View file

@ -0,0 +1,60 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.fxcomps.Comp;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.layout.*;
public class StandardStoreEntryComp extends StoreEntryComp {
public StandardStoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
super(entry, content);
}
protected Region createContent() {
var name = createName().createRegion();
var grid = new GridPane();
grid.setHgap(10);
grid.setVgap(0);
var storeIcon = createIcon(60, 45);
grid.add(storeIcon, 0, 0, 1, 2);
grid.getColumnConstraints().add(new ColumnConstraints(60));
grid.add(name, 1, 0);
grid.add(createSummary(), 1, 1);
var nameCC = new ColumnConstraints();
nameCC.setMinWidth(100);
nameCC.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(nameCC);
grid.add(createInformation(), 2, 0, 1, 2);
var info = new ColumnConstraints(content != null ? 300 : 600);
info.setHalignment(HPos.LEFT);
info.setMinWidth(Region.USE_PREF_SIZE);
info.setMaxWidth(Region.USE_PREF_SIZE);
grid.getColumnConstraints().add(info);
var custom = new ColumnConstraints(content != null ? 300 : 0);
custom.setHalignment(HPos.RIGHT);
custom.setMinWidth(Region.USE_PREF_SIZE);
custom.setMaxWidth(Region.USE_PREF_SIZE);
var cr = content != null ? content.createRegion() : new Region();
var bb = createButtonBar().createRegion();
var controls = new HBox(cr, bb);
controls.setFillHeight(true);
HBox.setHgrow(cr, Priority.ALWAYS);
controls.setAlignment(Pos.CENTER_RIGHT);
controls.setSpacing(10);
grid.add(controls, 3, 0, 1, 2);
grid.getColumnConstraints().add(custom);
grid.getStyleClass().add("store-entry-grid");
applyState(grid);
return grid;
}
}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
@ -20,52 +21,96 @@ 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 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 StoreEntryComp(StoreEntryWrapper entry) {
this.entry = entry;
public static Comp<?> customSection(StoreSection e) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customDisplay(e);
} else {
return new StandardStoreEntryComp(e.getWrapper(), null);
}
}
private Label createInformation() {
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
protected final StoreEntryWrapper wrapper;
protected final Comp<?> content;
public StoreEntryComp(StoreEntryWrapper wrapper, Comp<?> content) {
this.wrapper = wrapper;
this.content = content;
}
@Override
protected final Region createSimple() {
var r = createContent();
var button = new JFXButton();
button.setGraphic(r);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(r));
button.getStyleClass().add("store-entry-comp");
button.setPadding(Insets.EMPTY);
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return wrapper.getName();
},
wrapper.nameProperty()));
button.accessibleHelpProperty().bind(wrapper.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
wrapper.executeDefaultAction();
});
});
new ContextMenuAugment<>(() -> this.createContextMenu()).augment(new SimpleCompStructure<>(button));
var loading = new LoadingOverlayComp(Comp.of(() -> button), wrapper.getLoading());
var region = loading.createRegion();
return region;
}
protected abstract Region createContent();
protected Label createInformation() {
var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
information.setGraphicTextGap(7);
information.textProperty().bind(PlatformThread.sync(wrapper.getInformation()));
information.getStyleClass().add("information");
AppFont.header(information);
var state = wrapper.getEntry().getProvider() != null
? wrapper.getEntry().getProvider().stateDisplay(wrapper)
: Comp.empty();
information.setGraphic(state.createRegion());
return information;
}
private Label createSummary() {
protected Label createSummary() {
var summary = new Label();
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
summary.textProperty().bind(PlatformThread.sync(wrapper.getSummary()));
summary.getStyleClass().add("summary");
AppFont.small(summary);
return summary;
}
private void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
protected void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(wrapper.getState()), val -> {
switch (val) {
case LOAD_FAILED -> {
node.pseudoClassStateChanged(FAILED, true);
@ -83,111 +128,68 @@ 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 wrapper.getName();
// + (filtered.size() > 0 && entry.getEntry().getStore() instanceof
// FixedHierarchyStore
// ? " (" + filtered.size() + ")"
// : "");
},
wrapper.nameProperty(),
wrapper.getInformation()));
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() {
var img = entry.isDisabled()
protected Node createIcon(int w, int h) {
var img = wrapper.isDisabled()
? "disabled_icon.png"
: entry.getEntry()
: wrapper.getEntry()
.getProvider()
.getDisplayIconFileName(entry.getEntry().getStore());
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
.getDisplayIconFileName(wrapper.getEntry().getStore());
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), w, h);
var storeIcon = imageComp.createRegion();
storeIcon.getStyleClass().add("icon");
if (entry.getState().getValue().isUsable()) {
if (wrapper.getState().getValue().isUsable()) {
new FancyTooltipAugment<>(new SimpleStringProperty(
entry.getEntry().getProvider().getDisplayName()))
wrapper.getEntry().getProvider().getDisplayName()))
.augment(storeIcon);
}
storeIcon.setPadding(new Insets(3, 0, 0, 0));
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()) {
for (var p : wrapper.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
if (!actionProvider.isMajor()
|| p.getKey().equals(entry.getDefaultActionProvider().getValue())) {
|| p.getKey().equals(wrapper.getDefaultActionProvider().getValue())) {
continue;
}
var button = new IconButtonComp(
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
actionProvider.getIcon(wrapper.getEntry().getStore().asNeeded()), () -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
wrapper.getEntry().getStore().asNeeded());
action.execute();
});
});
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
actionProvider.getName(wrapper.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) {
@ -198,49 +200,51 @@ 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());
for (var p : entry.getActionProviders().entrySet()) {
for (var p : wrapper.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 name = actionProvider.getName(wrapper.getEntry().getStore().asNeeded());
var icon = actionProvider.getIcon(wrapper.getEntry().getStore().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
wrapper.getEntry().getStore().asNeeded());
action.execute();
});
});
@ -253,43 +257,35 @@ public class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(item);
}
if (entry.getActionProviders().size() > 0) {
if (wrapper.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()));
event -> DesktopHelper.browsePath(wrapper.getEntry().getDirectory()));
contextMenu.getItems().add(browse);
}
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
refresh.disableProperty().bind(entry.getRefreshable().not());
refresh.disableProperty().bind(wrapper.getRefreshable().not());
refresh.setOnAction(event -> {
DataStorage.get().refreshAsync(entry.getEntry(), true);
DataStorage.get().refreshAsync(wrapper.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());
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(wrapper.getDeletable().not());
del.setOnAction(event -> wrapper.delete());
contextMenu.getItems().add(del);
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

@ -1,17 +1,19 @@
package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.core.AppState;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableBooleanValue;
import javafx.geometry.Insets;
import javafx.scene.layout.Region;
import java.util.LinkedHashMap;
import java.util.List;
public class StoreEntryListComp extends SimpleComp {
@ -23,9 +25,10 @@ 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 content.styleClass("store-list-comp").styleClass(Styles.STRIPED);
var custom = StoreSection.customSection(e).hgrow();
return new HorizontalComp(List.of(Comp.spacer(10), custom, Comp.spacer(10))).styleClass("top");
}).apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
return content.styleClass("store-list-comp");
}
@Override

View file

@ -1,13 +1,16 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedHierarchyStore;
import javafx.beans.property.*;
import lombok.Getter;
@ -94,10 +97,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()) {
@ -164,20 +164,49 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
}
public void refreshIfNeeded() throws Exception {
var found = getDefaultActionProvider().getValue();
if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID) || found == null) {
if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID)
|| entry.getState().equals(DataStoreEntry.State.COMPLETE_NOT_VALIDATED)) {
getEntry().refresh(true);
PlatformThread.runLaterIfNeeded(() -> {
expanded.set(true);
});
}
}
public void refreshAsync() {
ThreadHelper.runFailableAsync(() -> {
getEntry().refresh(true);
});
}
public void refreshWithChildren() throws Exception {
getEntry().refresh(true);
var hasChildren = DataStorage.get().refreshChildren(entry);
PlatformThread.runLaterIfNeeded(() -> {
expanded.set(hasChildren);
});
}
public void refreshWithChildrenAsync() {
ThreadHelper.runFailableAsync(() -> {
refreshWithChildren();
});
}
public void mutateAsync(DataStore newValue) {
ThreadHelper.runAsync(() -> {
var hasChildren = DataStorage.get().setAndRefresh(getEntry(), newValue);
PlatformThread.runLaterIfNeeded(() -> {
expanded.set(hasChildren);
});
});
}
public void executeDefaultAction() throws Exception {
var found = getDefaultActionProvider().getValue();
entry.updateLastUsed();
if (found != null) {
entry.updateLastUsed();
refreshIfNeeded();
found.createAction(entry.getStore().asNeeded()).execute();
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
refreshWithChildrenAsync();
}
}

View file

@ -3,6 +3,7 @@ package io.xpipe.app.comp.storage.store;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.impl.LocalStore;
@ -35,7 +36,7 @@ public class StoreIntroComp extends SimpleComp {
});
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
scanButton.setOnAction(event -> ScanAlert.showAsync(new LocalStore(), false));
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().getStoreEntry(new LocalStore()), false));
var scanPane = new StackPane(scanButton);
scanPane.setAlignment(Pos.CENTER);

View file

@ -16,6 +16,7 @@ public class StoreLayoutComp extends SimpleComp {
var groupHeader = new StoreSidebarComp().createRegion();
r.setLeft(groupHeader);
r.setCenter(listR);
r.getStyleClass().add("layout");
return r;
}
}

View file

@ -1,9 +1,12 @@
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;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Value;
@ -14,12 +17,39 @@ 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 StoreSectionComp(e);
}
}
StoreEntryWrapper wrapper;
ObservableList<StoreSection> children;
int depth;
ObservableBooleanValue showDetails;
public StoreSection(StoreEntryWrapper wrapper, ObservableList<StoreSection> children, int depth) {
this.wrapper = wrapper;
this.children = children;
this.depth = depth;
if (wrapper != null) {
this.showDetails = Bindings.createBooleanBinding(
() -> {
return wrapper.getExpanded().get() || children.size() == 0;
},
wrapper.getExpanded(),
children);
} else {
this.showDetails = new SimpleBooleanProperty(true);
}
}
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
? o.wrapper.getEntry().getLastAccess()
o -> o.wrapper.getEntry().getState().isUsable()
? o.wrapper.getEntry().getLastModified()
: Instant.EPOCH)
.reversed()
.thenComparing(
@ -27,39 +57,31 @@ public class StoreSection implements StorageFilter.Filterable {
public static StoreSection createTopLevel() {
var topLevel = BindingsHelper.mappedContentBinding(
StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper));
StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper, 1));
var filtered = BindingsHelper.filteredContentBinding(topLevel, section -> {
if (!section.getWrapper().getEntry().getState().isUsable()) {
return true;
}
var parent = section.getWrapper()
.getEntry()
.getProvider()
.getParent(section.getWrapper().getEntry().getStore());
return parent == null
|| (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
return DataStorage.get()
.getParent(section.getWrapper().getEntry(), true)
.isEmpty();
});
var ordered = BindingsHelper.orderedContentBinding(filtered, COMPARATOR);
return new StoreSection(null, ordered);
return new StoreSection(null, ordered, 0);
}
private static StoreSection create(StoreEntryWrapper e) {
private static StoreSection create(StoreEntryWrapper e, int depth) {
if (!e.getEntry().getState().isUsable()) {
return new StoreSection(e, FXCollections.observableArrayList());
return new StoreSection(e, FXCollections.observableArrayList(), depth);
}
var filtered = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
other -> other.getEntry().getState().isUsable()
&& e.getEntry()
.getStore()
.equals(other.getEntry()
.getProvider()
.getParent(other.getEntry().getStore())));
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
var filtered =
BindingsHelper.filteredContentBinding(StoreViewState.get().getAllEntries(), other -> {
return DataStorage.get()
.getParent(other.getEntry(), true)
.map(found -> found.equals(e.getEntry()))
.orElse(false);
});
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1, depth + 1));
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
return new StoreSection(e, ordered);
return new StoreSection(e, ordered, depth);
}
@Override

View file

@ -8,23 +8,30 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.css.PseudoClass;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.List;
public class StoreEntrySection extends Comp<CompStructure<VBox>> {
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private final StoreSection section;
public StoreEntrySection(StoreSection section) {
public StoreSectionComp(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).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
var button = new IconButtonComp(
Bindings.createStringBinding(
() -> section.getWrapper().getExpanded().get()
@ -35,7 +42,7 @@ public class StoreEntrySection extends Comp<CompStructure<VBox>> {
() -> {
section.getWrapper().toggleExpanded();
})
.apply(struc -> struc.get().setPrefWidth(40))
.apply(struc -> struc.get().setPrefWidth(30))
.focusTraversable()
.accessibleText("Expand")
.disable(BindingsHelper.persist(
@ -51,23 +58,32 @@ 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));
})
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
.apply(struc -> struc.get().backgroundProperty().set(Background.fill(Color.color(0, 0, 0, 0.01))));
var spacer = Comp.of(() -> {
var padding = new Region();
padding.setMinWidth(25);
padding.setMaxWidth(25);
return padding;
});
return StoreSection.customSection(e).apply(GrowAugment.create(true, false));
}).hgrow();
var expanded = Bindings.createBooleanBinding(() -> {
return section.getWrapper().getExpanded().get() && section.getChildren().size() > 0;
}, section.getWrapper().getExpanded(), section.getChildren());
return new VerticalComp(List.of(
new HorizontalComp(topEntryList),
new HorizontalComp(List.of(spacer, content))
new HorizontalComp(topEntryList)
.apply(struc -> struc.get().setFillHeight(true)),
Comp.separator().visible(expanded),
new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getChildren()).isEqualTo(0))))))
.styleClass("store-entry-section-comp")
.apply(struc -> {
struc.get().setFillWidth(true);
SimpleChangeListener.apply(expanded, val -> {
struc.get().pseudoClassStateChanged(EXPANDED, val);
});
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
})
.createStructure();
}
}

View file

@ -2,7 +2,6 @@ package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
@ -13,8 +12,10 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.time.Instant;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
public class StoreViewState {
@ -55,25 +56,21 @@ public class StoreViewState {
DataStorage.get().addListener(new StorageListener() {
@Override
public void onStoreAdd(DataStoreEntry entry) {
public void onStoreAdd(DataStoreEntry... entry) {
var l = Arrays.stream(entry).map(StoreEntryWrapper::new).toList();
Platform.runLater(() -> {
var sg = new StoreEntryWrapper(entry);
allEntries.add(sg);
allEntries.addAll(l);
});
}
@Override
public void onStoreRemove(DataStoreEntry entry) {
public void onStoreRemove(DataStoreEntry... entry) {
var a = Arrays.stream(entry).collect(Collectors.toSet());
var l = StoreViewState.get().getAllEntries().stream().filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry())).toList();
Platform.runLater(() -> {
allEntries.removeIf(e -> e.getEntry().equals(entry));
allEntries.removeAll(l);
});
}
@Override
public void onCollectionAdd(DataSourceCollection collection) {}
@Override
public void onCollectionRemove(DataSourceCollection collection) {}
});
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppCache;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.comp.base.ButtonComp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;
@ -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

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.FileDropOverlayComp;
import io.xpipe.app.core.AppFont;
@ -12,11 +12,11 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.TabPaneComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.SimpleValidator;
import io.xpipe.app.util.Validatable;
import io.xpipe.app.util.Validator;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
@ -70,7 +70,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
@Override
protected Region createSimple() {
var isNamedStore =
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent();
DataStorage.get().getStoreDisplayName(selected.getValue()).isPresent();
var localStore = new SimpleObjectProperty<>(
!isNamedStore
&& selected.getValue() instanceof FileStore fileStore

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.ErrorOverlayComp;
import io.xpipe.app.comp.base.MultiStepComp;
@ -123,7 +123,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
e -> {
try {
DataStorage.get().addStoreEntry(e);
// ScanAlert.showAsync(e.getStore(), true);
ScanAlert.showAsync(e, true);
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store;
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListViewComp;

View file

@ -1,7 +1,6 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.issue.*;
@ -55,7 +54,6 @@ public class BaseMode extends OperationMode {
TrackEvent.info("mode", "Background mode shutdown started");
BrowserModel.DEFAULT.reset();
AppSocketServer.reset();
SourceCollectionViewState.reset();
StoreViewState.reset();
DataStorage.reset();
AppPrefs.reset();

View file

@ -1,6 +1,5 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.issue.TrackEvent;
@ -83,7 +82,6 @@ public abstract class PlatformMode extends OperationMode {
UpdateAvailableAlert.showIfNeeded();
}
SourceCollectionViewState.init();
StoreViewState.init();
}

View file

@ -1,70 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.ConvertExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.source.DataSource;
public class ConvertExchangeImpl extends ConvertExchange
implements MessageExchangeImpl<ConvertExchange.Request, ConvertExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), null, false);
DataSourceProvider<?> newProvider;
DataSource<?> newSource;
if (msg.getNewProvider() != null && msg.getNewCategory() != null) {
var provider = getProvider(msg.getNewProvider());
if (!provider.getPrimaryType().equals(msg.getNewCategory())) {
throw new ClientException("Incompatible types: " + provider.getId() + " and "
+ msg.getNewCategory().name().toLowerCase());
}
newProvider = provider;
newSource = newProvider.createDefaultSource(ds.getStore().asNeeded());
} else if (msg.getNewProvider() != null) {
if (msg.getNewProvider().equals(ds.getProvider().getId())) {
return ConvertExchange.Response.builder().build();
}
newProvider = getProvider(msg.getNewProvider());
newSource = newProvider.createDefaultSource(ds.getStore().asNeeded());
} else if (msg.getNewCategory() != null) {
if (msg.getNewCategory().equals(ds.getProvider().getPrimaryType())) {
return ConvertExchange.Response.builder().build();
}
var provider = ds.getProvider();
DataSource<?> s = ds.getSource();
if (!ds.getProvider().supportsConversion(s.asNeeded(), msg.getNewCategory())) {
throw new ClientException(
"Data source of type " + provider.getId() + " can not be converted to category "
+ msg.getNewCategory().name().toLowerCase());
}
newSource = provider.convert(s.asNeeded(), msg.getNewCategory());
newProvider = DataSourceProviders.byDataSourceClass(newSource.getClass());
} else {
throw new ClientException("No data format or data type specified");
}
var dialog = toCompleteConfig(newSource, newProvider, true);
var id = DataStorage.get().getId(ds);
var sent = Dialog.chain(
dialog,
Dialog.header("Successfully converted " + (id != null ? id : "source") + " to "
+ newProvider.getDisplayName()))
.evaluateTo(dialog);
var ref = DialogExchangeImpl.add(sent, (DataSource<?> o) -> {
ds.setSource(o);
DataStorage.get().save();
});
return ConvertExchange.Response.builder().config(ref).build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.EditExchange;
import io.xpipe.core.source.DataSource;
public class EditExchangeImpl extends EditExchange
implements MessageExchangeImpl<EditExchange.Request, EditExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getRef(), null, false);
var provider = source.getProvider();
var dialog = toCompleteConfig(source.getSource().asNeeded(), provider, true);
var config = DialogExchangeImpl.add(dialog, (DataSource<?> o) -> {
source.setSource(o);
});
return Response.builder()
.config(config)
.id(DataStorage.get().getId(source))
.build();
}
}

View file

@ -2,8 +2,6 @@ package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.beacon.BeaconHandler;
@ -14,9 +12,6 @@ import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.impl.NamedStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore;
import lombok.NonNull;
@ -68,42 +63,6 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
return store.get();
}
default DataSourceCollection getCollection(String name) throws ClientException {
var col = DataStorage.get().getCollectionForName(name);
if (col.isEmpty()) {
throw new ClientException("No collection with name " + name + " was found");
}
return col.get();
}
default DataSourceEntry getSourceEntry(DataSourceReference ref, DataSourceType typeFilter, boolean acceptDisabled)
throws ClientException {
var ds = DataStorage.get().getDataSource(ref);
if (ds.isEmpty() && ref.getType() == DataSourceReference.Type.LATEST) {
throw new ClientException("No latest data source available");
}
if (ds.isEmpty()) {
throw new ClientException("Unable to locate data source with reference " + ref.toRefString());
}
if (typeFilter != null && ds.get().getProvider().getPrimaryType() != typeFilter) {
throw new ClientException(
"Data source is not a " + typeFilter.name().toLowerCase());
}
if (!ds.get().getState().isUsable() && !acceptDisabled) {
throw new ClientException(
String.format("Data source %s is disabled", ds.get().getName()));
}
return ds.get();
}
default DataSourceEntry getSourceEntry(DataSourceId id, DataSourceType typeFilter, boolean acceptDisabled)
throws ClientException {
return getSourceEntry(DataSourceReference.id(id), typeFilter, acceptDisabled);
}
String getId();
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;

View file

@ -1,27 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.QueryDataSourceExchange;
import io.xpipe.core.dialog.DialogMapper;
public class QueryDataSourceExchangeImpl extends QueryDataSourceExchange
implements MessageExchangeImpl<QueryDataSourceExchange.Request, QueryDataSourceExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getRef(), null, false);
var id = DataStorage.get().getId(source);
var information = source.getInformation();
var dialog = source.getProvider().configDialog(source.getSource().asNeeded(), true);
var config = new DialogMapper(dialog).handle();
return QueryDataSourceExchange.Response.builder()
.id(id)
.type(source.getDataSourceType())
.information(information)
.internalSource(source.getSource())
.provider(source.getProvider().getId())
.config(config)
.build();
}
}

View file

@ -2,14 +2,11 @@ package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.ReadExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.source.DataSource;
import java.util.UUID;
@ -54,11 +51,6 @@ public class ReadExchangeImpl extends ReadExchange
var entryName =
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
var configRef = DialogExchangeImpl.add(Dialog.chain(typeQ, config), (DataSource<?> s) -> {
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, s);
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
});
return Response.builder().config(configRef).build();
return Response.builder().build();
}
}

View file

@ -1,22 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.SelectExchange;
import io.xpipe.core.source.DataSourceReference;
public class SelectExchangeImpl extends SelectExchange
implements MessageExchangeImpl<SelectExchange.Request, SelectExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
if (msg.getRef().getType() == DataSourceReference.Type.LATEST) {
throw new ClientException("Can't select latest data source");
}
var ds = getSourceEntry(msg.getRef(), null, false);
DataStorage.get().setLatest(ds);
return Response.builder().build();
}
}

View file

@ -1,50 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.WriteExecuteExchange;
import io.xpipe.core.impl.OutputStreamStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.WriteMode;
import io.xpipe.core.store.StreamDataStore;
public class WriteExecuteExchangeImpl extends WriteExecuteExchange
implements MessageExchangeImpl<WriteExecuteExchange.Request, WriteExecuteExchange.Response> {
@Override
@SuppressWarnings("unchecked")
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), null, false);
var target = WritePreparationExchangeImpl.CONFIGS.remove(msg.getId());
if (target.getType() != ds.getSource().getType()) {
throw new ClientException("Incompatible data source types");
}
var local = target.getStore() instanceof StreamDataStore s && s.isContentExclusivelyAccessible();
var mode = msg.getMode() != null ? WriteMode.byId(msg.getMode()) : WriteMode.REPLACE;
if (local) {
handler.postResponse(() -> {
var usedStore = new OutputStreamStore(handler.sendBody());
var out = ((DataSource<StreamDataStore>) target).withStore(usedStore);
try (var con = ds.getSource().openReadConnection();
var outCon = out.openWriteConnection(mode)) {
con.init();
outCon.init();
con.forward(outCon);
}
});
return WriteExecuteExchange.Response.builder().hasBody(true).build();
} else {
var out = target;
try (var con = ds.getSource().openReadConnection();
var outCon = out.openWriteConnection(mode)) {
con.init();
outCon.init();
con.forward(outCon);
}
return WriteExecuteExchange.Response.builder().build();
}
}
}

View file

@ -1,62 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.WritePreparationExchange;
import io.xpipe.core.source.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class WritePreparationExchangeImpl extends WritePreparationExchange
implements MessageExchangeImpl<WritePreparationExchange.Request, WritePreparationExchange.Response> {
public static final Map<UUID, DataSource<?>> CONFIGS = new HashMap<>();
@Override
public WritePreparationExchange.Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var store = resolveStore(msg.getOutputStore(), false);
var provider = DataSourceProviders.byPreferredStore(store, null);
DataSourceProvider<?> outputProvider;
DataSource<?> source;
if (msg.getOutputSource() == null) {
if (msg.getType() == null && provider.isEmpty()) {
var entry = getSourceEntry(msg.getSource(), null, false);
var sourceType = entry.getProvider();
if (sourceType.couldSupportStore(msg.getOutputStore())) {
outputProvider = sourceType;
} else {
throw new ClientException("Missing output type");
}
} else if (msg.getType() != null) {
outputProvider = DataSourceProviders.byName(msg.getType())
.orElseThrow(() -> new ClientException("Unknown output format type: " + msg.getType()));
} else if (provider.isPresent()) {
outputProvider = provider.get();
} else {
throw new IllegalStateException();
}
if (!outputProvider.couldSupportStore(store)) {
throw new ClientException("Unsupported store type");
}
source = outputProvider.createDefaultSource(store);
} else {
source = getSourceEntry(msg.getOutputSource(), null, false).getSource();
outputProvider = DataSourceProviders.byDataSourceClass(source.getClass());
}
var id = UUID.randomUUID();
var config = toCompleteConfig(source, outputProvider, false);
var configRef = DialogExchangeImpl.add(config, id, (DataSource<?> s) -> {
CONFIGS.put(id, s);
});
return Response.builder().config(configRef).build();
}
}

View file

@ -1,28 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.AddSourceExchange;
import java.util.UUID;
public class AddSourceExchangeImpl extends AddSourceExchange
implements MessageExchangeImpl<AddSourceExchange.Request, AddSourceExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var noTarget = msg.getTarget() == null;
var colName = noTarget ? null : msg.getTarget().getCollectionName();
var entryName =
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, msg.getSource());
entry.refresh(true);
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
return Response.builder().id(DataStorage.get().getId(entry)).build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.ForwardExchange;
import io.xpipe.core.source.WriteMode;
public class ForwardExchangeImpl extends ForwardExchange
implements MessageExchangeImpl<ForwardExchange.Request, ForwardExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getSource(), null, false);
var target = getSourceEntry(msg.getTarget(), null, false);
try (var con = source.getSource().openReadConnection();
var outCon =
target.getSource().openWriteConnection(msg.isAppend() ? WriteMode.APPEND : WriteMode.REPLACE)) {
con.init();
outCon.init();
con.forward(outCon);
}
return ForwardExchange.Response.builder().build();
}
}

View file

@ -1,25 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.RawReadConnection;
public class QueryRawDataExchangeImpl extends QueryRawDataExchange
implements MessageExchangeImpl<QueryRawDataExchange.Request, QueryRawDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.RAW, false);
handler.postResponse(() -> {
try (var out = handler.sendBody()) {
try (var con = (RawReadConnection) ds.getSource().openReadConnection()) {
con.init();
con.forwardBytes(out, msg.getMaxBytes());
}
}
});
return Response.builder().build();
}
}

View file

@ -1,39 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.TableReadConnection;
public class QueryTableDataExchangeImpl extends QueryTableDataExchange
implements MessageExchangeImpl<QueryTableDataExchange.Request, QueryTableDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.TABLE, false);
TableReadConnection readConnection = null;
TupleType dataType;
try {
readConnection = (TableReadConnection) ds.getDataSource().openReadConnection();
readConnection.init();
dataType = readConnection.getDataType();
TableReadConnection finalReadConnection = readConnection;
handler.postResponse(() -> {
try (var out = handler.sendBody()) {
try (var con = finalReadConnection) {
con.forwardRows(out, msg.getMaxRows());
}
}
});
} catch (Exception ex) {
if (readConnection != null) {
readConnection.close();
}
throw ex;
}
return QueryTableDataExchange.Response.builder().dataType(dataType).build();
}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.TextReadConnection;
import java.nio.charset.StandardCharsets;
public class QueryTextDataExchangeImpl extends QueryTextDataExchange
implements MessageExchangeImpl<QueryTextDataExchange.Request, QueryTextDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.TEXT, false);
handler.postResponse(() -> {
try (var out = handler.sendBody();
var con = ((TextReadConnection) ds.getSource().openReadConnection())) {
con.init();
try (var stream = con.lines().limit(msg.getMaxLines() == -1 ? Long.MAX_VALUE : msg.getMaxLines())) {
for (var s : stream.toList()) {
out.write(s.getBytes(StandardCharsets.UTF_8));
out.write("\n".getBytes(StandardCharsets.UTF_8));
}
}
}
});
return QueryTextDataExchange.Response.builder().build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.ListCollectionsExchange;
import io.xpipe.beacon.exchange.data.CollectionListEntry;
public class ListCollectionsExchangeImpl extends ListCollectionsExchange
implements MessageExchangeImpl<ListCollectionsExchange.Request, ListCollectionsExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
DataStorage s = DataStorage.get();
var e = s.getSourceCollections().stream()
.map(col -> CollectionListEntry.builder()
.name(col.getName())
.size(col.getEntries().size())
.lastUsed(col.getLastAccess())
.build())
.toList();
return Response.builder().entries(e).build();
}
}

View file

@ -1,37 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.ListEntriesExchange;
import io.xpipe.beacon.exchange.data.EntryListEntry;
import java.util.Optional;
public class ListEntriesExchangeImpl extends ListEntriesExchange
implements MessageExchangeImpl<ListEntriesExchange.Request, ListEntriesExchange.Response> {
@Override
public ListEntriesExchange.Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
DataStorage s = DataStorage.get();
var col = s.getCollectionForName(msg.getCollection())
.or(() -> Optional.ofNullable(
msg.getCollection().equalsIgnoreCase("temporary") ? s.getInternalCollection() : null));
if (col.isEmpty()) {
throw new ClientException("No collection with name " + msg.getCollection() + " was found");
}
var list = col.get().getEntries().stream()
.map(e -> {
return EntryListEntry.builder()
.name(e.getName())
.type(e.getProvider().getId())
.description("")
.lastUsed(e.getLastUsed())
.build();
})
.toList();
return Response.builder().entries(list).build();
}
}

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

@ -1,17 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RemoveCollectionExchange;
public class RemoveCollectionExchangeImpl extends RemoveCollectionExchange
implements MessageExchangeImpl<RemoveCollectionExchange.Request, RemoveCollectionExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var col = getCollection(msg.getCollectionName());
DataStorage.get().deleteCollection(col);
return Response.builder().build();
}
}

View file

@ -1,18 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RemoveEntryExchange;
public class RemoveEntryExchangeImpl extends RemoveEntryExchange
implements MessageExchangeImpl<RemoveEntryExchange.Request, RemoveEntryExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var e = getSourceEntry(msg.getRef(), null, true);
var id = DataStorage.get().getId(e);
DataStorage.get().deleteSourceEntry(e);
return Response.builder().id(id).build();
}
}

View file

@ -1,16 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RenameCollectionExchange;
public class RenameCollectionExchangeImpl extends RenameCollectionExchange
implements MessageExchangeImpl<RenameCollectionExchange.Request, RenameCollectionExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var col = getCollection(msg.getCollectionName());
col.setName(msg.getNewName());
return Response.builder().build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RenameEntryExchange;
public class RenameEntryExchangeImpl extends RenameEntryExchange
implements MessageExchangeImpl<RenameEntryExchange.Request, RenameEntryExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var e = getSourceEntry(msg.getRef(), null, true);
DataStorage.get().deleteSourceEntry(e);
DataStorage.get()
.add(
e,
DataStorage.get()
.getCollectionForName(msg.getNewId().getCollectionName())
.orElseThrow());
e.setName(msg.getNewId().getEntryName());
return Response.builder().build();
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.SourceProviderListExchange;
import io.xpipe.beacon.exchange.data.ProviderEntry;
import io.xpipe.core.source.DataSourceType;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class SourceProviderListExchangeImpl extends SourceProviderListExchange
implements MessageExchangeImpl<SourceProviderListExchange.Request, SourceProviderListExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
var all = DataSourceProviders.getAll();
var map = new LinkedHashMap<DataSourceType, List<ProviderEntry>>();
for (DataSourceType t : DataSourceType.values()) {
map.put(t, new ArrayList<>());
}
for (var p : all) {
map.get(p.getPrimaryType())
.add(ProviderEntry.builder()
.id(p.getPossibleNames().get(0))
.description(p.getDisplayDescription())
.build());
}
return Response.builder().entries(map).build();
}
}

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

@ -9,7 +9,6 @@ import javafx.beans.value.ObservableValue;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public interface ActionProvider {
@ -29,7 +28,7 @@ public interface ActionProvider {
return false;
}
})
.collect(Collectors.toSet()));
.toList());
}
@Override

View file

@ -1,13 +1,16 @@
package io.xpipe.app.ext;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.storage.store.StandardStoreEntryComp;
import io.xpipe.app.comp.storage.store.StoreSectionComp;
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.app.storage.DataStoreEntry;
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 +34,36 @@ public interface DataStoreProvider {
}
}
default Comp<?> customDisplay(StoreSection s) {
return new StandardStoreEntryComp(s.getWrapper(), null);
}
default Comp<?> customContainer(StoreSection section) {
return new StoreSectionComp(section);
}
default String failureInfo() {
return null;
}
default Comp<?> stateDisplay(StoreEntryWrapper w) {
var state = Bindings.createObjectBinding(
() -> {
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID
? SystemStateComp.State.FAILURE
: w.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
? SystemStateComp.State.SUCCESS
: SystemStateComp.State.OTHER;
},
w.getState());
var name = Bindings.createStringBinding(
() -> {
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID ? "stop" : "start";
},
w.getState());
return new SystemStateComp(name, state);
}
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
var content = Bindings.createStringBinding(
() -> {
@ -77,10 +110,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;
}
@ -97,6 +134,10 @@ public interface DataStoreProvider {
String queryInformationString(DataStore store, int length) throws Exception;
default String queryInvalidInformationString(DataStore store, int length) throws Exception {
return null;
}
String toSummaryString(DataStore store, int length);
default String i18n(String key) {
@ -129,7 +170,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 +186,7 @@ public interface DataStoreProvider {
List<Class<?>> getStoreClasses();
default boolean shouldShow() {
default boolean canManuallyCreate() {
return true;
}

View file

@ -1,8 +1,8 @@
package io.xpipe.app.ext;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.ModuleLayerLoader;
import lombok.Value;
import org.apache.commons.lang3.function.FailableRunnable;
@ -53,7 +53,7 @@ public abstract class ScanProvider {
return null;
}
public ScanOperation create(ShellStore store, ShellControl sc, boolean automatic) throws Exception {
public ScanOperation create(DataStoreEntry entry, ShellControl sc, boolean automatic) throws Exception {
return null;
}
}

View file

@ -3,7 +3,6 @@ package io.xpipe.app.ext;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ModuleLayerLoader;
@ -12,7 +11,8 @@ import io.xpipe.core.util.ProxyFunction;
public class XPipeServiceProviders {
public static void load(ModuleLayer layer) {
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
// TODO
var hasDaemon = true;
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
ErrorEvent.fromThrowable(t).handle();
});

View file

@ -1,12 +1,13 @@
package io.xpipe.app.fxcomps;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.WrapperComp;
import io.xpipe.app.fxcomps.util.Shortcuts;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.Separator;
import javafx.scene.control.Tooltip;
@ -26,19 +27,25 @@ public abstract class Comp<S extends CompStructure<?>> {
private List<Augment<S>> augments;
public static Comp<CompStructure<Region>> empty() {
return of(() -> new Region());
}
public static Comp<CompStructure<Spacer>> spacer(double size) {
return of(() -> new Spacer(size));
}
public static <R extends Region> Comp<CompStructure<R>> of(Supplier<R> r) {
return new WrapperComp<>(() -> {
var region = r.get();
return () -> region;
});
return new Comp<>() {
@Override
public CompStructure<R> createBase() {
return new SimpleCompStructure<>(r.get());
}
};
}
public static Comp<CompStructure<Separator>> separator() {
return Comp.of(() -> new Separator());
}
public static <S extends CompStructure<?>> Comp<S> ofStructure(Supplier<S> r) {
return new WrapperComp<>(r);
return of(() -> new Separator(Orientation.HORIZONTAL));
}
@SuppressWarnings("unchecked")

View file

@ -6,7 +6,6 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LeafShellStore;
import io.xpipe.core.store.ShellStore;
@ -70,7 +69,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return Optional.of(AppI18n.get("none"));
}
return XPipeDaemon.getInstance().getStoreName(store);
return DataStorage.get().getStoreDisplayName(store);
})
.orElse(AppI18n.get("unknown"));
@ -82,7 +81,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return AppI18n.get("none");
}
return XPipeDaemon.getInstance().getStoreName(store).orElse("?");
return DataStorage.get().getStoreDisplayName(store).orElse("?");
}
@Override

View file

@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
@ -27,7 +26,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
var name = DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(store))
.findAny()
.map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?"))
.map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
.orElse("?");
return name;
}

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

@ -6,15 +6,12 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebView;
public class PrettyImageComp extends SimpleComp {
@ -31,6 +28,8 @@ public class PrettyImageComp extends SimpleComp {
@Override
protected Region createSimple() {
var aspectRatioProperty = new SimpleDoubleProperty(1);
var imageAspectRatioProperty = new SimpleDoubleProperty(1);
var svgAspectRatioProperty = new SimpleDoubleProperty(1);
var widthProperty = Bindings.createDoubleBinding(
() -> {
boolean widthLimited = width / height < aspectRatioProperty.doubleValue();
@ -51,89 +50,84 @@ public class PrettyImageComp extends SimpleComp {
}
},
aspectRatioProperty);
var image = new SimpleStringProperty();
var currentNode = new SimpleObjectProperty<Node>();
var stack = new StackPane();
{
var svgImageContent = new SimpleStringProperty();
var storeIcon = SvgView.create(svgImageContent);
SimpleChangeListener.apply(image, newValue -> {
if (AppImages.hasSvgImage(newValue)) {
svgImageContent.set(AppImages.svgImage(newValue));
}
});
var ar = Bindings.createDoubleBinding(
() -> {
return storeIcon.getWidth().getValue().doubleValue()
/ storeIcon.getHeight().getValue().doubleValue();
},
storeIcon.getWidth(),
storeIcon.getHeight());
svgAspectRatioProperty.bind(ar);
var node = storeIcon.createWebview();
node.prefWidthProperty().bind(widthProperty);
node.maxWidthProperty().bind(widthProperty);
node.minWidthProperty().bind(widthProperty);
node.prefHeightProperty().bind(heightProperty);
node.maxHeightProperty().bind(heightProperty);
node.minHeightProperty().bind(heightProperty);
stack.getChildren().add(node);
}
{
var storeIcon = new ImageView();
storeIcon.setFocusTraversable(false);
storeIcon
.imageProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (!AppImages.hasNormalImage(image.getValue())) {
return null;
}
return AppImages.image(image.getValue());
},
image));
var ar = Bindings.createDoubleBinding(
() -> {
if (storeIcon.getImage() == null) {
return 1.0;
}
return storeIcon.getImage().getWidth()
/ storeIcon.getImage().getHeight();
},
storeIcon.imageProperty());
imageAspectRatioProperty.bind(ar);
storeIcon.fitWidthProperty().bind(widthProperty);
storeIcon.fitHeightProperty().bind(heightProperty);
storeIcon.setSmooth(true);
stack.getChildren().add(storeIcon);
}
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
image.set(val);
var requiresChange = val == null
|| (val.endsWith(".svg") && !(currentNode.get() instanceof WebView)
|| !(currentNode.get() instanceof ImageView));
if (!requiresChange) {
return;
}
aspectRatioProperty.unbind();
if (val == null) {
currentNode.set(new Region());
stack.getChildren().get(0).setOpacity(0.0);
stack.getChildren().get(1).setOpacity(0.0);
} else if (val.endsWith(".svg")) {
var storeIcon = SvgView.create(Bindings.createStringBinding(
() -> {
if (!AppImages.hasSvgImage(image.getValue())) {
return null;
}
return AppImages.svgImage(image.getValue());
},
image));
var ar = Bindings.createDoubleBinding(
() -> {
return storeIcon.getWidth().getValue().doubleValue()
/ storeIcon.getHeight().getValue().doubleValue();
},
storeIcon.getWidth(),
storeIcon.getHeight());
aspectRatioProperty.bind(ar);
var node = storeIcon.createWebview();
node.prefWidthProperty().bind(widthProperty);
node.maxWidthProperty().bind(widthProperty);
node.minWidthProperty().bind(widthProperty);
node.prefHeightProperty().bind(heightProperty);
node.maxHeightProperty().bind(heightProperty);
node.minHeightProperty().bind(heightProperty);
currentNode.set(node);
aspectRatioProperty.bind(svgAspectRatioProperty);
stack.getChildren().get(0).setOpacity(1.0);
stack.getChildren().get(1).setOpacity(0.0);
} else {
var storeIcon = new ImageView();
storeIcon.setFocusTraversable(false);
storeIcon
.imageProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (!AppImages.hasNormalImage(image.getValue())) {
return null;
}
return AppImages.image(image.getValue());
},
image));
var ar = Bindings.createDoubleBinding(
() -> {
if (storeIcon.getImage() == null) {
return 1.0;
}
return storeIcon.getImage().getWidth()
/ storeIcon.getImage().getHeight();
},
storeIcon.imageProperty());
aspectRatioProperty.bind(ar);
storeIcon.fitWidthProperty().bind(widthProperty);
storeIcon.fitHeightProperty().bind(heightProperty);
storeIcon.setSmooth(true);
currentNode.set(storeIcon);
aspectRatioProperty.bind(imageAspectRatioProperty);
stack.getChildren().get(0).setOpacity(0.0);
stack.getChildren().get(1).setOpacity(1.0);
}
});
var stack = new StackPane();
SimpleChangeListener.apply(currentNode, val -> {
if (val == null) {
stack.getChildren().clear();
return;
}
stack.getChildren().setAll(val);
});
stack.setFocusTraversable(false);
stack.setPrefWidth(width);
stack.setMinWidth(width);

View file

@ -1,20 +0,0 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import java.util.function.Supplier;
public class WrapperComp<S extends CompStructure<?>> extends Comp<S> {
private final Supplier<S> structureSupplier;
public WrapperComp(Supplier<S> structureSupplier) {
this.structureSupplier = structureSupplier;
}
@Override
public S createBase() {
return structureSupplier.get();
}
}

View file

@ -1,10 +0,0 @@
package io.xpipe.app.storage;
public interface CollectionListener {
void onUpdate();
void onEntryAdd(DataSourceEntry entry);
void onEntryRemove(DataSourceEntry entry);
}

View file

@ -1,141 +0,0 @@
package io.xpipe.app.storage;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.core.util.JacksonMapper;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
public class DataSourceCollection extends StorageElement {
private final Map<UUID, DataSourceEntry> entries;
private final List<CollectionListener> listeners;
private DataSourceCollection(
Path directory,
UUID uuid,
String name,
Instant lastUsed,
Instant lastModified,
Map<UUID, DataSourceEntry> entries,
boolean dirty) {
super(directory, uuid, name, lastUsed, lastModified, dirty);
this.entries = new LinkedHashMap<>(entries);
this.listeners = new ArrayList<>();
this.listeners.add(new CollectionListener() {
@Override
public void onUpdate() {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
@Override
public void onEntryAdd(DataSourceEntry entry) {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
@Override
public void onEntryRemove(DataSourceEntry entry) {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
});
}
public static DataSourceCollection createNew(String name) {
var c = new DataSourceCollection(null, UUID.randomUUID(), name, null, Instant.now(), Map.of(), true);
return c;
}
public static DataSourceCollection fromDirectory(DataStorage storage, Path dir) throws Exception {
ObjectMapper mapper = JacksonMapper.newMapper();
var json = mapper.readTree(dir.resolve("collection.json").toFile());
var uuid = UUID.fromString(json.required("uuid").textValue());
var name = json.required("name").textValue();
Objects.requireNonNull(name);
var lastModified = Instant.parse(json.required("lastModified").textValue());
JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, UUID.class);
var entries = new LinkedHashMap<UUID, DataSourceEntry>();
for (var u : mapper.<List<UUID>>readValue(dir.resolve("entries.json").toFile(), listType)) {
var v = storage.getSourceEntry(u).orElse(null);
entries.put(u, v);
}
Instant lastUsed = entries.values().stream()
.filter(Objects::nonNull)
.map(DataSourceEntry::getLastUsed)
.max(Comparator.naturalOrder())
.orElse(null);
return new DataSourceCollection(dir, uuid, name, lastUsed, lastModified, entries, false);
}
public void writeDataToDisk() throws Exception {
if (!dirty) {
return;
}
ObjectNode obj = JsonNodeFactory.instance.objectNode();
obj.put("uuid", uuid.toString());
obj.put("name", name);
obj.put("lastModified", lastModified.toString());
ObjectMapper mapper = JacksonMapper.newMapper();
var collectionString = mapper.writeValueAsString(obj);
var entriesString = mapper.writeValueAsString(entries.keySet().stream().toList());
FileUtils.forceMkdir(directory.toFile());
Files.writeString(directory.resolve("collection.json"), collectionString);
Files.writeString(directory.resolve("entries.json"), entriesString);
this.dirty = false;
}
public void addListener(CollectionListener l) {
this.listeners.add(l);
}
public void addEntry(DataSourceEntry e) {
this.entries.put(e.getUuid(), e);
this.listeners.forEach(l -> l.onEntryAdd(e));
}
public void removeEntry(DataSourceEntry e) {
this.entries.remove(e.getUuid());
this.listeners.forEach(l -> l.onEntryRemove(e));
}
@Override
public void refresh(boolean deep) {}
public void setName(String name) {
if (name.equals(this.name)) {
return;
}
var oldName = this.name;
this.name = name;
this.listeners.forEach(l -> l.onUpdate());
}
@Override
protected boolean shouldSave() {
return true;
}
public void clear() {
entries.clear();
}
public List<DataSourceEntry> getEntries() {
return entries.values().stream().filter(Objects::nonNull).toList();
}
}

Some files were not shown because too many files have changed in this diff Show more