mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
More store rework
This commit is contained in:
parent
9321af9998
commit
38cdf08bf2
117 changed files with 290 additions and 6924 deletions
|
@ -2,25 +2,16 @@ package io.xpipe.api.impl;
|
||||||
|
|
||||||
import io.xpipe.api.DataSourceConfig;
|
import io.xpipe.api.DataSourceConfig;
|
||||||
import io.xpipe.api.DataTable;
|
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.ArrayNode;
|
||||||
import io.xpipe.core.data.node.DataStructureNode;
|
import io.xpipe.core.data.node.DataStructureNode;
|
||||||
import io.xpipe.core.data.node.TupleNode;
|
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.DataSourceId;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
|
||||||
import io.xpipe.core.source.DataSourceType;
|
import io.xpipe.core.source.DataSourceType;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.ArrayList;
|
||||||
import java.io.InputStream;
|
import java.util.Iterator;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
public class DataTableImpl extends DataSourceImpl implements DataTable {
|
public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||||
|
|
||||||
|
@ -34,9 +25,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<TupleNode> stream() {
|
public Stream<TupleNode> stream() {
|
||||||
var iterator = new TableIterator();
|
return Stream.of();
|
||||||
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
|
|
||||||
.onClose(iterator::finish);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,71 +41,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||||
@Override
|
@Override
|
||||||
public ArrayNode read(int maxRows) {
|
public ArrayNode read(int maxRows) {
|
||||||
List<DataStructureNode> nodes = new ArrayList<>();
|
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);
|
return ArrayNode.of(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<TupleNode> iterator() {
|
public Iterator<TupleNode> iterator() {
|
||||||
return new TableIterator();
|
return new Iterator<TupleNode>() {
|
||||||
}
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
private class TableIterator implements Iterator<TupleNode> {
|
return false;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
if (node == null) {
|
|
||||||
// finish();
|
@Override
|
||||||
|
public TupleNode next() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return node != null;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TupleNode next() {
|
|
||||||
connection.checkClosed();
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,12 @@ package io.xpipe.api.impl;
|
||||||
|
|
||||||
import io.xpipe.api.DataSourceConfig;
|
import io.xpipe.api.DataSourceConfig;
|
||||||
import io.xpipe.api.DataText;
|
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.DataSourceId;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
|
||||||
import io.xpipe.core.source.DataSourceType;
|
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.List;
|
||||||
import java.util.Spliterator;
|
|
||||||
import java.util.Spliterators;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
public class DataTextImpl extends DataSourceImpl implements DataText {
|
public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||||
|
|
||||||
|
@ -53,47 +40,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<String> lines() {
|
public Stream<String> lines() {
|
||||||
var iterator = new Iterator<String>() {
|
return Stream.of();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.BusyProperty;
|
import io.xpipe.app.util.BusyProperty;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.core.impl.FileNames;
|
import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.process.ShellDialects;
|
import io.xpipe.core.process.ShellDialects;
|
||||||
|
@ -133,7 +132,7 @@ public final class OpenFileSystemModel {
|
||||||
&& fileSystem.getShell().isPresent()) {
|
&& fileSystem.getShell().isPresent()) {
|
||||||
var directory = currentPath.get();
|
var directory = currentPath.get();
|
||||||
var name = adjustedPath + " - "
|
var name = adjustedPath + " - "
|
||||||
+ XPipeDaemon.getInstance().getStoreName(store).orElse("?");
|
+ DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (ShellDialects.ALL.stream()
|
if (ShellDialects.ALL.stream()
|
||||||
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
||||||
|
@ -379,8 +378,7 @@ public final class OpenFileSystemModel {
|
||||||
var command = s.control()
|
var command = s.control()
|
||||||
.initWith(connection.getShellDialect().getCdCommand(directory))
|
.initWith(connection.getShellDialect().getCdCommand(directory))
|
||||||
.prepareTerminalOpen(directory + " - "
|
.prepareTerminalOpen(directory + " - "
|
||||||
+ XPipeDaemon.getInstance()
|
+ DataStorage.get().getStoreDisplayName(store)
|
||||||
.getStoreName(store)
|
|
||||||
.orElse("?"));
|
.orElse("?"));
|
||||||
TerminalHelper.open(directory, command);
|
TerminalHelper.open(directory, command);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
@ -18,6 +19,9 @@ import java.util.function.Function;
|
||||||
|
|
||||||
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
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> shown;
|
||||||
private final ObservableList<T> all;
|
private final ObservableList<T> all;
|
||||||
private final Function<T, Comp<?>> compFunction;
|
private final Function<T, Comp<?>> compFunction;
|
||||||
|
@ -65,6 +69,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||||
})
|
})
|
||||||
.toList();
|
.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)) {
|
if (!listView.getChildren().equals(newShown)) {
|
||||||
listView.getChildren().setAll(newShown);
|
listView.getChildren().setAll(newShown);
|
||||||
listView.layout();
|
listView.layout();
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
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.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
public class SystemStateComp extends SimpleComp {
|
||||||
|
|
||||||
|
|
||||||
|
public SystemStateComp(ObservableValue<String> name, ObservableValue<State> state) {
|
||||||
|
this.name = name;
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum State {
|
||||||
|
STOPPED,
|
||||||
|
RUNNING,
|
||||||
|
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.STOPPED ? "mdmz-stop_circle" : state.getValue() == State.RUNNING ? "mdrmz-play_circle_outline" : "mdmz-remove_circle_outline";
|
||||||
|
}, state));
|
||||||
|
var fi = new FontIcon();
|
||||||
|
SimpleChangeListener.apply(icon, val -> fi.setIconLiteral(val));
|
||||||
|
new FancyTooltipAugment<>(PlatformThread.sync(name)).augment(fi);
|
||||||
|
|
||||||
|
var pane = new Pane(fi);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +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.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 -> {
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +1,29 @@
|
||||||
package io.xpipe.app.comp.storage.store;
|
package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
|
||||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
|
||||||
import io.xpipe.app.core.AppFont;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
public class DenseStoreEntryComp extends StoreEntryComp {
|
public class DenseStoreEntryComp extends StoreEntryComp {
|
||||||
|
|
||||||
private final boolean showIcon;
|
private final boolean showIcon;
|
||||||
private final Comp<?> content;
|
|
||||||
|
|
||||||
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
|
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
|
||||||
super(entry);
|
super(entry, content);
|
||||||
this.showIcon = showIcon;
|
this.showIcon = showIcon;
|
||||||
this.content = content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Region createContent() {
|
protected Region createContent() {
|
||||||
var name = createName().createRegion();
|
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 grid = new GridPane();
|
||||||
|
grid.setHgap(10);
|
||||||
|
|
||||||
if (showIcon) {
|
if (showIcon) {
|
||||||
var storeIcon = createIcon(30, 25);
|
var storeIcon = createIcon(30, 25);
|
||||||
grid.getColumnConstraints().add(new ColumnConstraints(45));
|
grid.getColumnConstraints().add(new ColumnConstraints(30));
|
||||||
grid.add(storeIcon, 0, 0);
|
grid.add(storeIcon, 0, 0);
|
||||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,60 +31,40 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||||
grid.getColumnConstraints().add(new ColumnConstraints(5));
|
grid.getColumnConstraints().add(new ColumnConstraints(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
var fill = new ColumnConstraints();
|
var custom = new ColumnConstraints(content != null ? 300 : 0);
|
||||||
fill.setHgrow(Priority.ALWAYS);
|
custom.setHalignment(HPos.RIGHT);
|
||||||
grid.getColumnConstraints().addAll(new ColumnConstraints(450), fill);
|
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(name, 1, 0);
|
||||||
|
|
||||||
var c = content != null ? content.createRegion() : new Region();
|
grid.add(createInformation(), 2, 0);
|
||||||
grid.add(c, 2, 0);
|
grid.getColumnConstraints().addAll(info, custom);
|
||||||
GridPane.setHalignment(c, HPos.CENTER);
|
|
||||||
|
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);
|
||||||
|
|
||||||
grid.add(createButtonBar().createRegion(), 3, 0, 1, 1);
|
|
||||||
GrowAugment.create(true, false).augment(grid);
|
GrowAugment.create(true, false).augment(grid);
|
||||||
|
|
||||||
AppFont.small(size);
|
|
||||||
AppFont.small(date);
|
|
||||||
|
|
||||||
grid.getStyleClass().add("store-entry-grid");
|
grid.getStyleClass().add("store-entry-grid");
|
||||||
|
grid.getStyleClass().add("dense");
|
||||||
|
|
||||||
applyState(grid);
|
applyState(grid);
|
||||||
|
return grid;
|
||||||
var button = new JFXButton();
|
|
||||||
button.setGraphic(grid);
|
|
||||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
|
|
||||||
button.getStyleClass().add("store-entry-comp");
|
|
||||||
button.getStyleClass().add("condensed-store-entry-comp");
|
|
||||||
button.setMaxWidth(2000);
|
|
||||||
button.setFocusTraversable(true);
|
|
||||||
button.accessibleTextProperty()
|
|
||||||
.bind(Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
return entry.getName();
|
|
||||||
},
|
|
||||||
entry.nameProperty()));
|
|
||||||
button.accessibleHelpProperty().bind(entry.getInformation());
|
|
||||||
button.setOnAction(event -> {
|
|
||||||
event.consume();
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
|
||||||
entry.refreshIfNeeded();
|
|
||||||
entry.executeDefaultAction();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
HBox.setHgrow(button, Priority.ALWAYS);
|
|
||||||
|
|
||||||
new ContextMenuAugment<>(() -> DenseStoreEntryComp.this.createContextMenu())
|
|
||||||
.augment(new SimpleCompStructure<>(button));
|
|
||||||
|
|
||||||
return new HBox(button, new Spacer(25));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
protected Region createSimple() {
|
|
||||||
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
|
|
||||||
var region = loading.createRegion();
|
|
||||||
return region;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,304 +1,60 @@
|
||||||
package io.xpipe.app.comp.storage.store;
|
package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
|
||||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
|
||||||
import io.xpipe.app.core.AppFont;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.ext.ActionProvider;
|
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.app.util.DesktopHelper;
|
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.css.PseudoClass;
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.control.*;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.layout.ColumnConstraints;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
public class StandardStoreEntryComp extends StoreEntryComp {
|
||||||
|
|
||||||
public class StandardStoreEntryComp extends SimpleComp {
|
public StandardStoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
|
||||||
|
super(entry, content);
|
||||||
public static Comp<?> customSection(StoreEntryWrapper e) {
|
|
||||||
var prov = e.getEntry().getProvider();
|
|
||||||
if (prov != null) {
|
|
||||||
return prov.customDisplay(e);
|
|
||||||
} else {
|
|
||||||
return new StandardStoreEntryComp(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final double NAME_WIDTH = 0.30;
|
|
||||||
private static final double STORE_TYPE_WIDTH = 0.08;
|
|
||||||
private static final double DETAILS_WIDTH = 0.52;
|
|
||||||
private static final double BUTTONS_WIDTH = 0.1;
|
|
||||||
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
|
||||||
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
|
||||||
private final StoreEntryWrapper entry;
|
|
||||||
|
|
||||||
public StandardStoreEntryComp(StoreEntryWrapper entry) {
|
|
||||||
this.entry = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Label createInformation() {
|
|
||||||
var information = new Label();
|
|
||||||
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
|
|
||||||
information.getStyleClass().add("information");
|
|
||||||
AppFont.header(information);
|
|
||||||
return information;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Label createSummary() {
|
|
||||||
var summary = new Label();
|
|
||||||
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
|
|
||||||
summary.getStyleClass().add("summary");
|
|
||||||
AppFont.small(summary);
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyState(Node node) {
|
|
||||||
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
|
|
||||||
switch (val) {
|
|
||||||
case LOAD_FAILED -> {
|
|
||||||
node.pseudoClassStateChanged(FAILED, true);
|
|
||||||
node.pseudoClassStateChanged(INCOMPLETE, false);
|
|
||||||
}
|
|
||||||
case INCOMPLETE -> {
|
|
||||||
node.pseudoClassStateChanged(FAILED, false);
|
|
||||||
node.pseudoClassStateChanged(INCOMPLETE, true);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
node.pseudoClassStateChanged(FAILED, false);
|
|
||||||
node.pseudoClassStateChanged(INCOMPLETE, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comp<?> createName() {
|
|
||||||
var name = new LabelComp(entry.nameProperty())
|
|
||||||
.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
|
|
||||||
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
|
|
||||||
name.apply(s -> AppFont.header(s.get()));
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Node createIcon() {
|
|
||||||
var img = entry.isDisabled()
|
|
||||||
? "disabled_icon.png"
|
|
||||||
: entry.getEntry()
|
|
||||||
.getProvider()
|
|
||||||
.getDisplayIconFileName(entry.getEntry().getStore());
|
|
||||||
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
|
|
||||||
var storeIcon = imageComp.createRegion();
|
|
||||||
storeIcon.getStyleClass().add("icon");
|
|
||||||
if (entry.getState().getValue().isUsable()) {
|
|
||||||
new FancyTooltipAugment<>(new SimpleStringProperty(
|
|
||||||
entry.getEntry().getProvider().getDisplayName()))
|
|
||||||
.augment(storeIcon);
|
|
||||||
}
|
|
||||||
return storeIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Region createContent() {
|
protected Region createContent() {
|
||||||
var name = createName().createRegion();
|
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 grid = new GridPane();
|
||||||
|
grid.setHgap(10);
|
||||||
|
grid.setVgap(0);
|
||||||
|
|
||||||
var storeIcon = createIcon();
|
var storeIcon = createIcon(60, 45);
|
||||||
|
|
||||||
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(storeIcon, 0, 0, 1, 2);
|
||||||
grid.add(name, 1, 0);
|
grid.getColumnConstraints().add(new ColumnConstraints(60));
|
||||||
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);
|
grid.add(name, 1, 0);
|
||||||
AppFont.small(date);
|
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");
|
grid.getStyleClass().add("store-entry-grid");
|
||||||
|
|
||||||
applyState(grid);
|
applyState(grid);
|
||||||
|
|
||||||
var button = new JFXButton();
|
return grid;
|
||||||
button.setGraphic(grid);
|
|
||||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
|
|
||||||
button.getStyleClass().add("store-entry-comp");
|
|
||||||
button.setMaxWidth(2000);
|
|
||||||
button.setFocusTraversable(true);
|
|
||||||
button.accessibleTextProperty()
|
|
||||||
.bind(Bindings.createStringBinding(
|
|
||||||
() -> {
|
|
||||||
return entry.getName();
|
|
||||||
},
|
|
||||||
entry.nameProperty()));
|
|
||||||
button.accessibleHelpProperty().bind(entry.getInformation());
|
|
||||||
button.setOnAction(event -> {
|
|
||||||
event.consume();
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
|
||||||
entry.refreshIfNeeded();
|
|
||||||
entry.executeDefaultAction();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
new ContextMenuAugment<>(() -> StandardStoreEntryComp.this.createContextMenu())
|
|
||||||
.augment(new SimpleCompStructure<>(button));
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Comp<?> createButtonBar() {
|
|
||||||
var list = new ArrayList<Comp<?>>();
|
|
||||||
for (var p : entry.getActionProviders().entrySet()) {
|
|
||||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
|
||||||
if (!actionProvider.isMajor()
|
|
||||||
|| p.getKey().equals(entry.getDefaultActionProvider().getValue())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var button = new IconButtonComp(
|
|
||||||
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
|
||||||
var action = actionProvider.createAction(
|
|
||||||
entry.getEntry().getStore().asNeeded());
|
|
||||||
action.execute();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
button.apply(new FancyTooltipAugment<>(
|
|
||||||
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
|
|
||||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
|
||||||
button.hide(Bindings.not(p.getValue()));
|
|
||||||
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
|
|
||||||
button.disable(Bindings.not(p.getValue()));
|
|
||||||
}
|
|
||||||
list.add(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
var settingsButton = createSettingsButton();
|
|
||||||
list.add(settingsButton);
|
|
||||||
return new HorizontalComp(list)
|
|
||||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT))
|
|
||||||
.apply(struc -> {
|
|
||||||
for (Node child : struc.get().getChildren()) {
|
|
||||||
((Region) child)
|
|
||||||
.prefWidthProperty()
|
|
||||||
.bind((struc.get().heightProperty().divide(1.7)));
|
|
||||||
((Region) child).prefHeightProperty().bind((struc.get().heightProperty()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Comp<?> createSettingsButton() {
|
|
||||||
var settingsButton = new IconButtonComp("mdomz-settings");
|
|
||||||
settingsButton.styleClass("settings");
|
|
||||||
settingsButton.accessibleText("Settings");
|
|
||||||
settingsButton.apply(new ContextMenuAugment<>(
|
|
||||||
event -> event.getButton() == MouseButton.PRIMARY, () -> StandardStoreEntryComp.this.createContextMenu()));
|
|
||||||
settingsButton.apply(GrowAugment.create(false, true));
|
|
||||||
settingsButton.apply(s -> {
|
|
||||||
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
|
|
||||||
});
|
|
||||||
settingsButton.apply(new FancyTooltipAugment<>("more"));
|
|
||||||
return settingsButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ContextMenu createContextMenu() {
|
|
||||||
var contextMenu = new ContextMenu();
|
|
||||||
AppFont.normal(contextMenu.getStyleableNode());
|
|
||||||
|
|
||||||
for (var p : entry.getActionProviders().entrySet()) {
|
|
||||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
|
||||||
if (actionProvider.isMajor()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = actionProvider.getName(entry.getEntry().getStore().asNeeded());
|
|
||||||
var icon = actionProvider.getIcon(entry.getEntry().getStore().asNeeded());
|
|
||||||
var item = new MenuItem(null, new FontIcon(icon));
|
|
||||||
item.setOnAction(event -> {
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
|
||||||
var action = actionProvider.createAction(
|
|
||||||
entry.getEntry().getStore().asNeeded());
|
|
||||||
action.execute();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
item.textProperty().bind(name);
|
|
||||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
|
||||||
item.visibleProperty().bind(p.getValue());
|
|
||||||
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
|
|
||||||
item.disableProperty().bind(Bindings.not(p.getValue()));
|
|
||||||
}
|
|
||||||
contextMenu.getItems().add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.getActionProviders().size() > 0) {
|
|
||||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AppPrefs.get().developerMode().getValue()) {
|
|
||||||
var browse = new MenuItem(AppI18n.get("browse"), new FontIcon("mdi2f-folder-open-outline"));
|
|
||||||
browse.setOnAction(
|
|
||||||
event -> DesktopHelper.browsePath(entry.getEntry().getDirectory()));
|
|
||||||
contextMenu.getItems().add(browse);
|
|
||||||
}
|
|
||||||
|
|
||||||
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
|
|
||||||
refresh.disableProperty().bind(entry.getRefreshable().not());
|
|
||||||
refresh.setOnAction(event -> {
|
|
||||||
DataStorage.get().refreshAsync(entry.getEntry(), true);
|
|
||||||
});
|
|
||||||
contextMenu.getItems().add(refresh);
|
|
||||||
|
|
||||||
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
|
|
||||||
del.disableProperty().bind(entry.getDeletable().not());
|
|
||||||
del.setOnAction(event -> entry.delete());
|
|
||||||
contextMenu.getItems().add(del);
|
|
||||||
|
|
||||||
return contextMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ColumnConstraints createShareConstraint(Region r, double share) {
|
|
||||||
var cc = new ColumnConstraints();
|
|
||||||
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
protected Region createSimple() {
|
|
||||||
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
|
|
||||||
var region = loading.createRegion();
|
|
||||||
return region;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
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.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.DataStoreProvider;
|
import io.xpipe.app.ext.DataStoreProvider;
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package io.xpipe.app.comp.storage.store;
|
package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.ext.ActionProvider;
|
import io.xpipe.app.ext.ActionProvider;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
|
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
import io.xpipe.app.fxcomps.impl.*;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
@ -25,6 +30,8 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.ColumnConstraints;
|
import javafx.scene.layout.ColumnConstraints;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
@ -37,22 +44,58 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
if (prov != null) {
|
if (prov != null) {
|
||||||
return prov.customDisplay(e);
|
return prov.customDisplay(e);
|
||||||
} else {
|
} else {
|
||||||
return new StandardStoreEntryComp(e);
|
return new StandardStoreEntryComp(e, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final double NAME_WIDTH = 0.30;
|
|
||||||
public static final double STORE_TYPE_WIDTH = 0.08;
|
|
||||||
public static final double DETAILS_WIDTH = 0.52;
|
|
||||||
public static final double BUTTONS_WIDTH = 0.1;
|
|
||||||
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
||||||
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
||||||
protected final StoreEntryWrapper entry;
|
protected final StoreEntryWrapper entry;
|
||||||
|
protected final Comp<?> content;
|
||||||
|
|
||||||
public StoreEntryComp(StoreEntryWrapper entry) {
|
public StoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
|
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 entry.getName();
|
||||||
|
},
|
||||||
|
entry.nameProperty()));
|
||||||
|
button.accessibleHelpProperty().bind(entry.getInformation());
|
||||||
|
button.setOnAction(event -> {
|
||||||
|
event.consume();
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
entry.refreshIfNeeded();
|
||||||
|
entry.executeDefaultAction();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
new ContextMenuAugment<>(() -> this.createContextMenu())
|
||||||
|
.augment(new SimpleCompStructure<>(button));
|
||||||
|
|
||||||
|
HBox.setHgrow(button, Priority.ALWAYS);
|
||||||
|
var hbox = new HBox(button, new Spacer(25));
|
||||||
|
|
||||||
|
var loading = new LoadingOverlayComp(Comp.of(() -> hbox), entry.getLoading());
|
||||||
|
var region = loading.createRegion();
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Region createContent();
|
||||||
|
|
||||||
protected Label createInformation() {
|
protected Label createInformation() {
|
||||||
var information = new Label();
|
var information = new Label();
|
||||||
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
|
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
|
||||||
|
@ -100,9 +143,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
LabelComp name = new LabelComp(Bindings.createStringBinding(
|
LabelComp name = new LabelComp(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return entry.getName()
|
return entry.getName()
|
||||||
+ (entry.getInformation().get() != null
|
|
||||||
? " [" + entry.getInformation().get() + "]"
|
|
||||||
: "")
|
|
||||||
+ (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore
|
+ (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore
|
||||||
? " (" + filtered.size() + ")"
|
? " (" + filtered.size() + ")"
|
||||||
: "");
|
: "");
|
||||||
|
@ -130,6 +170,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
entry.getEntry().getProvider().getDisplayName()))
|
entry.getEntry().getProvider().getDisplayName()))
|
||||||
.augment(storeIcon);
|
.augment(storeIcon);
|
||||||
}
|
}
|
||||||
|
storeIcon.setPadding(new Insets(3, 0, 0, 0));
|
||||||
return storeIcon;
|
return storeIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +278,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
contextMenu.getItems().add(refresh);
|
contextMenu.getItems().add(refresh);
|
||||||
|
|
||||||
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
|
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||||
del.disableProperty().bind(entry.getDeletable().not());
|
del.disableProperty().bind(entry.getDeletable().not());
|
||||||
del.setOnAction(event -> entry.delete());
|
del.setOnAction(event -> entry.delete());
|
||||||
contextMenu.getItems().add(del);
|
contextMenu.getItems().add(del);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.comp.storage.store;
|
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.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.MultiContentComp;
|
import io.xpipe.app.comp.base.MultiContentComp;
|
||||||
import io.xpipe.app.core.AppState;
|
import io.xpipe.app.core.AppState;
|
||||||
|
@ -23,9 +22,9 @@ public class StoreEntryListComp extends SimpleComp {
|
||||||
.getFilterString()
|
.getFilterString()
|
||||||
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
||||||
var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> {
|
var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> {
|
||||||
return StoreSection.customSection(e);
|
return StoreSection.customSection(e).styleClass("top");
|
||||||
});
|
});
|
||||||
return content.styleClass("store-list-comp").styleClass(Styles.STRIPED);
|
return content.styleClass("store-list-comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class StoreEntrySectionComp extends Comp<CompStructure<VBox>> {
|
||||||
() -> {
|
() -> {
|
||||||
section.getWrapper().toggleExpanded();
|
section.getWrapper().toggleExpanded();
|
||||||
})
|
})
|
||||||
.apply(struc -> struc.get().setPrefWidth(40))
|
.apply(struc -> struc.get().setPrefWidth(30))
|
||||||
.focusTraversable()
|
.focusTraversable()
|
||||||
.accessibleText("Expand")
|
.accessibleText("Expand")
|
||||||
.disable(BindingsHelper.persist(
|
.disable(BindingsHelper.persist(
|
||||||
|
@ -69,6 +69,7 @@ public class StoreEntrySectionComp extends Comp<CompStructure<VBox>> {
|
||||||
.hide(BindingsHelper.persist(Bindings.or(
|
.hide(BindingsHelper.persist(Bindings.or(
|
||||||
Bindings.not(section.getWrapper().getExpanded()),
|
Bindings.not(section.getWrapper().getExpanded()),
|
||||||
Bindings.size(section.getChildren()).isEqualTo(0))))))
|
Bindings.size(section.getChildren()).isEqualTo(0))))))
|
||||||
|
.styleClass("store-entry-section-comp")
|
||||||
.createStructure();
|
.createStructure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.comp.storage.store;
|
package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
|
import io.xpipe.app.comp.store.GuiDsStoreCreator;
|
||||||
import io.xpipe.app.comp.storage.StorageFilter;
|
import io.xpipe.app.comp.storage.StorageFilter;
|
||||||
import io.xpipe.app.ext.ActionProvider;
|
import io.xpipe.app.ext.ActionProvider;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
|
@ -177,7 +177,12 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
||||||
entry.updateLastUsed();
|
entry.updateLastUsed();
|
||||||
found.createAction(entry.getStore().asNeeded()).execute();
|
found.createAction(entry.getStore().asNeeded()).execute();
|
||||||
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
|
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
|
||||||
DataStorage.get().refreshChildren(entry);
|
var hasChildren = DataStorage.get().refreshChildren(entry);
|
||||||
|
if (!hasChildren) {
|
||||||
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
expanded.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class StoreSection implements StorageFilter.Filterable {
|
||||||
|
|
||||||
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
|
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
|
||||||
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
|
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
|
||||||
? o.wrapper.getEntry().getLastAccess()
|
? o.wrapper.getEntry().getLastModified()
|
||||||
: Instant.EPOCH)
|
: Instant.EPOCH)
|
||||||
.reversed()
|
.reversed()
|
||||||
.thenComparing(
|
.thenComparing(
|
||||||
|
@ -66,7 +66,7 @@ public class StoreSection implements StorageFilter.Filterable {
|
||||||
.getStore()
|
.getStore()
|
||||||
.equals(other.getEntry()
|
.equals(other.getEntry()
|
||||||
.getProvider()
|
.getProvider()
|
||||||
.getLogicalParent(other.getEntry().getStore())));
|
.getDisplayParent(other.getEntry().getStore())));
|
||||||
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
|
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
|
||||||
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
|
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
|
||||||
return new StoreSection(e, ordered);
|
return new StoreSection(e, ordered);
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.comp.storage.store;
|
||||||
|
|
||||||
import io.xpipe.app.comp.storage.StorageFilter;
|
import io.xpipe.app.comp.storage.StorageFilter;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataSourceCollection;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.StorageListener;
|
import io.xpipe.app.storage.StorageListener;
|
||||||
|
@ -68,12 +67,6 @@ public class StoreViewState {
|
||||||
allEntries.removeIf(e -> e.getEntry().equals(entry));
|
allEntries.removeIf(e -> e.getEntry().equals(entry));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCollectionAdd(DataSourceCollection collection) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCollectionRemove(DataSourceCollection collection) {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.source.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
|
@ -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.AppFont;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.source.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.app.comp.source.store;
|
package io.xpipe.app.comp.store;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
|
@ -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.browser.StandaloneFileBrowser;
|
||||||
import io.xpipe.app.comp.base.ButtonComp;
|
import io.xpipe.app.comp.base.ButtonComp;
|
|
@ -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.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
|
@ -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.core.AppI18n;
|
||||||
import io.xpipe.app.ext.DataStoreProvider;
|
import io.xpipe.app.ext.DataStoreProvider;
|
|
@ -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.comp.base.FileDropOverlayComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
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.TabPaneComp;
|
||||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.SimpleValidator;
|
import io.xpipe.app.util.SimpleValidator;
|
||||||
import io.xpipe.app.util.Validatable;
|
import io.xpipe.app.util.Validatable;
|
||||||
import io.xpipe.app.util.Validator;
|
import io.xpipe.app.util.Validator;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.core.impl.FileStore;
|
import io.xpipe.core.impl.FileStore;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
import io.xpipe.core.impl.LocalStore;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
@ -70,7 +70,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var isNamedStore =
|
var isNamedStore =
|
||||||
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent();
|
DataStorage.get().getStoreDisplayName(selected.getValue()).isPresent();
|
||||||
var localStore = new SimpleObjectProperty<>(
|
var localStore = new SimpleObjectProperty<>(
|
||||||
!isNamedStore
|
!isNamedStore
|
||||||
&& selected.getValue() instanceof FileStore fileStore
|
&& selected.getValue() instanceof FileStore fileStore
|
|
@ -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.ErrorOverlayComp;
|
||||||
import io.xpipe.app.comp.base.MultiStepComp;
|
import io.xpipe.app.comp.base.MultiStepComp;
|
|
@ -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.ButtonComp;
|
||||||
import io.xpipe.app.comp.base.ListViewComp;
|
import io.xpipe.app.comp.base.ListViewComp;
|
|
@ -1,7 +1,6 @@
|
||||||
package io.xpipe.app.core.mode;
|
package io.xpipe.app.core.mode;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserModel;
|
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.comp.storage.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.issue.*;
|
import io.xpipe.app.issue.*;
|
||||||
|
@ -55,7 +54,6 @@ public class BaseMode extends OperationMode {
|
||||||
TrackEvent.info("mode", "Background mode shutdown started");
|
TrackEvent.info("mode", "Background mode shutdown started");
|
||||||
BrowserModel.DEFAULT.reset();
|
BrowserModel.DEFAULT.reset();
|
||||||
AppSocketServer.reset();
|
AppSocketServer.reset();
|
||||||
SourceCollectionViewState.reset();
|
|
||||||
StoreViewState.reset();
|
StoreViewState.reset();
|
||||||
DataStorage.reset();
|
DataStorage.reset();
|
||||||
AppPrefs.reset();
|
AppPrefs.reset();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.core.mode;
|
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.comp.storage.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
@ -83,7 +82,6 @@ public abstract class PlatformMode extends OperationMode {
|
||||||
UpdateAvailableAlert.showIfNeeded();
|
UpdateAvailableAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceCollectionViewState.init();
|
|
||||||
StoreViewState.init();
|
StoreViewState.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@ package io.xpipe.app.exchange;
|
||||||
|
|
||||||
import io.xpipe.app.ext.DataSourceProvider;
|
import io.xpipe.app.ext.DataSourceProvider;
|
||||||
import io.xpipe.app.ext.DataSourceProviders;
|
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.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.beacon.BeaconHandler;
|
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.dialog.Dialog;
|
||||||
import io.xpipe.core.impl.NamedStore;
|
import io.xpipe.core.impl.NamedStore;
|
||||||
import io.xpipe.core.source.DataSource;
|
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 io.xpipe.core.store.DataStore;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
@ -68,42 +63,6 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
|
||||||
return store.get();
|
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();
|
String getId();
|
||||||
|
|
||||||
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;
|
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,14 +2,11 @@ package io.xpipe.app.exchange;
|
||||||
|
|
||||||
import io.xpipe.app.ext.DataSourceProvider;
|
import io.xpipe.app.ext.DataSourceProvider;
|
||||||
import io.xpipe.app.ext.DataSourceProviders;
|
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.BeaconHandler;
|
||||||
import io.xpipe.beacon.ClientException;
|
import io.xpipe.beacon.ClientException;
|
||||||
import io.xpipe.beacon.exchange.ReadExchange;
|
import io.xpipe.beacon.exchange.ReadExchange;
|
||||||
import io.xpipe.core.dialog.Dialog;
|
import io.xpipe.core.dialog.Dialog;
|
||||||
import io.xpipe.core.dialog.QueryConverter;
|
import io.xpipe.core.dialog.QueryConverter;
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -54,11 +51,6 @@ public class ReadExchangeImpl extends ReadExchange
|
||||||
var entryName =
|
var entryName =
|
||||||
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
|
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
|
||||||
|
|
||||||
var configRef = DialogExchangeImpl.add(Dialog.chain(typeQ, config), (DataSource<?> s) -> {
|
return Response.builder().build();
|
||||||
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, s);
|
|
||||||
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
|
|
||||||
});
|
|
||||||
|
|
||||||
return Response.builder().config(configRef).build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,7 +33,7 @@ public interface DataStoreProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
default Comp<?> customDisplay(StoreEntryWrapper w) {
|
default Comp<?> customDisplay(StoreEntryWrapper w) {
|
||||||
return new StandardStoreEntryComp(w);
|
return new StandardStoreEntryComp(w, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Comp<?> customContainer(StoreSection section) {
|
default Comp<?> customContainer(StoreSection section) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.ext;
|
||||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.core.process.ProcessControlProvider;
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.core.util.ModuleLayerLoader;
|
import io.xpipe.core.util.ModuleLayerLoader;
|
||||||
|
@ -12,7 +11,8 @@ import io.xpipe.core.util.ProxyFunction;
|
||||||
public class XPipeServiceProviders {
|
public class XPipeServiceProviders {
|
||||||
|
|
||||||
public static void load(ModuleLayer layer) {
|
public static void load(ModuleLayer layer) {
|
||||||
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
|
// TODO
|
||||||
|
var hasDaemon = true;
|
||||||
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
|
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
|
||||||
ErrorEvent.fromThrowable(t).handle();
|
ErrorEvent.fromThrowable(t).handle();
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.CustomComboBoxBuilder;
|
import io.xpipe.app.util.CustomComboBoxBuilder;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.LeafShellStore;
|
import io.xpipe.core.store.LeafShellStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
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 Optional.of(AppI18n.get("none"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return XPipeDaemon.getInstance().getStoreName(store);
|
return DataStorage.get().getStoreDisplayName(store);
|
||||||
})
|
})
|
||||||
.orElse(AppI18n.get("unknown"));
|
.orElse(AppI18n.get("unknown"));
|
||||||
|
|
||||||
|
@ -82,7 +81,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
||||||
return AppI18n.get("none");
|
return AppI18n.get("none");
|
||||||
}
|
}
|
||||||
|
|
||||||
return XPipeDaemon.getInstance().getStoreName(store).orElse("?");
|
return DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.util.CustomComboBoxBuilder;
|
import io.xpipe.app.util.CustomComboBoxBuilder;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.core.impl.FileStore;
|
import io.xpipe.core.impl.FileStore;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
@ -27,7 +26,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
|
||||||
var name = DataStorage.get().getUsableStores().stream()
|
var name = DataStorage.get().getUsableStores().stream()
|
||||||
.filter(e -> e.equals(store))
|
.filter(e -> e.equals(store))
|
||||||
.findAny()
|
.findAny()
|
||||||
.map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?"))
|
.map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
|
||||||
.orElse("?");
|
.orElse("?");
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package io.xpipe.app.storage;
|
|
||||||
|
|
||||||
public interface CollectionListener {
|
|
||||||
|
|
||||||
void onUpdate();
|
|
||||||
|
|
||||||
void onEntryAdd(DataSourceEntry entry);
|
|
||||||
|
|
||||||
void onEntryRemove(DataSourceEntry entry);
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,246 +0,0 @@
|
||||||
package io.xpipe.app.storage;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import io.xpipe.app.ext.DataSourceProvider;
|
|
||||||
import io.xpipe.app.ext.DataSourceProviders;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.source.DataSourceType;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.Value;
|
|
||||||
import lombok.experimental.NonFinal;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public class DataSourceEntry extends StorageElement {
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
State state;
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
String information;
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
JsonNode sourceNode;
|
|
||||||
|
|
||||||
@NonFinal
|
|
||||||
DataSource<?> dataSource;
|
|
||||||
|
|
||||||
private DataSourceEntry(
|
|
||||||
Path directory,
|
|
||||||
UUID uuid,
|
|
||||||
String name,
|
|
||||||
Instant lastUsed,
|
|
||||||
Instant lastModified,
|
|
||||||
boolean dirty,
|
|
||||||
JsonNode sourceNode,
|
|
||||||
String info,
|
|
||||||
State state) {
|
|
||||||
super(directory, uuid, name, lastUsed, lastModified, dirty);
|
|
||||||
this.sourceNode = sourceNode;
|
|
||||||
this.dataSource = DataStorageParser.sourceFromNode(sourceNode);
|
|
||||||
this.information = info;
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DataSourceEntry createExisting(
|
|
||||||
@NonNull Path directory,
|
|
||||||
@NonNull UUID uuid,
|
|
||||||
@NonNull String name,
|
|
||||||
@NonNull Instant lastUsed,
|
|
||||||
@NonNull Instant lastModified,
|
|
||||||
JsonNode sourceNode,
|
|
||||||
String info,
|
|
||||||
@NonNull State state)
|
|
||||||
throws Exception {
|
|
||||||
var entry = new DataSourceEntry(directory, uuid, name, lastUsed, lastModified, false, sourceNode, info, state);
|
|
||||||
entry.refresh(false);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DataSourceEntry createNew(
|
|
||||||
@NonNull UUID uuid, @NonNull String name, @NonNull DataSource<?> dataSource) {
|
|
||||||
var entry = new DataSourceEntry(
|
|
||||||
null,
|
|
||||||
uuid,
|
|
||||||
name,
|
|
||||||
Instant.now(),
|
|
||||||
Instant.now(),
|
|
||||||
true,
|
|
||||||
DataStorageWriter.sourceToNode(dataSource),
|
|
||||||
null,
|
|
||||||
State.INCOMPLETE);
|
|
||||||
entry.simpleRefresh();
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DataSourceEntry fromDirectory(Path dir) throws Exception {
|
|
||||||
ObjectMapper mapper = JacksonMapper.newMapper();
|
|
||||||
|
|
||||||
var entryFile = dir.resolve("entry.json");
|
|
||||||
var sourceFile = dir.resolve("source.json");
|
|
||||||
if (!Files.exists(entryFile) || !Files.exists(sourceFile)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = mapper.readTree(entryFile.toFile());
|
|
||||||
var uuid = UUID.fromString(json.required("uuid").textValue());
|
|
||||||
var name = json.required("name").textValue();
|
|
||||||
var info = json.required("info").textValue();
|
|
||||||
var lastUsed = Instant.parse(json.required("lastUsed").textValue());
|
|
||||||
var lastModified = Instant.parse(json.required("lastModified").textValue());
|
|
||||||
var state = mapper.treeToValue(json.get("state"), State.class);
|
|
||||||
if (state == null) {
|
|
||||||
state = State.INCOMPLETE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source loading is prone to errors.
|
|
||||||
// Therefore, handle invalid source in a special way
|
|
||||||
JsonNode sourceNode = null;
|
|
||||||
try {
|
|
||||||
sourceNode = mapper.readTree(sourceFile.toFile());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
return createExisting(dir, uuid, name, lastUsed, lastModified, sourceNode, info, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeDataToDisk() throws Exception {
|
|
||||||
if (!dirty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectMapper mapper = JacksonMapper.newMapper();
|
|
||||||
ObjectNode obj = JsonNodeFactory.instance.objectNode();
|
|
||||||
obj.put("uuid", uuid.toString());
|
|
||||||
obj.put("info", information);
|
|
||||||
obj.put("name", name);
|
|
||||||
obj.put("lastUsed", lastUsed.toString());
|
|
||||||
obj.put("lastModified", lastModified.toString());
|
|
||||||
|
|
||||||
var entryString = mapper.writeValueAsString(obj);
|
|
||||||
var sourceString = mapper.writeValueAsString(dataSource);
|
|
||||||
|
|
||||||
FileUtils.forceMkdir(directory.toFile());
|
|
||||||
Files.writeString(directory.resolve("entry.json"), entryString);
|
|
||||||
Files.writeString(directory.resolve("source.json"), sourceString);
|
|
||||||
|
|
||||||
this.dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh(boolean deep) throws Exception {
|
|
||||||
var oldSource = dataSource;
|
|
||||||
DataSource<?> newSource = DataStorageParser.sourceFromNode(sourceNode);
|
|
||||||
|
|
||||||
// TrackEvent.storage()
|
|
||||||
// .trace()
|
|
||||||
// .message("Refreshing data source entry")
|
|
||||||
// .tag("old", oldSource)
|
|
||||||
// .tag("new", newSource)
|
|
||||||
// .handle();
|
|
||||||
|
|
||||||
if (newSource == null) {
|
|
||||||
dataSource = null;
|
|
||||||
state = State.LOAD_FAILED;
|
|
||||||
information = null;
|
|
||||||
dirty = oldSource != null;
|
|
||||||
propagateUpdate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataSource = newSource;
|
|
||||||
dirty = true;
|
|
||||||
|
|
||||||
if (state == State.LOAD_FAILED || state == State.INCOMPLETE) {
|
|
||||||
state = newSource.isComplete() ? State.COMPLETE_BUT_INVALID : State.INCOMPLETE;
|
|
||||||
propagateUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!deep || !dataSource.isComplete()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
state = State.VALIDATING;
|
|
||||||
propagateUpdate();
|
|
||||||
|
|
||||||
dataSource.validate();
|
|
||||||
|
|
||||||
state = State.COMPLETE_AND_VALID;
|
|
||||||
information = getProvider().queryInformationString(getStore(), 50);
|
|
||||||
propagateUpdate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
state = dataSource.isComplete() ? State.COMPLETE_BUT_INVALID : State.INCOMPLETE;
|
|
||||||
information = null;
|
|
||||||
propagateUpdate();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldSave() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataSourceProvider<?> getProvider() {
|
|
||||||
if (state == State.LOAD_FAILED) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DataSourceProviders.byDataSourceClass(dataSource.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataSourceType getDataSourceType() {
|
|
||||||
return getProvider() != null ? getProvider().getPrimaryType() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataStore getStore() {
|
|
||||||
return dataSource.getStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataSource<?> getSource() {
|
|
||||||
return dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSource(DataSource<?> dataSource) {
|
|
||||||
this.dirty = true;
|
|
||||||
this.dataSource = dataSource;
|
|
||||||
this.sourceNode = DataStorageWriter.sourceToNode(dataSource);
|
|
||||||
propagateUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public enum State {
|
|
||||||
@JsonProperty("loadFailed")
|
|
||||||
LOAD_FAILED(false),
|
|
||||||
@JsonProperty("incomplete")
|
|
||||||
INCOMPLETE(false),
|
|
||||||
@JsonProperty("completeButInvalid")
|
|
||||||
COMPLETE_BUT_INVALID(true),
|
|
||||||
@JsonProperty("validating")
|
|
||||||
VALIDATING(true),
|
|
||||||
@JsonProperty("completeAndValid")
|
|
||||||
COMPLETE_AND_VALID(true);
|
|
||||||
|
|
||||||
private final boolean isUsable;
|
|
||||||
|
|
||||||
State(boolean isUsable) {
|
|
||||||
this.isUsable = isUsable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,9 @@
|
||||||
package io.xpipe.app.storage;
|
package io.xpipe.app.storage;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCharsets;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.charsetter.Charsettable;
|
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.source.DataSourceId;
|
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.FixedHierarchyStore;
|
import io.xpipe.core.store.FixedHierarchyStore;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -26,19 +19,13 @@ public abstract class DataStorage {
|
||||||
|
|
||||||
private static DataStorage INSTANCE;
|
private static DataStorage INSTANCE;
|
||||||
protected final Path dir;
|
protected final Path dir;
|
||||||
protected final List<DataSourceEntry> sourceEntries;
|
|
||||||
protected final List<DataSourceCollection> sourceCollections;
|
|
||||||
protected final List<DataStoreEntry> storeEntries;
|
protected final List<DataStoreEntry> storeEntries;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final List<StorageListener> listeners = new ArrayList<>();
|
private final List<StorageListener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
private DataSourceEntry latest;
|
|
||||||
|
|
||||||
public DataStorage() {
|
public DataStorage() {
|
||||||
this.dir = AppPrefs.get().storageDirectory().getValue();
|
this.dir = AppPrefs.get().storageDirectory().getValue();
|
||||||
this.sourceEntries = new ArrayList<>();
|
|
||||||
this.sourceCollections = new ArrayList<>();
|
|
||||||
this.storeEntries = new ArrayList<>();
|
this.storeEntries = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,30 +67,13 @@ public abstract class DataStorage {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized DataSourceCollection getInternalCollection() {
|
public synchronized boolean refreshChildren(DataStoreEntry e) {
|
||||||
var found = sourceCollections.stream()
|
return refreshChildren(e, List.of());
|
||||||
.filter(o -> o.getName() != null && o.getName().equals("Internal"))
|
|
||||||
.findAny();
|
|
||||||
if (found.isPresent()) {
|
|
||||||
return found.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
var internalCollection = DataSourceCollection.createNew("Internal");
|
|
||||||
internalCollection.setDirectory(getSourcesDir()
|
|
||||||
.resolve("collections")
|
|
||||||
.resolve(internalCollection.getUuid().toString()));
|
|
||||||
this.sourceCollections.add(internalCollection);
|
|
||||||
return internalCollection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void refreshChildren(DataStoreEntry e) {
|
public synchronized boolean refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
|
||||||
refreshChildren(e, List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
|
|
||||||
if (!(e.getStore() instanceof FixedHierarchyStore)) {
|
if (!(e.getStore() instanceof FixedHierarchyStore)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -115,13 +85,15 @@ public abstract class DataStorage {
|
||||||
newChildren.entrySet().stream().filter(entry -> oldChildren.stream().noneMatch(old -> old.getStore().equals(entry.getValue()))).forEach(entry -> {
|
newChildren.entrySet().stream().filter(entry -> oldChildren.stream().noneMatch(old -> old.getStore().equals(entry.getValue()))).forEach(entry -> {
|
||||||
addStoreEntryIfNotPresent(entry.getKey(), entry.getValue());
|
addStoreEntryIfNotPresent(entry.getKey(), entry.getValue());
|
||||||
});
|
});
|
||||||
|
return newChildren.size() > 0;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void deleteChildren(DataStoreEntry e, boolean deep) {
|
public synchronized void deleteChildren(DataStoreEntry e, boolean deep) {
|
||||||
getStoreChildren(e, deep).forEach(entry -> {
|
getLogicalStoreChildren(e, deep).forEach(entry -> {
|
||||||
if (!entry.getConfiguration().isDeletable()) {
|
if (!entry.getConfiguration().isDeletable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +102,7 @@ public abstract class DataStorage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean deep) {
|
public synchronized List<DataStoreEntry> getLogicalStoreChildren(DataStoreEntry entry, boolean deep) {
|
||||||
var children = new ArrayList<>(getStoreEntries().stream()
|
var children = new ArrayList<>(getStoreEntries().stream()
|
||||||
.filter(other -> {
|
.filter(other -> {
|
||||||
if (!other.getState().isUsable()) {
|
if (!other.getState().isUsable()) {
|
||||||
|
@ -144,75 +116,13 @@ public abstract class DataStorage {
|
||||||
|
|
||||||
if (deep) {
|
if (deep) {
|
||||||
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
|
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
|
||||||
children.addAll(getStoreChildren(dataStoreEntry, true));
|
children.addAll(getLogicalStoreChildren(dataStoreEntry, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String createUniqueSourceEntryName(DataSourceCollection col, DataSource<?> source) {
|
|
||||||
var def = source.determineDefaultName();
|
|
||||||
if (def.isPresent()) {
|
|
||||||
return createUniqueSourceEntryName(col, def.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
var storeDef = source.getStore().determineDefaultName();
|
|
||||||
if (storeDef.isPresent()) {
|
|
||||||
return createUniqueSourceEntryName(col, storeDef.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeName =
|
|
||||||
switch (source.getType()) {
|
|
||||||
case TABLE -> AppI18n.get("table");
|
|
||||||
case STRUCTURE -> AppI18n.get("structure");
|
|
||||||
case TEXT -> AppI18n.get("text");
|
|
||||||
case RAW -> AppI18n.get("raw");
|
|
||||||
case COLLECTION -> AppI18n.get("collection");
|
|
||||||
};
|
|
||||||
return createUniqueSourceEntryName(col, typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized String createUniqueStoreEntryName(String base) {
|
|
||||||
if (DataStorage.get().getStoreEntryIfPresent(base).isEmpty()) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
int counter = 1;
|
|
||||||
while (true) {
|
|
||||||
var name = base + counter;
|
|
||||||
if (DataStorage.get().getStoreEntryIfPresent(name).isEmpty()) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized String createUniqueSourceEntryName(DataSourceCollection col, String base) {
|
|
||||||
base = DataSourceId.cleanString(base);
|
|
||||||
var id = DataSourceId.create(col != null ? col.getName() : null, base);
|
|
||||||
if (DataStorage.get().getDataSource(DataSourceReference.id(id)).isEmpty()) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
int counter = 1;
|
|
||||||
while (true) {
|
|
||||||
id = DataSourceId.create(col != null ? col.getName() : null, base + counter);
|
|
||||||
if (DataStorage.get().getDataSource(DataSourceReference.id(id)).isEmpty()) {
|
|
||||||
return base + counter;
|
|
||||||
}
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DataSourceEntry getLatest() {
|
|
||||||
return latest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setLatest(DataSourceEntry latest) {
|
|
||||||
this.latest = latest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Path getInternalStreamPath(@NonNull UUID uuid);
|
public abstract Path getInternalStreamPath(@NonNull UUID uuid);
|
||||||
|
|
||||||
private void checkImmutable() {
|
private void checkImmutable() {
|
||||||
|
@ -235,26 +145,6 @@ public abstract class DataStorage {
|
||||||
return dir.resolve("streams");
|
return dir.resolve("streams");
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Optional<DataSourceCollection> getCollectionForSourceEntry(DataSourceEntry entry) {
|
|
||||||
if (entry == null) {
|
|
||||||
return Optional.of(getInternalCollection());
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourceCollections.stream()
|
|
||||||
.filter(c -> c.getEntries().contains(entry))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized Optional<DataSourceCollection> getCollectionForName(String name) {
|
|
||||||
if (name == null) {
|
|
||||||
return Optional.ofNullable(getInternalCollection());
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourceCollections.stream()
|
|
||||||
.filter(c -> name.equalsIgnoreCase(c.getName()))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized List<DataStore> getUsableStores() {
|
public synchronized List<DataStore> getUsableStores() {
|
||||||
return new ArrayList<>(getStoreEntries().stream()
|
return new ArrayList<>(getStoreEntries().stream()
|
||||||
.filter(entry -> !entry.isDisabled())
|
.filter(entry -> !entry.isDisabled())
|
||||||
|
@ -301,107 +191,18 @@ public abstract class DataStorage {
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Optional<DataSourceEntry> getDataSource(DataSourceReference ref) {
|
public void updateAndRefreshAsync(DataStoreEntry entry, DataStore s) {
|
||||||
Objects.requireNonNull(ref, "ref");
|
|
||||||
|
|
||||||
switch (ref.getType()) {
|
|
||||||
case LATEST -> {
|
|
||||||
return Optional.ofNullable(latest);
|
|
||||||
}
|
|
||||||
case NAME -> {
|
|
||||||
var found = sourceCollections.stream()
|
|
||||||
.map(col -> col.getEntries().stream()
|
|
||||||
.filter(e -> e.getName().equalsIgnoreCase(ref.getName()))
|
|
||||||
.findAny())
|
|
||||||
.flatMap(Optional::stream)
|
|
||||||
.toList();
|
|
||||||
return Optional.ofNullable(found.size() == 1 ? found.get(0) : null);
|
|
||||||
}
|
|
||||||
case ID -> {
|
|
||||||
var id = ref.getId();
|
|
||||||
var col = id.getCollectionName() != null
|
|
||||||
? sourceCollections.stream()
|
|
||||||
.filter(c ->
|
|
||||||
c.getName() != null && c.getName().equalsIgnoreCase(id.getCollectionName()))
|
|
||||||
.findAny()
|
|
||||||
: Optional.of(getInternalCollection());
|
|
||||||
if (col.isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return col.get().getEntries().stream()
|
|
||||||
.filter(e -> e.getName().equalsIgnoreCase(id.getEntryName()))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DataSourceId getId(DataSourceEntry entry) {
|
|
||||||
Objects.requireNonNull(entry, "entry");
|
|
||||||
if (!sourceEntries.contains(entry)) {
|
|
||||||
throw new IllegalArgumentException("Entry not in storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
var col = sourceCollections.stream()
|
|
||||||
.filter(p -> p.getEntries().contains(entry))
|
|
||||||
.findAny()
|
|
||||||
.map(DataSourceCollection::getName)
|
|
||||||
.orElse(null);
|
|
||||||
var en = entry.getName();
|
|
||||||
return DataSourceId.create(col, en);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void add(DataSourceEntry e, DataSourceCollection c) {
|
|
||||||
Objects.requireNonNull(e, "entry");
|
|
||||||
|
|
||||||
if (c != null && !sourceCollections.contains(c)) {
|
|
||||||
throw new IllegalArgumentException("Collection does not belong to the storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == null) {
|
|
||||||
c = getInternalCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = DataSourceId.create(c.getName(), e.getName());
|
|
||||||
if (getDataSource(DataSourceReference.id(id)).isPresent()) {
|
|
||||||
throw new IllegalArgumentException("Entry with id " + id + " is already in storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
checkImmutable();
|
|
||||||
|
|
||||||
// Observe charset of all sources
|
|
||||||
if (e.getSource() instanceof Charsettable cs) {
|
|
||||||
AppCharsets.observe(cs.getCharset());
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackEvent.withTrace("storage", "Adding new data source entry")
|
|
||||||
.tag("name", e.getName())
|
|
||||||
.tag("uuid", e.getUuid())
|
|
||||||
.tag("collection", c)
|
|
||||||
.handle();
|
|
||||||
|
|
||||||
e.setDirectory(getSourcesDir().resolve("entries").resolve(e.getUuid().toString()));
|
|
||||||
this.sourceEntries.add(e);
|
|
||||||
c.addEntry(e);
|
|
||||||
save();
|
|
||||||
|
|
||||||
latest = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAndRefreshAsync(DataStoreEntry entry, DataStore s) {
|
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
var old = entry.getStore();
|
var old = entry.getStore();
|
||||||
var children = getStoreChildren(entry, false);
|
var children = getLogicalStoreChildren(entry, false);
|
||||||
try {
|
try {
|
||||||
entry.setStoreInternal(s);
|
entry.setStoreInternal(s, false);
|
||||||
entry.refresh(true);
|
entry.refresh(true);
|
||||||
// Update old children
|
// Update old children
|
||||||
children.forEach(entry1 -> propagateUpdate(entry1));
|
children.forEach(entry1 -> propagateUpdate(entry1));
|
||||||
DataStorage.get().refreshChildren(entry, children);
|
DataStorage.get().refreshChildren(entry, children);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
entry.setStoreInternal(old);
|
entry.setStoreInternal(old, false);
|
||||||
entry.simpleRefresh();
|
entry.simpleRefresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -420,17 +221,13 @@ public abstract class DataStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
void propagateUpdate(DataStoreEntry origin) {
|
void propagateUpdate(DataStoreEntry origin) {
|
||||||
getStoreChildren(origin, false).forEach(entry -> {
|
getLogicalStoreChildren(origin, false).forEach(entry -> {
|
||||||
entry.simpleRefresh();
|
entry.simpleRefresh();
|
||||||
propagateUpdate(entry);
|
propagateUpdate(entry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStoreEntry(@NonNull DataStoreEntry e) {
|
public void addStoreEntry(@NonNull DataStoreEntry e) {
|
||||||
if (getStoreEntryIfPresent(e.getName()).isPresent()) {
|
|
||||||
throw new IllegalArgumentException("Store with name " + e.getName() + " already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
|
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
|
||||||
this.storeEntries.add(e);
|
this.storeEntries.add(e);
|
||||||
|
@ -447,22 +244,29 @@ public abstract class DataStorage {
|
||||||
return found.get();
|
return found.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
|
var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
|
||||||
addStoreEntry(e);
|
addStoreEntry(e);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) {
|
public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) {
|
||||||
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
|
var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
|
||||||
addStoreEntry(e);
|
addStoreEntry(e);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
|
public Optional<String> getStoreDisplayName(DataStore store) {
|
||||||
if (!store.getConfiguration().isDeletable()) {
|
if (store == null) {
|
||||||
// throw new UnsupportedOperationException();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return DataStorage.get().getStoreEntries().stream()
|
||||||
|
.filter(entry -> !entry.isDisabled() && entry.getStore().equals(store))
|
||||||
|
.findFirst()
|
||||||
|
.map(entry -> entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.storeEntries.remove(store);
|
this.storeEntries.remove(store);
|
||||||
}
|
}
|
||||||
|
@ -475,43 +279,6 @@ public abstract class DataStorage {
|
||||||
this.listeners.add(l);
|
this.listeners.add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataSourceCollection createOrGetCollection(String name) {
|
|
||||||
return getCollectionForName(name).orElseGet(() -> {
|
|
||||||
var col = DataSourceCollection.createNew(name);
|
|
||||||
addCollection(col);
|
|
||||||
return col;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void addCollection(DataSourceCollection c) {
|
|
||||||
checkImmutable();
|
|
||||||
|
|
||||||
c.setDirectory(
|
|
||||||
getSourcesDir().resolve("collections").resolve(c.getUuid().toString()));
|
|
||||||
this.sourceCollections.add(c);
|
|
||||||
this.listeners.forEach(l -> l.onCollectionAdd(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void deleteCollection(DataSourceCollection c) {
|
|
||||||
checkImmutable();
|
|
||||||
|
|
||||||
this.sourceCollections.remove(c);
|
|
||||||
this.listeners.forEach(l -> l.onCollectionRemove(c));
|
|
||||||
|
|
||||||
c.getEntries().forEach(this::deleteSourceEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void deleteSourceEntry(DataSourceEntry e) {
|
|
||||||
checkImmutable();
|
|
||||||
|
|
||||||
this.sourceEntries.remove(e);
|
|
||||||
|
|
||||||
var col = sourceCollections.stream()
|
|
||||||
.filter(p -> p.getEntries().contains(e))
|
|
||||||
.findAny();
|
|
||||||
col.ifPresent(dataSourceCollection -> dataSourceCollection.removeEntry(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void load();
|
public abstract void load();
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
|
@ -527,28 +294,6 @@ public abstract class DataStorage {
|
||||||
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
|
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Optional<DataSourceEntry> getSourceEntry(UUID id) {
|
|
||||||
return sourceEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized Optional<DataSourceEntry> getSourceEntry(DataSource<?> source) {
|
|
||||||
return sourceEntries.stream()
|
|
||||||
.filter(e -> e.getSource() != null && e.getSource().equals(source))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized Optional<DataSourceCollection> getCollection(UUID id) {
|
|
||||||
return sourceCollections.stream().filter(e -> e.getUuid().equals(id)).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized List<DataSourceEntry> getSourceEntries() {
|
|
||||||
return new ArrayList<>(sourceEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized List<DataSourceCollection> getSourceCollections() {
|
|
||||||
return new ArrayList<>(sourceCollections);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized List<DataStoreEntry> getStoreEntries() {
|
public synchronized List<DataStoreEntry> getStoreEntries() {
|
||||||
return new ArrayList<>(storeEntries);
|
return new ArrayList<>(storeEntries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,15 +69,6 @@ public class DataStorageParser {
|
||||||
return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds));
|
return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds));
|
||||||
});
|
});
|
||||||
|
|
||||||
node = replaceReferenceIdsForType(node, "sourceId", id -> {
|
|
||||||
var foundEntry = DataStorage.get().getSourceEntry(id);
|
|
||||||
if (foundEntry.isPresent()) {
|
|
||||||
var sourceNode = mapper.valueToTree(foundEntry.get().getSource());
|
|
||||||
// return Optional.of(resolvesReferenceIds(sourceNode));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,22 +53,6 @@ public class DataStorageWriter {
|
||||||
"storeId",
|
"storeId",
|
||||||
isRoot);
|
isRoot);
|
||||||
|
|
||||||
node = replaceReferencesWithIds(
|
|
||||||
node,
|
|
||||||
possibleReference -> {
|
|
||||||
try {
|
|
||||||
var source = mapper.treeToValue(possibleReference, DataSource.class);
|
|
||||||
if (!isRoot) {
|
|
||||||
var found = DataStorage.get().getSourceEntry(source);
|
|
||||||
return found.map(dataSourceEntry -> dataSourceEntry.getUuid());
|
|
||||||
}
|
|
||||||
} catch (JsonProcessingException ignored) {
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
},
|
|
||||||
"sourceId",
|
|
||||||
isRoot);
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
true,
|
true,
|
||||||
State.LOAD_FAILED,
|
State.LOAD_FAILED,
|
||||||
Configuration.defaultConfiguration(),
|
Configuration.defaultConfiguration(),
|
||||||
true);
|
false);
|
||||||
entry.refresh(false);
|
entry.refresh(false);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -203,10 +203,12 @@ public class DataStoreEntry extends StorageElement {
|
||||||
simpleRefresh();
|
simpleRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStoreInternal(DataStore store) {
|
void setStoreInternal(DataStore store, boolean updateTime) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.storeNode = DataStorageWriter.storeToNode(store);
|
this.storeNode = DataStorageWriter.storeToNode(store);
|
||||||
lastModified = Instant.now();
|
if (updateTime) {
|
||||||
|
lastModified = Instant.now();
|
||||||
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@ public class ImpersistentStorage extends DataStorage {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
// Add temporary collection it is not added yet
|
|
||||||
getInternalCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.storage;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.XPipeSession;
|
import io.xpipe.app.util.XPipeSession;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
@ -17,7 +18,6 @@ import java.util.stream.Stream;
|
||||||
public class StandardStorage extends DataStorage {
|
public class StandardStorage extends DataStorage {
|
||||||
|
|
||||||
private final List<Path> directoriesToKeep = new ArrayList<>();
|
private final List<Path> directoriesToKeep = new ArrayList<>();
|
||||||
private DataSourceCollection recovery;
|
|
||||||
|
|
||||||
private boolean isNewSession() {
|
private boolean isNewSession() {
|
||||||
return XPipeSession.get().isNewSystemSession();
|
return XPipeSession.get().isNewSystemSession();
|
||||||
|
@ -59,70 +59,6 @@ public class StandardStorage extends DataStorage {
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete leftover directories in entries dir
|
|
||||||
try (var s = Files.list(entriesDir)) {
|
|
||||||
s.forEach(file -> {
|
|
||||||
if (directoriesToKeep.contains(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = file.getFileName().toString();
|
|
||||||
try {
|
|
||||||
UUID uuid;
|
|
||||||
try {
|
|
||||||
uuid = UUID.fromString(name);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
FileUtils.forceDelete(file.toFile());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = getSourceEntry(uuid);
|
|
||||||
if (entry.isEmpty()) {
|
|
||||||
TrackEvent.withTrace("storage", "Deleting leftover entry directory")
|
|
||||||
.tag("uuid", uuid)
|
|
||||||
.handle();
|
|
||||||
FileUtils.forceDelete(file.toFile());
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete leftover directories in collections dir
|
|
||||||
try (var s = Files.list(collectionsDir)) {
|
|
||||||
s.forEach(file -> {
|
|
||||||
if (directoriesToKeep.contains(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = file.getFileName().toString();
|
|
||||||
try {
|
|
||||||
UUID uuid;
|
|
||||||
try {
|
|
||||||
uuid = UUID.fromString(name);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
FileUtils.forceDelete(file.toFile());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var col = getCollection(uuid);
|
|
||||||
if (col.isEmpty()) {
|
|
||||||
TrackEvent.withTrace("storage", "Deleting leftover collection directory")
|
|
||||||
.tag("uuid", uuid)
|
|
||||||
.handle();
|
|
||||||
FileUtils.forceDelete(file.toFile());
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void load() {
|
public synchronized void load() {
|
||||||
|
@ -158,75 +94,23 @@ public class StandardStorage extends DataStorage {
|
||||||
|
|
||||||
storeEntries.add(entry);
|
storeEntries.add(entry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
directoriesToKeep.add(path);
|
// We only keep invalid entries in developer mode as there's no point in keeping them in
|
||||||
|
// production.
|
||||||
|
if (AppPrefs.get().developerMode().getValue()) {
|
||||||
|
directoriesToKeep.add(path);
|
||||||
|
}
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Refresh to update state
|
||||||
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh());
|
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh());
|
||||||
}
|
|
||||||
|
|
||||||
try (var dirs = Files.list(entriesDir)) {
|
// Remove even incomplete stores when in production
|
||||||
dirs.filter(Files::isDirectory).forEach(path -> {
|
if (!AppPrefs.get().developerMode().getValue()) {
|
||||||
try {
|
storeEntries.removeIf(entry -> {
|
||||||
try (Stream<Path> list = Files.list(path)) {
|
return !entry.getState().isUsable();
|
||||||
if (list.findAny().isEmpty()) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = DataSourceEntry.fromDirectory(path);
|
|
||||||
if (entry == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceEntries.add(entry);
|
|
||||||
} catch (Exception e) {
|
|
||||||
directoriesToKeep.add(path);
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var dirs = Files.list(collectionsDir)) {
|
|
||||||
dirs.filter(Files::isDirectory).forEach(path -> {
|
|
||||||
try {
|
|
||||||
try (Stream<Path> list = Files.list(path)) {
|
|
||||||
if (list.findAny().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var col = DataSourceCollection.fromDirectory(this, path);
|
|
||||||
|
|
||||||
// Empty temp on new session
|
|
||||||
if (col.getName() == null && newSession) {
|
|
||||||
for (var e : col.getEntries()) {
|
|
||||||
var file = e.getDirectory();
|
|
||||||
TrackEvent.withTrace("storage", "Deleting temporary entry directory")
|
|
||||||
.tag("uuid", e.getUuid())
|
|
||||||
.handle();
|
|
||||||
FileUtils.forceDelete(file.toFile());
|
|
||||||
}
|
|
||||||
col.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceCollections.add(col);
|
|
||||||
} catch (Exception e) {
|
|
||||||
directoriesToKeep.add(path);
|
|
||||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add temporary collection it is not added yet
|
|
||||||
getInternalCollection();
|
|
||||||
|
|
||||||
for (var e : getSourceEntries()) {
|
|
||||||
var inCol = getSourceCollections().stream()
|
|
||||||
.anyMatch(col -> col.getEntries().contains(e));
|
|
||||||
if (!inCol) {
|
|
||||||
recovery = createOrGetCollection("Recovery");
|
|
||||||
recovery.addEntry(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -257,31 +141,6 @@ public class StandardStorage extends DataStorage {
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save entries
|
|
||||||
sourceEntries.stream()
|
|
||||||
.filter(dataStoreEntry -> dataStoreEntry.shouldSave())
|
|
||||||
.forEach(e -> {
|
|
||||||
try {
|
|
||||||
e.writeDataToDisk();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save collections
|
|
||||||
for (var c : sourceCollections) {
|
|
||||||
if (c.equals(recovery)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
c.writeDataToDisk();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteLeftovers();
|
deleteLeftovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,4 @@ public interface StorageListener {
|
||||||
void onStoreAdd(DataStoreEntry entry);
|
void onStoreAdd(DataStoreEntry entry);
|
||||||
|
|
||||||
void onStoreRemove(DataStoreEntry entry);
|
void onStoreRemove(DataStoreEntry entry);
|
||||||
|
|
||||||
void onCollectionAdd(DataSourceCollection collection);
|
|
||||||
|
|
||||||
void onCollectionRemove(DataSourceCollection collection);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
import io.xpipe.app.ext.DataStoreProviders;
|
import io.xpipe.app.ext.DataStoreProviders;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ public class DataStoreFormatter {
|
||||||
|
|
||||||
public static String formatSubHost(IntFunction<String> func, DataStore at, int length) {
|
public static String formatSubHost(IntFunction<String> func, DataStore at, int length) {
|
||||||
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
||||||
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
|
? DataStorage.get().getStoreDisplayName(at).orElse(null)
|
||||||
: null;
|
: null;
|
||||||
if (atString == null) {
|
if (atString == null) {
|
||||||
return func.apply(length);
|
return func.apply(length);
|
||||||
|
@ -22,7 +23,7 @@ public class DataStoreFormatter {
|
||||||
|
|
||||||
public static String formatAtHost(IntFunction<String> func, DataStore at, int length) {
|
public static String formatAtHost(IntFunction<String> func, DataStore at, int length) {
|
||||||
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
||||||
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
|
? DataStorage.get().getStoreDisplayName(at).orElse(null)
|
||||||
: null;
|
: null;
|
||||||
if (atString == null) {
|
if (atString == null) {
|
||||||
return func.apply(length);
|
return func.apply(length);
|
||||||
|
@ -34,7 +35,7 @@ public class DataStoreFormatter {
|
||||||
|
|
||||||
public static String formatViaProxy(IntFunction<String> func, DataStore at, int length) {
|
public static String formatViaProxy(IntFunction<String> func, DataStore at, int length) {
|
||||||
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
|
||||||
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
|
? DataStorage.get().getStoreDisplayName(at).orElse(null)
|
||||||
: null;
|
: null;
|
||||||
if (atString == null) {
|
if (atString == null) {
|
||||||
return func.apply(length);
|
return func.apply(length);
|
||||||
|
@ -53,7 +54,7 @@ public class DataStoreFormatter {
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
var named = XPipeDaemon.getInstance().getStoreName(input);
|
var named = DataStorage.get().getStoreDisplayName(input);
|
||||||
if (named.isPresent()) {
|
if (named.isPresent()) {
|
||||||
return cut(named.get(), length);
|
return cut(named.get(), length);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.core.charsetter.StreamCharset;
|
||||||
import io.xpipe.core.dialog.Dialog;
|
import io.xpipe.core.dialog.Dialog;
|
||||||
import io.xpipe.core.dialog.QueryConverter;
|
import io.xpipe.core.dialog.QueryConverter;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
import io.xpipe.core.impl.LocalStore;
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.store.DataFlow;
|
import io.xpipe.core.store.DataFlow;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
@ -14,8 +13,6 @@ import io.xpipe.core.store.ShellStore;
|
||||||
import io.xpipe.core.util.SecretValue;
|
import io.xpipe.core.util.SecretValue;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class DialogHelper {
|
public class DialogHelper {
|
||||||
|
|
||||||
public static Dialog addressQuery(Address address) {
|
public static Dialog addressQuery(Address address) {
|
||||||
|
@ -26,7 +23,7 @@ public class DialogHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dialog machineQuery(DataStore store) {
|
public static Dialog machineQuery(DataStore store) {
|
||||||
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("localhost");
|
var storeName = DataStorage.get().getStoreDisplayName(store).orElse("localhost");
|
||||||
return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING)
|
return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING)
|
||||||
.map((String name) -> {
|
.map((String name) -> {
|
||||||
if (name.equals("local") || name.equals("localhost")) {
|
if (name.equals("local") || name.equals("localhost")) {
|
||||||
|
@ -51,7 +48,7 @@ public class DialogHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dialog shellQuery(String displayName, DataStore store) {
|
public static Dialog shellQuery(String displayName, DataStore store) {
|
||||||
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("localhost");
|
var storeName = DataStorage.get().getStoreDisplayName(store).orElse("localhost");
|
||||||
return Dialog.query(displayName, false, true, false, storeName, QueryConverter.STRING)
|
return Dialog.query(displayName, false, true, false, storeName, QueryConverter.STRING)
|
||||||
.map((String name) -> {
|
.map((String name) -> {
|
||||||
if (name.equals("local") || name.equals("localhost")) {
|
if (name.equals("local") || name.equals("localhost")) {
|
||||||
|
@ -96,7 +93,7 @@ public class DialogHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dialog namedStoreQuery(DataStore store, Class<? extends DataStore> filter) {
|
public static Dialog namedStoreQuery(DataStore store, Class<? extends DataStore> filter) {
|
||||||
var name = XPipeDaemon.getInstance().getStoreName(store).orElse(null);
|
var name = DataStorage.get().getStoreDisplayName(store).orElse(null);
|
||||||
return Dialog.query("Store", false, true, false, name, QueryConverter.STRING)
|
return Dialog.query("Store", false, true, false, name, QueryConverter.STRING)
|
||||||
.map((String newName) -> {
|
.map((String newName) -> {
|
||||||
var found = DataStorage.get()
|
var found = DataStorage.get()
|
||||||
|
@ -110,18 +107,6 @@ public class DialogHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dialog sourceQuery(DataSource<?> source, Predicate<DataSource<?>> filter) {
|
|
||||||
var id = XPipeDaemon.getInstance().getSourceId(source).orElse(null);
|
|
||||||
return Dialog.query("Source Id", false, true, false, id, QueryConverter.STRING)
|
|
||||||
.map((String newName) -> {
|
|
||||||
var found = XPipeDaemon.getInstance().getSource(newName).orElseThrow();
|
|
||||||
if (!filter.test(found)) {
|
|
||||||
throw new IllegalArgumentException("Incompatible store type");
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dialog passwordQuery(SecretValue password) {
|
public static Dialog passwordQuery(SecretValue password) {
|
||||||
return Dialog.querySecret("Password", false, true, password);
|
return Dialog.querySecret("Password", false, true, password);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package io.xpipe.app.util;
|
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
|
|
||||||
public interface XPipeDaemon {
|
|
||||||
|
|
||||||
static XPipeDaemon getInstance() {
|
|
||||||
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Optional<XPipeDaemon> getInstanceIfPresent() {
|
|
||||||
return ServiceLoader.load(XPipeDaemon.class).findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<DataSource<?>> getSource(String id);
|
|
||||||
|
|
||||||
Optional<String> getStoreName(DataStore store);
|
|
||||||
|
|
||||||
Optional<String> getSourceId(DataSource<?> source);
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package io.xpipe.app.util;
|
|
||||||
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.source.DataSourceId;
|
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class XPipeDaemonProvider implements XPipeDaemon {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<DataSource<?>> getSource(String id) {
|
|
||||||
var sourceId = DataSourceId.fromString(id);
|
|
||||||
return DataStorage.get()
|
|
||||||
.getDataSource(DataSourceReference.id(sourceId))
|
|
||||||
.map(dataSourceEntry -> dataSourceEntry.getSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<String> getStoreName(DataStore store) {
|
|
||||||
if (store == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return DataStorage.get().getStoreEntries().stream()
|
|
||||||
.filter(entry -> !entry.isDisabled() && entry.getStore().equals(store))
|
|
||||||
.findFirst()
|
|
||||||
.map(entry -> entry.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<String> getSourceId(DataSource<?> source) {
|
|
||||||
var entry = DataStorage.get().getSourceEntry(source);
|
|
||||||
return entry.map(
|
|
||||||
dataSourceEntry -> DataStorage.get().getId(dataSourceEntry).toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
import io.xpipe.app.browser.action.BrowserAction;
|
import io.xpipe.app.browser.action.BrowserAction;
|
||||||
import io.xpipe.app.core.AppLogs;
|
import io.xpipe.app.core.AppLogs;
|
||||||
import io.xpipe.app.exchange.*;
|
import io.xpipe.app.exchange.*;
|
||||||
import io.xpipe.app.exchange.api.*;
|
|
||||||
import io.xpipe.app.exchange.cli.*;
|
import io.xpipe.app.exchange.cli.*;
|
||||||
import io.xpipe.app.ext.*;
|
import io.xpipe.app.ext.*;
|
||||||
import io.xpipe.app.issue.EventHandler;
|
import io.xpipe.app.issue.EventHandler;
|
||||||
|
@ -9,8 +8,6 @@ import io.xpipe.app.issue.EventHandlerImpl;
|
||||||
import io.xpipe.app.storage.DataStateProviderImpl;
|
import io.xpipe.app.storage.DataStateProviderImpl;
|
||||||
import io.xpipe.app.util.ProxyManagerProviderImpl;
|
import io.xpipe.app.util.ProxyManagerProviderImpl;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalHelper;
|
||||||
import io.xpipe.app.util.XPipeDaemon;
|
|
||||||
import io.xpipe.app.util.XPipeDaemonProvider;
|
|
||||||
import io.xpipe.core.util.DataStateProvider;
|
import io.xpipe.core.util.DataStateProvider;
|
||||||
import io.xpipe.core.util.ModuleLayerLoader;
|
import io.xpipe.core.util.ModuleLayerLoader;
|
||||||
import io.xpipe.core.util.ProxyFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
|
@ -19,18 +16,16 @@ import org.slf4j.spi.SLF4JServiceProvider;
|
||||||
|
|
||||||
open module io.xpipe.app {
|
open module io.xpipe.app {
|
||||||
exports io.xpipe.app.core;
|
exports io.xpipe.app.core;
|
||||||
exports io.xpipe.app.comp.source;
|
|
||||||
exports io.xpipe.app.util;
|
exports io.xpipe.app.util;
|
||||||
exports io.xpipe.app;
|
exports io.xpipe.app;
|
||||||
exports io.xpipe.app.issue;
|
exports io.xpipe.app.issue;
|
||||||
exports io.xpipe.app.comp.base;
|
exports io.xpipe.app.comp.base;
|
||||||
exports io.xpipe.app.core.mode;
|
exports io.xpipe.app.core.mode;
|
||||||
exports io.xpipe.app.prefs;
|
exports io.xpipe.app.prefs;
|
||||||
exports io.xpipe.app.comp.source.store;
|
exports io.xpipe.app.comp.store;
|
||||||
exports io.xpipe.app.storage;
|
exports io.xpipe.app.storage;
|
||||||
exports io.xpipe.app.update;
|
exports io.xpipe.app.update;
|
||||||
exports io.xpipe.app.comp.storage;
|
exports io.xpipe.app.comp.storage;
|
||||||
exports io.xpipe.app.comp.storage.collection;
|
|
||||||
exports io.xpipe.app.ext;
|
exports io.xpipe.app.ext;
|
||||||
exports io.xpipe.app.fxcomps.impl;
|
exports io.xpipe.app.fxcomps.impl;
|
||||||
exports io.xpipe.app.fxcomps;
|
exports io.xpipe.app.fxcomps;
|
||||||
|
@ -122,7 +117,6 @@ open module io.xpipe.app {
|
||||||
uses EventHandler;
|
uses EventHandler;
|
||||||
uses PrefsProvider;
|
uses PrefsProvider;
|
||||||
uses DataStoreProvider;
|
uses DataStoreProvider;
|
||||||
uses XPipeDaemon;
|
|
||||||
uses ProxyFunction;
|
uses ProxyFunction;
|
||||||
uses ModuleLayerLoader;
|
uses ModuleLayerLoader;
|
||||||
uses ScanProvider;
|
uses ScanProvider;
|
||||||
|
@ -142,26 +136,17 @@ open module io.xpipe.app {
|
||||||
AppLogs.Slf4jProvider;
|
AppLogs.Slf4jProvider;
|
||||||
provides EventHandler with
|
provides EventHandler with
|
||||||
EventHandlerImpl;
|
EventHandlerImpl;
|
||||||
provides XPipeDaemon with
|
|
||||||
XPipeDaemonProvider;
|
|
||||||
provides MessageExchangeImpl with
|
provides MessageExchangeImpl with
|
||||||
ReadDrainExchangeImpl,
|
ReadDrainExchangeImpl,
|
||||||
ForwardExchangeImpl,
|
|
||||||
EditStoreExchangeImpl,
|
EditStoreExchangeImpl,
|
||||||
AddSourceExchangeImpl,
|
|
||||||
StoreProviderListExchangeImpl,
|
StoreProviderListExchangeImpl,
|
||||||
ListCollectionsExchangeImpl,
|
|
||||||
OpenExchangeImpl,
|
OpenExchangeImpl,
|
||||||
LaunchExchangeImpl,
|
LaunchExchangeImpl,
|
||||||
FocusExchangeImpl,
|
FocusExchangeImpl,
|
||||||
ListEntriesExchangeImpl,
|
|
||||||
ProxyReadConnectionExchangeImpl,
|
ProxyReadConnectionExchangeImpl,
|
||||||
StatusExchangeImpl,
|
StatusExchangeImpl,
|
||||||
StopExchangeImpl,
|
StopExchangeImpl,
|
||||||
ModeExchangeImpl,
|
ModeExchangeImpl,
|
||||||
WritePreparationExchangeImpl,
|
|
||||||
WriteExecuteExchangeImpl,
|
|
||||||
ReadExchangeImpl,
|
|
||||||
DialogExchangeImpl,
|
DialogExchangeImpl,
|
||||||
ProxyWriteConnectionExchangeImpl,
|
ProxyWriteConnectionExchangeImpl,
|
||||||
RemoveStoreExchangeImpl,
|
RemoveStoreExchangeImpl,
|
||||||
|
@ -169,22 +154,10 @@ open module io.xpipe.app {
|
||||||
ProxyFunctionExchangeImpl,
|
ProxyFunctionExchangeImpl,
|
||||||
ListStoresExchangeImpl,
|
ListStoresExchangeImpl,
|
||||||
StoreAddExchangeImpl,
|
StoreAddExchangeImpl,
|
||||||
QueryDataSourceExchangeImpl,
|
|
||||||
RemoveCollectionExchangeImpl,
|
|
||||||
RemoveEntryExchangeImpl,
|
|
||||||
RenameCollectionExchangeImpl,
|
|
||||||
RenameEntryExchangeImpl,
|
|
||||||
AskpassExchangeImpl,
|
AskpassExchangeImpl,
|
||||||
SourceProviderListExchangeImpl,
|
|
||||||
QueryStoreExchangeImpl,
|
QueryStoreExchangeImpl,
|
||||||
SelectExchangeImpl,
|
|
||||||
WriteStreamExchangeImpl,
|
WriteStreamExchangeImpl,
|
||||||
ReadStreamExchangeImpl,
|
ReadStreamExchangeImpl,
|
||||||
QueryTextDataExchangeImpl,
|
|
||||||
EditExchangeImpl,
|
|
||||||
QueryTableDataExchangeImpl,
|
|
||||||
QueryRawDataExchangeImpl,
|
|
||||||
ConvertExchangeImpl,
|
|
||||||
InstanceExchangeImpl,
|
InstanceExchangeImpl,
|
||||||
VersionExchangeImpl;
|
VersionExchangeImpl;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue