mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
More fixes
This commit is contained in:
parent
0b6aee858c
commit
8ca763f185
146 changed files with 1102 additions and 6976 deletions
|
@ -2,25 +2,16 @@ package io.xpipe.api.impl;
|
|||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataTable;
|
||||
import io.xpipe.api.connector.XPipeApiConnection;
|
||||
import io.xpipe.beacon.BeaconConnection;
|
||||
import io.xpipe.beacon.BeaconException;
|
||||
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
||||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
import io.xpipe.core.data.typed.TypedAbstractReader;
|
||||
import io.xpipe.core.data.typed.TypedDataStreamParser;
|
||||
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||
|
||||
|
@ -34,9 +25,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
|||
}
|
||||
|
||||
public Stream<TupleNode> stream() {
|
||||
var iterator = new TableIterator();
|
||||
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
|
||||
.onClose(iterator::finish);
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,71 +41,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
|||
@Override
|
||||
public ArrayNode read(int maxRows) {
|
||||
List<DataStructureNode> nodes = new ArrayList<>();
|
||||
XPipeApiConnection.execute(con -> {
|
||||
var req = QueryTableDataExchange.Request.builder()
|
||||
.ref(DataSourceReference.id(getId()))
|
||||
.maxRows(maxRows)
|
||||
.build();
|
||||
con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> {
|
||||
var r = new TypedDataStreamParser(res.getDataType());
|
||||
|
||||
r.parseStructures(in, TypedDataStructureNodeReader.of(res.getDataType()), nodes::add);
|
||||
});
|
||||
});
|
||||
return ArrayNode.of(nodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TupleNode> iterator() {
|
||||
return new TableIterator();
|
||||
}
|
||||
|
||||
private class TableIterator implements Iterator<TupleNode> {
|
||||
|
||||
private final BeaconConnection connection;
|
||||
private final TypedDataStreamParser parser;
|
||||
private final TypedAbstractReader nodeReader;
|
||||
private TupleNode node;
|
||||
|
||||
{
|
||||
connection = XPipeApiConnection.open();
|
||||
var req = QueryTableDataExchange.Request.builder()
|
||||
.ref(DataSourceReference.id(getId()))
|
||||
.maxRows(Integer.MAX_VALUE)
|
||||
.build();
|
||||
connection.sendRequest(req);
|
||||
QueryTableDataExchange.Response response = connection.receiveResponse();
|
||||
|
||||
nodeReader = TypedDataStructureNodeReader.of(response.getDataType());
|
||||
parser = new TypedDataStreamParser(response.getDataType());
|
||||
|
||||
connection.receiveBody();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
return new Iterator<TupleNode>() {
|
||||
@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();
|
||||
}
|
||||
return node != null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TupleNode next() {
|
||||
connection.checkClosed();
|
||||
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,12 @@ package io.xpipe.api.impl;
|
|||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataText;
|
||||
import io.xpipe.api.connector.XPipeApiConnection;
|
||||
import io.xpipe.beacon.BeaconConnection;
|
||||
import io.xpipe.beacon.BeaconException;
|
||||
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||
|
||||
|
@ -53,47 +40,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
|
|||
|
||||
@Override
|
||||
public Stream<String> lines() {
|
||||
var iterator = new Iterator<String>() {
|
||||
|
||||
private final BeaconConnection connection;
|
||||
private final BufferedReader reader;
|
||||
private String nextValue;
|
||||
|
||||
{
|
||||
connection = XPipeApiConnection.open();
|
||||
var req = QueryTextDataExchange.Request.builder()
|
||||
.ref(DataSourceReference.id(getId()))
|
||||
.maxLines(-1)
|
||||
.build();
|
||||
connection.sendRequest(req);
|
||||
connection.receiveResponse();
|
||||
reader = new BufferedReader(new InputStreamReader(connection.receiveBody(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private void close() {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
connection.checkClosed();
|
||||
|
||||
try {
|
||||
nextValue = reader.readLine();
|
||||
} catch (IOException e) {
|
||||
throw new BeaconException(e);
|
||||
}
|
||||
return nextValue != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return nextValue;
|
||||
}
|
||||
};
|
||||
|
||||
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
|
||||
.onClose(iterator::close);
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,6 +11,7 @@ import io.xpipe.app.storage.DataStoreEntry;
|
|||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
|
@ -130,11 +131,15 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (getItem().getEntry().getStore() instanceof ShellStore fileSystem) {
|
||||
BusyProperty.execute(busy, () -> {
|
||||
getItem().refreshIfNeeded();
|
||||
});
|
||||
if (getItem().getEntry().getStore() instanceof ShellStore fileSystem) {
|
||||
model.openFileSystemAsync(null, fileSystem, null, busy);
|
||||
} else if (getItem().getEntry().getStore() instanceof FixedHierarchyStore) {
|
||||
BusyProperty.execute(busy, () -> {
|
||||
getItem().refreshWithChildren();
|
||||
});
|
||||
}
|
||||
});
|
||||
event.consume();
|
||||
|
@ -170,10 +175,14 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
|
||||
// Don't set image as that would trigger image comp update
|
||||
// and cells are emptied on each change, leading to unnecessary changes
|
||||
// img.set(null);
|
||||
setGraphic(null);
|
||||
|
||||
// Use opacity instead of visibility as visibility is kinda bugged with web views
|
||||
setOpacity(0.0);
|
||||
|
||||
setFocusTraversable(false);
|
||||
setAccessibleText(null);
|
||||
} else {
|
||||
|
@ -190,7 +199,7 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
img.set(item.getEntry()
|
||||
.getProvider()
|
||||
.getDisplayIconFileName(item.getEntry().getStore()));
|
||||
setGraphic(imageView);
|
||||
setOpacity(1.0);
|
||||
setFocusTraversable(true);
|
||||
setAccessibleText(
|
||||
item.getName() + " " + item.getEntry().getProvider().getDisplayName());
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.storage.DataStorage;
|
|||
import io.xpipe.app.util.BusyProperty;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
@ -133,7 +132,7 @@ public final class OpenFileSystemModel {
|
|||
&& fileSystem.getShell().isPresent()) {
|
||||
var directory = currentPath.get();
|
||||
var name = adjustedPath + " - "
|
||||
+ XPipeDaemon.getInstance().getStoreName(store).orElse("?");
|
||||
+ DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (ShellDialects.ALL.stream()
|
||||
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
||||
|
@ -379,8 +378,7 @@ public final class OpenFileSystemModel {
|
|||
var command = s.control()
|
||||
.initWith(connection.getShellDialect().getCdCommand(directory))
|
||||
.prepareTerminalOpen(directory + " - "
|
||||
+ XPipeDaemon.getInstance()
|
||||
.getStoreName(store)
|
||||
+ DataStorage.get().getStoreDisplayName(store)
|
||||
.orElse("?"));
|
||||
TerminalHelper.open(directory, command);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
@ -18,6 +19,9 @@ import java.util.function.Function;
|
|||
|
||||
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
|
||||
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
|
@ -32,30 +36,35 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
public CompStructure<ScrollPane> createBase() {
|
||||
Map<T, Region> cache = new HashMap<>();
|
||||
|
||||
VBox listView = new VBox();
|
||||
listView.setFocusTraversable(false);
|
||||
VBox vbox = new VBox();
|
||||
vbox.getStyleClass().add("content");
|
||||
vbox.setFocusTraversable(false);
|
||||
|
||||
refresh(listView, shown, cache, false);
|
||||
listView.requestLayout();
|
||||
refresh(vbox, shown, all, cache, false);
|
||||
vbox.requestLayout();
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
refresh(listView, c.getList(), cache, true);
|
||||
refresh(vbox, c.getList(), all, cache, true);
|
||||
});
|
||||
|
||||
all.addListener((ListChangeListener<? super T>) c -> {
|
||||
cache.keySet().retainAll(c.getList());
|
||||
});
|
||||
|
||||
var scroll = new ScrollPane(listView);
|
||||
var scroll = new ScrollPane(vbox);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.getStyleClass().add("list-box-view-comp");
|
||||
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
|
||||
private void refresh(VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
||||
Runnable update = () -> {
|
||||
var newShown = c.stream()
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
|
||||
var newShown = shown.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, compFunction.apply(v).createRegion());
|
||||
|
@ -65,6 +74,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
})
|
||||
.toList();
|
||||
|
||||
for (int i = 0; i < newShown.size(); i++) {
|
||||
var r = newShown.get(i);
|
||||
r.pseudoClassStateChanged(ODD, false);
|
||||
r.pseudoClassStateChanged(EVEN, false);
|
||||
r.pseudoClassStateChanged(i % 2 == 0 ? EVEN : ODD, true);
|
||||
}
|
||||
|
||||
if (!listView.getChildren().equals(newShown)) {
|
||||
listView.getChildren().setAll(newShown);
|
||||
listView.layout();
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
@ -27,8 +28,6 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||
|
||||
var loading = new RingProgressIndicator(0, false);
|
||||
loading.setProgress(-1);
|
||||
loading.setPrefWidth(50);
|
||||
loading.setPrefHeight(50);
|
||||
|
||||
var loadingBg = new StackPane(loading);
|
||||
loadingBg.getStyleClass().add("loading-comp");
|
||||
|
@ -69,7 +68,14 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
|||
};
|
||||
PlatformThread.sync(showLoading).addListener(listener);
|
||||
|
||||
var stack = new StackPane(compStruc.get(), loadingBg);
|
||||
var r = compStruc.get();
|
||||
var stack = new StackPane(r, loadingBg);
|
||||
|
||||
loading.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
return Math.min(r.getHeight() - 20, 50);
|
||||
}, r.heightProperty()));
|
||||
loading.prefHeightProperty().bind(loading.prefWidthProperty());
|
||||
|
||||
return new SimpleCompStructure<>(stack);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
public class NamedToggleComp extends SimpleComp {
|
||||
|
||||
private final BooleanProperty selected;
|
||||
private final ObservableValue<String> name;
|
||||
|
||||
public NamedToggleComp(BooleanProperty selected, ObservableValue<String> name) {
|
||||
this.selected = selected;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var s = new ToggleSwitch();
|
||||
s.setSelected(selected.getValue());
|
||||
s.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
selected.set(newValue);
|
||||
});
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
s.setSelected(newValue);
|
||||
});
|
||||
});
|
||||
s.textProperty().bind(PlatformThread.sync(name));
|
||||
return s;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.javafx.StackedFontIcon;
|
||||
|
||||
public class SystemStateComp extends SimpleComp {
|
||||
|
||||
|
||||
public SystemStateComp(ObservableValue<String> name, ObservableValue<State> state) {
|
||||
this.name = name;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public static enum State {
|
||||
FAILURE,
|
||||
SUCCESS,
|
||||
OTHER
|
||||
}
|
||||
|
||||
private final ObservableValue<String> name;
|
||||
private final ObservableValue<State> state;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return state.getValue() == State.FAILURE
|
||||
? "mdi2l-lightning-bolt"
|
||||
: state.getValue() == State.SUCCESS ? "mdal-check" : "mdsmz-remove";
|
||||
},
|
||||
state));
|
||||
var fi = new FontIcon();
|
||||
fi.getStyleClass().add("inner-icon");
|
||||
SimpleChangeListener.apply(icon, val -> fi.setIconLiteral(val));
|
||||
|
||||
var border = new FontIcon("mdi2c-circle-outline");
|
||||
border.getStyleClass().add("outer-icon");
|
||||
border.setOpacity(0.5);
|
||||
|
||||
var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }");
|
||||
var failure = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }");
|
||||
var other = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }");
|
||||
|
||||
var pane = new StackedFontIcon();
|
||||
pane.getChildren().addAll(fi, border);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
|
||||
var dataClass1 = """
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 22px;
|
||||
}
|
||||
.stacked-ikonli-font-icon > .inner-icon {
|
||||
-fx-icon-size: 12px;
|
||||
}
|
||||
""";
|
||||
pane.getStylesheets().add(Styles.toDataURI(dataClass1));
|
||||
|
||||
SimpleChangeListener.apply(PlatformThread.sync(state), val -> {
|
||||
pane.getStylesheets().removeAll(success, failure, other);
|
||||
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other);
|
||||
});
|
||||
|
||||
new FancyTooltipAugment<>(PlatformThread.sync(name)).augment(pane);
|
||||
return pane;
|
||||
}
|
||||
}
|
|
@ -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,93 +0,0 @@
|
|||
package io.xpipe.app.comp.storage.source;
|
||||
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataSourceEntry;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class SourceEntryContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
|
||||
|
||||
public SourceEntryContextMenu(boolean showOnPrimaryButton, SourceEntryWrapper entry, Region renameTextField) {
|
||||
super(() -> createContextMenu(entry, renameTextField));
|
||||
}
|
||||
|
||||
protected static ContextMenu createContextMenu(SourceEntryWrapper entry, Region renameTextField) {
|
||||
var cm = new ContextMenu();
|
||||
AppFont.normal(cm.getStyleableNode());
|
||||
|
||||
for (var actionProvider : entry.getActionProviders()) {
|
||||
var c = actionProvider.getDataSourceCallSite();
|
||||
var name = c.getName(entry.getEntry().getSource().asNeeded());
|
||||
var icon = c.getIcon(entry.getEntry().getSource().asNeeded());
|
||||
var item = new MenuItem(null, new FontIcon(icon));
|
||||
item.setOnAction(event -> {
|
||||
event.consume();
|
||||
try {
|
||||
var action = c.createAction(entry.getEntry().getSource().asNeeded());
|
||||
action.execute();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
});
|
||||
item.textProperty().bind(name);
|
||||
// item.setDisable(!entry.getState().getValue().isUsable());
|
||||
cm.getItems().add(item);
|
||||
|
||||
// actionProvider.applyToRegion(entry.getEntry().getStore().asNeeded(), region);
|
||||
}
|
||||
|
||||
if (entry.getActionProviders().size() > 0) {
|
||||
cm.getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
||||
var properties = new MenuItem(AppI18n.get("properties"), new FontIcon("mdi2a-application-cog"));
|
||||
properties.setOnAction(e -> {});
|
||||
// cm.getItems().add(properties);
|
||||
|
||||
var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdi2r-rename-box"));
|
||||
rename.setOnAction(e -> {
|
||||
renameTextField.requestFocus();
|
||||
});
|
||||
cm.getItems().add(rename);
|
||||
|
||||
var validate = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
|
||||
validate.setOnAction(event -> {
|
||||
DataStorage.get().refreshAsync(entry.getEntry(), true);
|
||||
});
|
||||
cm.getItems().add(validate);
|
||||
|
||||
var edit = new MenuItem(AppI18n.get("edit"), new FontIcon("mdal-edit"));
|
||||
edit.setOnAction(event -> entry.editDialog());
|
||||
edit.disableProperty().bind(Bindings.equal(DataSourceEntry.State.LOAD_FAILED, entry.getState()));
|
||||
cm.getItems().add(edit);
|
||||
|
||||
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
|
||||
del.setOnAction(e -> {
|
||||
entry.delete();
|
||||
});
|
||||
cm.getItems().add(del);
|
||||
|
||||
if (AppPrefs.get().developerMode().getValue()) {
|
||||
cm.getItems().add(new SeparatorMenuItem());
|
||||
|
||||
var openDir = new MenuItem(AppI18n.get("browseInternal"), new FontIcon("mdi2f-folder-open-outline"));
|
||||
openDir.setOnAction(e -> {
|
||||
DesktopHelper.browsePath(entry.getEntry().getDirectory());
|
||||
});
|
||||
cm.getItems().add(openDir);
|
||||
}
|
||||
|
||||
return cm;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
|
||||
private final boolean showIcon;
|
||||
|
||||
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
|
||||
super(entry, content);
|
||||
this.showIcon = showIcon;
|
||||
}
|
||||
|
||||
protected Region createContent() {
|
||||
var name = createName().createRegion();
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(8);
|
||||
|
||||
if (showIcon) {
|
||||
var storeIcon = createIcon(30, 25);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(30));
|
||||
grid.add(storeIcon, 0, 0);
|
||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
} else {
|
||||
grid.add(new Region(), 0, 0);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(0));
|
||||
}
|
||||
|
||||
var custom = new ColumnConstraints(content != null ? 300 : 0);
|
||||
custom.setHalignment(HPos.RIGHT);
|
||||
custom.setMinWidth(Region.USE_PREF_SIZE);
|
||||
custom.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
|
||||
var info = new ColumnConstraints(content != null ? 300 : 600);
|
||||
info.setHalignment(HPos.LEFT);
|
||||
info.setMinWidth(Region.USE_PREF_SIZE);
|
||||
info.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
|
||||
var nameCC = new ColumnConstraints();
|
||||
nameCC.setMinWidth(100);
|
||||
nameCC.setHgrow(Priority.ALWAYS);
|
||||
grid.getColumnConstraints().addAll(nameCC);
|
||||
grid.add(name, 1, 0);
|
||||
|
||||
grid.add(createInformation(), 2, 0);
|
||||
grid.getColumnConstraints().addAll(info, custom);
|
||||
|
||||
var cr = content != null ? content.createRegion() : new Region();
|
||||
var bb = createButtonBar().createRegion();
|
||||
var controls = new HBox(cr, bb);
|
||||
controls.setFillHeight(true);
|
||||
controls.setAlignment(Pos.CENTER_RIGHT);
|
||||
controls.setSpacing(10);
|
||||
HBox.setHgrow(cr, Priority.ALWAYS);
|
||||
grid.add(controls, 3, 0);
|
||||
|
||||
GrowAugment.create(true, false).augment(grid);
|
||||
|
||||
grid.getStyleClass().add("store-entry-grid");
|
||||
grid.getStyleClass().add("dense");
|
||||
|
||||
applyState(grid);
|
||||
return grid;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
|
||||
public StandardStoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
|
||||
super(entry, content);
|
||||
}
|
||||
|
||||
|
||||
protected Region createContent() {
|
||||
var name = createName().createRegion();
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(10);
|
||||
grid.setVgap(0);
|
||||
|
||||
var storeIcon = createIcon(60, 45);
|
||||
grid.add(storeIcon, 0, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(60));
|
||||
|
||||
grid.add(name, 1, 0);
|
||||
grid.add(createSummary(), 1, 1);
|
||||
var nameCC = new ColumnConstraints();
|
||||
nameCC.setMinWidth(100);
|
||||
nameCC.setHgrow(Priority.ALWAYS);
|
||||
grid.getColumnConstraints().addAll(nameCC);
|
||||
|
||||
grid.add(createInformation(), 2, 0, 1, 2);
|
||||
var info = new ColumnConstraints(content != null ? 300 : 600);
|
||||
info.setHalignment(HPos.LEFT);
|
||||
info.setMinWidth(Region.USE_PREF_SIZE);
|
||||
info.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
grid.getColumnConstraints().add(info);
|
||||
|
||||
var custom = new ColumnConstraints(content != null ? 300 : 0);
|
||||
custom.setHalignment(HPos.RIGHT);
|
||||
custom.setMinWidth(Region.USE_PREF_SIZE);
|
||||
custom.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
var cr = content != null ? content.createRegion() : new Region();
|
||||
var bb = createButtonBar().createRegion();
|
||||
var controls = new HBox(cr, bb);
|
||||
controls.setFillHeight(true);
|
||||
HBox.setHgrow(cr, Priority.ALWAYS);
|
||||
controls.setAlignment(Pos.CENTER_RIGHT);
|
||||
controls.setSpacing(10);
|
||||
grid.add(controls, 3, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(custom);
|
||||
|
||||
grid.getStyleClass().add("store-entry-grid");
|
||||
|
||||
applyState(grid);
|
||||
|
||||
return grid;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.comp.storage.store;
|
|||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
|
||||
import io.xpipe.app.comp.store.GuiDsStoreCreator;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreProvider;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
|
@ -20,52 +21,96 @@ import io.xpipe.app.util.ThreadHelper;
|
|||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.SneakyThrows;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StoreEntryComp extends SimpleComp {
|
||||
public abstract class StoreEntryComp extends SimpleComp {
|
||||
|
||||
private static final double NAME_WIDTH = 0.30;
|
||||
private static final double STORE_TYPE_WIDTH = 0.08;
|
||||
private static final double DETAILS_WIDTH = 0.52;
|
||||
private static final double BUTTONS_WIDTH = 0.1;
|
||||
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
||||
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
||||
private final StoreEntryWrapper entry;
|
||||
|
||||
public StoreEntryComp(StoreEntryWrapper entry) {
|
||||
this.entry = entry;
|
||||
public static Comp<?> customSection(StoreSection e) {
|
||||
var prov = e.getWrapper().getEntry().getProvider();
|
||||
if (prov != null) {
|
||||
return prov.customDisplay(e);
|
||||
} else {
|
||||
return new StandardStoreEntryComp(e.getWrapper(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private Label createInformation() {
|
||||
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
|
||||
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
|
||||
protected final StoreEntryWrapper wrapper;
|
||||
protected final Comp<?> content;
|
||||
|
||||
public StoreEntryComp(StoreEntryWrapper wrapper, Comp<?> content) {
|
||||
this.wrapper = wrapper;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Region createSimple() {
|
||||
var r = createContent();
|
||||
|
||||
var button = new JFXButton();
|
||||
button.setGraphic(r);
|
||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(r));
|
||||
button.getStyleClass().add("store-entry-comp");
|
||||
button.setPadding(Insets.EMPTY);
|
||||
button.setMaxWidth(2000);
|
||||
button.setFocusTraversable(true);
|
||||
button.accessibleTextProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return wrapper.getName();
|
||||
},
|
||||
wrapper.nameProperty()));
|
||||
button.accessibleHelpProperty().bind(wrapper.getInformation());
|
||||
button.setOnAction(event -> {
|
||||
event.consume();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
wrapper.executeDefaultAction();
|
||||
});
|
||||
});
|
||||
new ContextMenuAugment<>(() -> this.createContextMenu()).augment(new SimpleCompStructure<>(button));
|
||||
|
||||
var loading = new LoadingOverlayComp(Comp.of(() -> button), wrapper.getLoading());
|
||||
var region = loading.createRegion();
|
||||
return region;
|
||||
}
|
||||
|
||||
protected abstract Region createContent();
|
||||
|
||||
protected Label createInformation() {
|
||||
var information = new Label();
|
||||
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
|
||||
information.setGraphicTextGap(7);
|
||||
information.textProperty().bind(PlatformThread.sync(wrapper.getInformation()));
|
||||
information.getStyleClass().add("information");
|
||||
AppFont.header(information);
|
||||
|
||||
var state = wrapper.getEntry().getProvider() != null
|
||||
? wrapper.getEntry().getProvider().stateDisplay(wrapper)
|
||||
: Comp.empty();
|
||||
information.setGraphic(state.createRegion());
|
||||
|
||||
return information;
|
||||
}
|
||||
|
||||
private Label createSummary() {
|
||||
protected Label createSummary() {
|
||||
var summary = new Label();
|
||||
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
|
||||
summary.textProperty().bind(PlatformThread.sync(wrapper.getSummary()));
|
||||
summary.getStyleClass().add("summary");
|
||||
AppFont.small(summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
private void applyState(Node node) {
|
||||
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
|
||||
protected void applyState(Node node) {
|
||||
SimpleChangeListener.apply(PlatformThread.sync(wrapper.getState()), val -> {
|
||||
switch (val) {
|
||||
case LOAD_FAILED -> {
|
||||
node.pseudoClassStateChanged(FAILED, true);
|
||||
|
@ -83,111 +128,68 @@ public class StoreEntryComp extends SimpleComp {
|
|||
});
|
||||
}
|
||||
|
||||
private Comp<?> createName() {
|
||||
var name = new LabelComp(entry.nameProperty())
|
||||
.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
|
||||
protected Comp<?> createName() {
|
||||
// var filtered = BindingsHelper.filteredContentBinding(
|
||||
// StoreViewState.get().getAllEntries(),
|
||||
// other -> other.getEntry().getState().isUsable()
|
||||
// && entry.getEntry()
|
||||
// .getStore()
|
||||
// .equals(other.getEntry()
|
||||
// .getProvider()
|
||||
// .getLogicalParent(other.getEntry().getStore())));
|
||||
LabelComp name = new LabelComp(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return wrapper.getName();
|
||||
// + (filtered.size() > 0 && entry.getEntry().getStore() instanceof
|
||||
// FixedHierarchyStore
|
||||
// ? " (" + filtered.size() + ")"
|
||||
// : "");
|
||||
},
|
||||
wrapper.nameProperty(),
|
||||
wrapper.getInformation()));
|
||||
name.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
|
||||
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
|
||||
name.apply(s -> AppFont.header(s.get()));
|
||||
return name;
|
||||
}
|
||||
|
||||
private Node createIcon() {
|
||||
var img = entry.isDisabled()
|
||||
protected Node createIcon(int w, int h) {
|
||||
var img = wrapper.isDisabled()
|
||||
? "disabled_icon.png"
|
||||
: entry.getEntry()
|
||||
: wrapper.getEntry()
|
||||
.getProvider()
|
||||
.getDisplayIconFileName(entry.getEntry().getStore());
|
||||
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
|
||||
.getDisplayIconFileName(wrapper.getEntry().getStore());
|
||||
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), w, h);
|
||||
var storeIcon = imageComp.createRegion();
|
||||
storeIcon.getStyleClass().add("icon");
|
||||
if (entry.getState().getValue().isUsable()) {
|
||||
if (wrapper.getState().getValue().isUsable()) {
|
||||
new FancyTooltipAugment<>(new SimpleStringProperty(
|
||||
entry.getEntry().getProvider().getDisplayName()))
|
||||
wrapper.getEntry().getProvider().getDisplayName()))
|
||||
.augment(storeIcon);
|
||||
}
|
||||
storeIcon.setPadding(new Insets(3, 0, 0, 0));
|
||||
return storeIcon;
|
||||
}
|
||||
|
||||
protected Region createContent() {
|
||||
var name = createName().createRegion();
|
||||
|
||||
var size = createInformation();
|
||||
|
||||
var date = new Label();
|
||||
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
|
||||
AppFont.small(date);
|
||||
date.getStyleClass().add("date");
|
||||
|
||||
var grid = new GridPane();
|
||||
|
||||
var storeIcon = createIcon();
|
||||
|
||||
grid.getColumnConstraints()
|
||||
.addAll(
|
||||
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
|
||||
createShareConstraint(grid, DETAILS_WIDTH), createShareConstraint(grid, BUTTONS_WIDTH));
|
||||
grid.add(storeIcon, 0, 0, 1, 2);
|
||||
grid.add(name, 1, 0);
|
||||
grid.add(date, 1, 1);
|
||||
grid.add(createSummary(), 2, 1);
|
||||
grid.add(createInformation(), 2, 0);
|
||||
grid.add(createButtonBar().createRegion(), 3, 0, 1, 2);
|
||||
grid.setVgap(5);
|
||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
|
||||
AppFont.small(size);
|
||||
AppFont.small(date);
|
||||
|
||||
grid.getStyleClass().add("store-entry-grid");
|
||||
|
||||
applyState(grid);
|
||||
|
||||
var button = new JFXButton();
|
||||
button.setGraphic(grid);
|
||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
|
||||
button.getStyleClass().add("store-entry-comp");
|
||||
button.setMaxWidth(2000);
|
||||
button.setFocusTraversable(true);
|
||||
button.accessibleTextProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return entry.getName();
|
||||
},
|
||||
entry.nameProperty()));
|
||||
button.accessibleHelpProperty().bind(entry.getInformation());
|
||||
button.setOnAction(event -> {
|
||||
event.consume();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
entry.refreshIfNeeded();
|
||||
entry.executeDefaultAction();
|
||||
});
|
||||
});
|
||||
|
||||
new ContextMenuAugment<>(() -> StoreEntryComp.this.createContextMenu())
|
||||
.augment(new SimpleCompStructure<>(button));
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private Comp<?> createButtonBar() {
|
||||
protected Comp<?> createButtonBar() {
|
||||
var list = new ArrayList<Comp<?>>();
|
||||
for (var p : entry.getActionProviders().entrySet()) {
|
||||
for (var p : wrapper.getActionProviders().entrySet()) {
|
||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||
if (!actionProvider.isMajor()
|
||||
|| p.getKey().equals(entry.getDefaultActionProvider().getValue())) {
|
||||
|| p.getKey().equals(wrapper.getDefaultActionProvider().getValue())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var button = new IconButtonComp(
|
||||
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
|
||||
actionProvider.getIcon(wrapper.getEntry().getStore().asNeeded()), () -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var action = actionProvider.createAction(
|
||||
entry.getEntry().getStore().asNeeded());
|
||||
wrapper.getEntry().getStore().asNeeded());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
button.apply(new FancyTooltipAugment<>(
|
||||
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
|
||||
actionProvider.getName(wrapper.getEntry().getStore().asNeeded())));
|
||||
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
|
||||
button.hide(Bindings.not(p.getValue()));
|
||||
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
|
||||
|
@ -198,49 +200,51 @@ public class StoreEntryComp extends SimpleComp {
|
|||
|
||||
var settingsButton = createSettingsButton();
|
||||
list.add(settingsButton);
|
||||
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()));
|
||||
if (list.size() > 1) {
|
||||
list.get(0).styleClass(Styles.LEFT_PILL);
|
||||
for (int i = 1; i < list.size() - 1; i++) {
|
||||
list.get(i).styleClass(Styles.CENTER_PILL);
|
||||
}
|
||||
list.get(list.size() - 1).styleClass(Styles.RIGHT_PILL);
|
||||
}
|
||||
list.forEach(comp -> {
|
||||
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
|
||||
});
|
||||
return new HorizontalComp(list)
|
||||
.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER_RIGHT);
|
||||
struc.get().setPadding(new Insets(5));
|
||||
})
|
||||
.styleClass("button-bar");
|
||||
}
|
||||
|
||||
private Comp<?> createSettingsButton() {
|
||||
protected Comp<?> createSettingsButton() {
|
||||
var settingsButton = new IconButtonComp("mdomz-settings");
|
||||
settingsButton.styleClass("settings");
|
||||
settingsButton.accessibleText("Settings");
|
||||
settingsButton.apply(new ContextMenuAugment<>(
|
||||
event -> event.getButton() == MouseButton.PRIMARY, () -> StoreEntryComp.this.createContextMenu()));
|
||||
settingsButton.apply(GrowAugment.create(false, true));
|
||||
settingsButton.apply(s -> {
|
||||
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
|
||||
});
|
||||
settingsButton.apply(new FancyTooltipAugment<>("more"));
|
||||
return settingsButton;
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
protected ContextMenu createContextMenu() {
|
||||
var contextMenu = new ContextMenu();
|
||||
AppFont.normal(contextMenu.getStyleableNode());
|
||||
|
||||
for (var p : entry.getActionProviders().entrySet()) {
|
||||
for (var p : wrapper.getActionProviders().entrySet()) {
|
||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||
if (actionProvider.isMajor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = actionProvider.getName(entry.getEntry().getStore().asNeeded());
|
||||
var icon = actionProvider.getIcon(entry.getEntry().getStore().asNeeded());
|
||||
var name = actionProvider.getName(wrapper.getEntry().getStore().asNeeded());
|
||||
var icon = actionProvider.getIcon(wrapper.getEntry().getStore().asNeeded());
|
||||
var item = new MenuItem(null, new FontIcon(icon));
|
||||
item.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var action = actionProvider.createAction(
|
||||
entry.getEntry().getStore().asNeeded());
|
||||
wrapper.getEntry().getStore().asNeeded());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
|
@ -253,43 +257,35 @@ public class StoreEntryComp extends SimpleComp {
|
|||
contextMenu.getItems().add(item);
|
||||
}
|
||||
|
||||
if (entry.getActionProviders().size() > 0) {
|
||||
if (wrapper.getActionProviders().size() > 0) {
|
||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
||||
if (AppPrefs.get().developerMode().getValue()) {
|
||||
var browse = new MenuItem(AppI18n.get("browse"), new FontIcon("mdi2f-folder-open-outline"));
|
||||
browse.setOnAction(
|
||||
event -> DesktopHelper.browsePath(entry.getEntry().getDirectory()));
|
||||
event -> DesktopHelper.browsePath(wrapper.getEntry().getDirectory()));
|
||||
contextMenu.getItems().add(browse);
|
||||
}
|
||||
|
||||
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
|
||||
refresh.disableProperty().bind(entry.getRefreshable().not());
|
||||
refresh.disableProperty().bind(wrapper.getRefreshable().not());
|
||||
refresh.setOnAction(event -> {
|
||||
DataStorage.get().refreshAsync(entry.getEntry(), true);
|
||||
DataStorage.get().refreshAsync(wrapper.getEntry(), true);
|
||||
});
|
||||
contextMenu.getItems().add(refresh);
|
||||
|
||||
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
|
||||
del.disableProperty().bind(entry.getDeletable().not());
|
||||
del.setOnAction(event -> entry.delete());
|
||||
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||
del.disableProperty().bind(wrapper.getDeletable().not());
|
||||
del.setOnAction(event -> wrapper.delete());
|
||||
contextMenu.getItems().add(del);
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private ColumnConstraints createShareConstraint(Region r, double share) {
|
||||
protected ColumnConstraints createShareConstraint(Region r, double share) {
|
||||
var cc = new ColumnConstraints();
|
||||
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
|
||||
return cc;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
|
||||
var region = loading.createRegion();
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.core.AppState;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class StoreEntryListComp extends SimpleComp {
|
||||
|
||||
|
@ -23,9 +25,10 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
.getFilterString()
|
||||
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
||||
var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> {
|
||||
return new StoreEntrySection(e);
|
||||
});
|
||||
return content.styleClass("store-list-comp").styleClass(Styles.STRIPED);
|
||||
var custom = StoreSection.customSection(e).hgrow();
|
||||
return new HorizontalComp(List.of(Comp.spacer(10), custom, Comp.spacer(10))).styleClass("top");
|
||||
}).apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
|
||||
return content.styleClass("store-list-comp");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
|
||||
import io.xpipe.app.comp.storage.StorageFilter;
|
||||
import io.xpipe.app.comp.store.GuiDsStoreCreator;
|
||||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import javafx.beans.property.*;
|
||||
import lombok.Getter;
|
||||
|
||||
|
@ -94,10 +97,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
|||
disabled.setValue(entry.isDisabled());
|
||||
state.setValue(entry.getState());
|
||||
expanded.setValue(entry.isExpanded());
|
||||
information.setValue(
|
||||
entry.getInformation() != null
|
||||
? entry.getInformation()
|
||||
: entry.isDisabled() ? null : entry.getProvider().getDisplayName());
|
||||
information.setValue(entry.getInformation());
|
||||
|
||||
loading.setValue(entry.getState() == DataStoreEntry.State.VALIDATING);
|
||||
if (entry.getState().isUsable()) {
|
||||
|
@ -164,20 +164,49 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
|||
}
|
||||
|
||||
public void refreshIfNeeded() throws Exception {
|
||||
var found = getDefaultActionProvider().getValue();
|
||||
if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID) || found == null) {
|
||||
if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID)
|
||||
|| entry.getState().equals(DataStoreEntry.State.COMPLETE_NOT_VALIDATED)) {
|
||||
getEntry().refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshAsync() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
getEntry().refresh(true);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
expanded.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void refreshWithChildren() throws Exception {
|
||||
getEntry().refresh(true);
|
||||
var hasChildren = DataStorage.get().refreshChildren(entry);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
expanded.set(hasChildren);
|
||||
});
|
||||
}
|
||||
|
||||
public void refreshWithChildrenAsync() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
refreshWithChildren();
|
||||
});
|
||||
}
|
||||
|
||||
public void mutateAsync(DataStore newValue) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var hasChildren = DataStorage.get().setAndRefresh(getEntry(), newValue);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
expanded.set(hasChildren);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void executeDefaultAction() throws Exception {
|
||||
var found = getDefaultActionProvider().getValue();
|
||||
if (found != null) {
|
||||
entry.updateLastUsed();
|
||||
if (found != null) {
|
||||
refreshIfNeeded();
|
||||
found.createAction(entry.getStore().asNeeded()).execute();
|
||||
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
|
||||
refreshWithChildrenAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.comp.storage.store;
|
|||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
|
@ -35,7 +36,7 @@ public class StoreIntroComp extends SimpleComp {
|
|||
});
|
||||
|
||||
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(new LocalStore(), false));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().getStoreEntry(new LocalStore()), false));
|
||||
var scanPane = new StackPane(scanButton);
|
||||
scanPane.setAlignment(Pos.CENTER);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ public class StoreLayoutComp extends SimpleComp {
|
|||
var groupHeader = new StoreSidebarComp().createRegion();
|
||||
r.setLeft(groupHeader);
|
||||
r.setCenter(listR);
|
||||
r.getStyleClass().add("layout");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package io.xpipe.app.comp.storage.store;
|
||||
|
||||
import io.xpipe.app.comp.storage.StorageFilter;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Value;
|
||||
|
@ -14,12 +17,39 @@ import java.util.Comparator;
|
|||
@Value
|
||||
public class StoreSection implements StorageFilter.Filterable {
|
||||
|
||||
public static Comp<?> customSection(StoreSection e) {
|
||||
var prov = e.getWrapper().getEntry().getProvider();
|
||||
if (prov != null) {
|
||||
return prov.customContainer(e);
|
||||
} else {
|
||||
return new StoreSectionComp(e);
|
||||
}
|
||||
}
|
||||
|
||||
StoreEntryWrapper wrapper;
|
||||
ObservableList<StoreSection> children;
|
||||
int depth;
|
||||
ObservableBooleanValue showDetails;
|
||||
|
||||
public StoreSection(StoreEntryWrapper wrapper, ObservableList<StoreSection> children, int depth) {
|
||||
this.wrapper = wrapper;
|
||||
this.children = children;
|
||||
this.depth = depth;
|
||||
if (wrapper != null) {
|
||||
this.showDetails = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return wrapper.getExpanded().get() || children.size() == 0;
|
||||
},
|
||||
wrapper.getExpanded(),
|
||||
children);
|
||||
} else {
|
||||
this.showDetails = new SimpleBooleanProperty(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
|
||||
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
|
||||
? o.wrapper.getEntry().getLastAccess()
|
||||
o -> o.wrapper.getEntry().getState().isUsable()
|
||||
? o.wrapper.getEntry().getLastModified()
|
||||
: Instant.EPOCH)
|
||||
.reversed()
|
||||
.thenComparing(
|
||||
|
@ -27,39 +57,31 @@ public class StoreSection implements StorageFilter.Filterable {
|
|||
|
||||
public static StoreSection createTopLevel() {
|
||||
var topLevel = BindingsHelper.mappedContentBinding(
|
||||
StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper));
|
||||
StoreViewState.get().getAllEntries(), storeEntryWrapper -> create(storeEntryWrapper, 1));
|
||||
var filtered = BindingsHelper.filteredContentBinding(topLevel, section -> {
|
||||
if (!section.getWrapper().getEntry().getState().isUsable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var parent = section.getWrapper()
|
||||
.getEntry()
|
||||
.getProvider()
|
||||
.getParent(section.getWrapper().getEntry().getStore());
|
||||
return parent == null
|
||||
|| (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
|
||||
return DataStorage.get()
|
||||
.getParent(section.getWrapper().getEntry(), true)
|
||||
.isEmpty();
|
||||
});
|
||||
var ordered = BindingsHelper.orderedContentBinding(filtered, COMPARATOR);
|
||||
return new StoreSection(null, ordered);
|
||||
return new StoreSection(null, ordered, 0);
|
||||
}
|
||||
|
||||
private static StoreSection create(StoreEntryWrapper e) {
|
||||
private static StoreSection create(StoreEntryWrapper e, int depth) {
|
||||
if (!e.getEntry().getState().isUsable()) {
|
||||
return new StoreSection(e, FXCollections.observableArrayList());
|
||||
return new StoreSection(e, FXCollections.observableArrayList(), depth);
|
||||
}
|
||||
|
||||
var filtered = BindingsHelper.filteredContentBinding(
|
||||
StoreViewState.get().getAllEntries(),
|
||||
other -> other.getEntry().getState().isUsable()
|
||||
&& e.getEntry()
|
||||
.getStore()
|
||||
.equals(other.getEntry()
|
||||
.getProvider()
|
||||
.getParent(other.getEntry().getStore())));
|
||||
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
|
||||
var filtered =
|
||||
BindingsHelper.filteredContentBinding(StoreViewState.get().getAllEntries(), other -> {
|
||||
return DataStorage.get()
|
||||
.getParent(other.getEntry(), true)
|
||||
.map(found -> found.equals(e.getEntry()))
|
||||
.orElse(false);
|
||||
});
|
||||
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1, depth + 1));
|
||||
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
|
||||
return new StoreSection(e, ordered);
|
||||
return new StoreSection(e, ordered, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,23 +8,30 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
|||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StoreEntrySection extends Comp<CompStructure<VBox>> {
|
||||
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
|
||||
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
|
||||
|
||||
private final StoreSection section;
|
||||
|
||||
public StoreEntrySection(StoreSection section) {
|
||||
public StoreSectionComp(StoreSection section) {
|
||||
this.section = section;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var root = new StoreEntryComp(section.getWrapper()).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var root = StandardStoreEntryComp.customSection(section).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> section.getWrapper().getExpanded().get()
|
||||
|
@ -35,7 +42,7 @@ public class StoreEntrySection extends Comp<CompStructure<VBox>> {
|
|||
() -> {
|
||||
section.getWrapper().toggleExpanded();
|
||||
})
|
||||
.apply(struc -> struc.get().setPrefWidth(40))
|
||||
.apply(struc -> struc.get().setPrefWidth(30))
|
||||
.focusTraversable()
|
||||
.accessibleText("Expand")
|
||||
.disable(BindingsHelper.persist(
|
||||
|
@ -51,23 +58,32 @@ public class StoreEntrySection extends Comp<CompStructure<VBox>> {
|
|||
.getFilterString()
|
||||
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
|
||||
var content = new ListBoxViewComp<>(shown, all, (StoreSection e) -> {
|
||||
return new StoreEntrySection(e).apply(GrowAugment.create(true, false));
|
||||
})
|
||||
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
|
||||
.apply(struc -> struc.get().backgroundProperty().set(Background.fill(Color.color(0, 0, 0, 0.01))));
|
||||
var spacer = Comp.of(() -> {
|
||||
var padding = new Region();
|
||||
padding.setMinWidth(25);
|
||||
padding.setMaxWidth(25);
|
||||
return padding;
|
||||
});
|
||||
return StoreSection.customSection(e).apply(GrowAugment.create(true, false));
|
||||
}).hgrow();
|
||||
|
||||
var expanded = Bindings.createBooleanBinding(() -> {
|
||||
return section.getWrapper().getExpanded().get() && section.getChildren().size() > 0;
|
||||
}, section.getWrapper().getExpanded(), section.getChildren());
|
||||
|
||||
return new VerticalComp(List.of(
|
||||
new HorizontalComp(topEntryList),
|
||||
new HorizontalComp(List.of(spacer, content))
|
||||
new HorizontalComp(topEntryList)
|
||||
.apply(struc -> struc.get().setFillHeight(true)),
|
||||
Comp.separator().visible(expanded),
|
||||
new HorizontalComp(List.of(content))
|
||||
.styleClass("content")
|
||||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.hide(BindingsHelper.persist(Bindings.or(
|
||||
Bindings.not(section.getWrapper().getExpanded()),
|
||||
Bindings.size(section.getChildren()).isEqualTo(0))))))
|
||||
.styleClass("store-entry-section-comp")
|
||||
.apply(struc -> {
|
||||
struc.get().setFillWidth(true);
|
||||
SimpleChangeListener.apply(expanded, val -> {
|
||||
struc.get().pseudoClassStateChanged(EXPANDED, val);
|
||||
});
|
||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
|
||||
})
|
||||
.createStructure();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.comp.storage.store;
|
|||
|
||||
import io.xpipe.app.comp.storage.StorageFilter;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataSourceCollection;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.StorageListener;
|
||||
|
@ -13,8 +12,10 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StoreViewState {
|
||||
|
||||
|
@ -55,25 +56,21 @@ public class StoreViewState {
|
|||
|
||||
DataStorage.get().addListener(new StorageListener() {
|
||||
@Override
|
||||
public void onStoreAdd(DataStoreEntry entry) {
|
||||
public void onStoreAdd(DataStoreEntry... entry) {
|
||||
var l = Arrays.stream(entry).map(StoreEntryWrapper::new).toList();
|
||||
Platform.runLater(() -> {
|
||||
var sg = new StoreEntryWrapper(entry);
|
||||
allEntries.add(sg);
|
||||
allEntries.addAll(l);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreRemove(DataStoreEntry entry) {
|
||||
public void onStoreRemove(DataStoreEntry... entry) {
|
||||
var a = Arrays.stream(entry).collect(Collectors.toSet());
|
||||
var l = StoreViewState.get().getAllEntries().stream().filter(storeEntryWrapper -> a.contains(storeEntryWrapper.getEntry())).toList();
|
||||
Platform.runLater(() -> {
|
||||
allEntries.removeIf(e -> e.getEntry().equals(entry));
|
||||
allEntries.removeAll(l);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectionAdd(DataSourceCollection collection) {}
|
||||
|
||||
@Override
|
||||
public void onCollectionRemove(DataSourceCollection collection) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import io.xpipe.app.core.AppI18n;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import io.xpipe.app.core.AppCache;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import io.xpipe.app.core.AppI18n;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.browser.StandaloneFileBrowser;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreProvider;
|
||||
|
@ -50,7 +50,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
|
|||
var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true);
|
||||
comboBox.setAccessibleNames(dataStoreProvider -> dataStoreProvider.getDisplayName());
|
||||
getProviders().stream()
|
||||
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.shouldShow())
|
||||
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.canManuallyCreate())
|
||||
.forEach(comboBox::add);
|
||||
ComboBox<Node> cb = comboBox.build();
|
||||
cb.getStyleClass().add("data-source-type");
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.FileDropOverlayComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
|
@ -12,11 +12,11 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
|
|||
import io.xpipe.app.fxcomps.impl.TabPaneComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.SimpleValidator;
|
||||
import io.xpipe.app.util.Validatable;
|
||||
import io.xpipe.app.util.Validator;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
@ -70,7 +70,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
|
|||
@Override
|
||||
protected Region createSimple() {
|
||||
var isNamedStore =
|
||||
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent();
|
||||
DataStorage.get().getStoreDisplayName(selected.getValue()).isPresent();
|
||||
var localStore = new SimpleObjectProperty<>(
|
||||
!isNamedStore
|
||||
&& selected.getValue() instanceof FileStore fileStore
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.ErrorOverlayComp;
|
||||
import io.xpipe.app.comp.base.MultiStepComp;
|
||||
|
@ -123,7 +123,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
|
|||
e -> {
|
||||
try {
|
||||
DataStorage.get().addStoreEntry(e);
|
||||
// ScanAlert.showAsync(e.getStore(), true);
|
||||
ScanAlert.showAsync(e, true);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.xpipe.app.comp.source.store;
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListViewComp;
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.core.mode;
|
||||
|
||||
import io.xpipe.app.browser.BrowserModel;
|
||||
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
||||
import io.xpipe.app.comp.storage.store.StoreViewState;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.issue.*;
|
||||
|
@ -55,7 +54,6 @@ public class BaseMode extends OperationMode {
|
|||
TrackEvent.info("mode", "Background mode shutdown started");
|
||||
BrowserModel.DEFAULT.reset();
|
||||
AppSocketServer.reset();
|
||||
SourceCollectionViewState.reset();
|
||||
StoreViewState.reset();
|
||||
DataStorage.reset();
|
||||
AppPrefs.reset();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.core.mode;
|
||||
|
||||
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
||||
import io.xpipe.app.comp.storage.store.StoreViewState;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
@ -83,7 +82,6 @@ public abstract class PlatformMode extends OperationMode {
|
|||
UpdateAvailableAlert.showIfNeeded();
|
||||
}
|
||||
|
||||
SourceCollectionViewState.init();
|
||||
StoreViewState.init();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.DataSourceProviders;
|
||||
import io.xpipe.app.storage.DataSourceCollection;
|
||||
import io.xpipe.app.storage.DataSourceEntry;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
|
@ -14,9 +12,6 @@ import io.xpipe.beacon.exchange.MessageExchange;
|
|||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.impl.NamedStore;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.NonNull;
|
||||
|
||||
|
@ -68,42 +63,6 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
|
|||
return store.get();
|
||||
}
|
||||
|
||||
default DataSourceCollection getCollection(String name) throws ClientException {
|
||||
var col = DataStorage.get().getCollectionForName(name);
|
||||
if (col.isEmpty()) {
|
||||
throw new ClientException("No collection with name " + name + " was found");
|
||||
}
|
||||
return col.get();
|
||||
}
|
||||
|
||||
default DataSourceEntry getSourceEntry(DataSourceReference ref, DataSourceType typeFilter, boolean acceptDisabled)
|
||||
throws ClientException {
|
||||
var ds = DataStorage.get().getDataSource(ref);
|
||||
if (ds.isEmpty() && ref.getType() == DataSourceReference.Type.LATEST) {
|
||||
throw new ClientException("No latest data source available");
|
||||
}
|
||||
if (ds.isEmpty()) {
|
||||
throw new ClientException("Unable to locate data source with reference " + ref.toRefString());
|
||||
}
|
||||
|
||||
if (typeFilter != null && ds.get().getProvider().getPrimaryType() != typeFilter) {
|
||||
throw new ClientException(
|
||||
"Data source is not a " + typeFilter.name().toLowerCase());
|
||||
}
|
||||
|
||||
if (!ds.get().getState().isUsable() && !acceptDisabled) {
|
||||
throw new ClientException(
|
||||
String.format("Data source %s is disabled", ds.get().getName()));
|
||||
}
|
||||
|
||||
return ds.get();
|
||||
}
|
||||
|
||||
default DataSourceEntry getSourceEntry(DataSourceId id, DataSourceType typeFilter, boolean acceptDisabled)
|
||||
throws ClientException {
|
||||
return getSourceEntry(DataSourceReference.id(id), typeFilter, acceptDisabled);
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;
|
||||
|
|
|
@ -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.DataSourceProviders;
|
||||
import io.xpipe.app.storage.DataSourceEntry;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
import io.xpipe.beacon.exchange.ReadExchange;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -54,11 +51,6 @@ public class ReadExchangeImpl extends ReadExchange
|
|||
var entryName =
|
||||
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
|
||||
|
||||
var configRef = DialogExchangeImpl.add(Dialog.chain(typeQ, config), (DataSource<?> s) -> {
|
||||
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, s);
|
||||
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
|
||||
});
|
||||
|
||||
return Response.builder().config(configRef).build();
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ public class ListStoresExchangeImpl extends ListStoresExchange
|
|||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
DataStorage s = DataStorage.get();
|
||||
var e = s.getStoreEntries().stream()
|
||||
.filter(entry -> !entry.isDisabled() && entry.getProvider().shouldShow())
|
||||
.filter(entry -> !entry.isDisabled() && entry.getProvider().canManuallyCreate())
|
||||
.sorted(Comparator.comparing(dataStoreEntry -> dataStoreEntry.getLastUsed()))
|
||||
.map(col -> StoreListEntry.builder()
|
||||
.name(col.getName())
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
|
|||
.map(p -> ProviderEntry.builder()
|
||||
.id(p.getId())
|
||||
.description(p.getDisplayDescription())
|
||||
.hidden(!p.shouldShow())
|
||||
.hidden(!p.canManuallyCreate())
|
||||
.build())
|
||||
.toList()));
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import javafx.beans.value.ObservableValue;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface ActionProvider {
|
||||
|
||||
|
@ -29,7 +28,7 @@ public interface ActionProvider {
|
|||
return false;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toSet()));
|
||||
.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
import io.xpipe.app.comp.base.SystemStateComp;
|
||||
import io.xpipe.app.comp.storage.store.StandardStoreEntryComp;
|
||||
import io.xpipe.app.comp.storage.store.StoreSectionComp;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.comp.storage.store.StoreSection;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import io.xpipe.core.store.*;
|
||||
import io.xpipe.core.util.JacksonizedValue;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -31,6 +34,36 @@ public interface DataStoreProvider {
|
|||
}
|
||||
}
|
||||
|
||||
default Comp<?> customDisplay(StoreSection s) {
|
||||
return new StandardStoreEntryComp(s.getWrapper(), null);
|
||||
}
|
||||
|
||||
default Comp<?> customContainer(StoreSection section) {
|
||||
return new StoreSectionComp(section);
|
||||
}
|
||||
|
||||
default String failureInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Comp<?> stateDisplay(StoreEntryWrapper w) {
|
||||
var state = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID
|
||||
? SystemStateComp.State.FAILURE
|
||||
: w.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? SystemStateComp.State.SUCCESS
|
||||
: SystemStateComp.State.OTHER;
|
||||
},
|
||||
w.getState());
|
||||
var name = Bindings.createStringBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID ? "stop" : "start";
|
||||
},
|
||||
w.getState());
|
||||
return new SystemStateComp(name, state);
|
||||
}
|
||||
|
||||
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
|
||||
var content = Bindings.createStringBinding(
|
||||
() -> {
|
||||
|
@ -77,10 +110,14 @@ public interface DataStoreProvider {
|
|||
return DisplayCategory.OTHER;
|
||||
}
|
||||
|
||||
default DataStore getParent(DataStore store) {
|
||||
default DataStore getLogicalParent(DataStore store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default DataStore getDisplayParent(DataStore store) {
|
||||
return getLogicalParent(store);
|
||||
}
|
||||
|
||||
default GuiDialog guiDialog(Property<DataStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
@ -97,6 +134,10 @@ public interface DataStoreProvider {
|
|||
|
||||
String queryInformationString(DataStore store, int length) throws Exception;
|
||||
|
||||
default String queryInvalidInformationString(DataStore store, int length) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
String toSummaryString(DataStore store, int length);
|
||||
|
||||
default String i18n(String key) {
|
||||
|
@ -129,7 +170,13 @@ public interface DataStoreProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
DataStore defaultStore();
|
||||
default boolean requiresFrequentRefresh() {
|
||||
return getStoreClasses().stream().anyMatch(aClass -> FixedHierarchyStore.class.isAssignableFrom(aClass));
|
||||
}
|
||||
|
||||
default DataStore defaultStore() {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> getPossibleNames();
|
||||
|
||||
|
@ -139,7 +186,7 @@ public interface DataStoreProvider {
|
|||
|
||||
List<Class<?>> getStoreClasses();
|
||||
|
||||
default boolean shouldShow() {
|
||||
default boolean canManuallyCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import lombok.Value;
|
||||
import org.apache.commons.lang3.function.FailableRunnable;
|
||||
|
@ -53,7 +53,7 @@ public abstract class ScanProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
public ScanOperation create(ShellStore store, ShellControl sc, boolean automatic) throws Exception {
|
||||
public ScanOperation create(DataStoreEntry entry, ShellControl sc, boolean automatic) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.ext;
|
|||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.process.ProcessControlProvider;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
|
@ -12,7 +11,8 @@ import io.xpipe.core.util.ProxyFunction;
|
|||
public class XPipeServiceProviders {
|
||||
|
||||
public static void load(ModuleLayer layer) {
|
||||
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
|
||||
// TODO
|
||||
var hasDaemon = true;
|
||||
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
|
||||
ErrorEvent.fromThrowable(t).handle();
|
||||
});
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package io.xpipe.app.fxcomps;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.fxcomps.augment.Augment;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.WrapperComp;
|
||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Tooltip;
|
||||
|
@ -26,19 +27,25 @@ public abstract class Comp<S extends CompStructure<?>> {
|
|||
|
||||
private List<Augment<S>> augments;
|
||||
|
||||
public static Comp<CompStructure<Region>> empty() {
|
||||
return of(() -> new Region());
|
||||
}
|
||||
|
||||
public static Comp<CompStructure<Spacer>> spacer(double size) {
|
||||
return of(() -> new Spacer(size));
|
||||
}
|
||||
|
||||
public static <R extends Region> Comp<CompStructure<R>> of(Supplier<R> r) {
|
||||
return new WrapperComp<>(() -> {
|
||||
var region = r.get();
|
||||
return () -> region;
|
||||
});
|
||||
return new Comp<>() {
|
||||
@Override
|
||||
public CompStructure<R> createBase() {
|
||||
return new SimpleCompStructure<>(r.get());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Comp<CompStructure<Separator>> separator() {
|
||||
return Comp.of(() -> new Separator());
|
||||
}
|
||||
|
||||
public static <S extends CompStructure<?>> Comp<S> ofStructure(Supplier<S> r) {
|
||||
return new WrapperComp<>(r);
|
||||
return of(() -> new Separator(Orientation.HORIZONTAL));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.ext.DataStoreProviders;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.CustomComboBoxBuilder;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.LeafShellStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
@ -70,7 +69,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
return Optional.of(AppI18n.get("none"));
|
||||
}
|
||||
|
||||
return XPipeDaemon.getInstance().getStoreName(store);
|
||||
return DataStorage.get().getStoreDisplayName(store);
|
||||
})
|
||||
.orElse(AppI18n.get("unknown"));
|
||||
|
||||
|
@ -82,7 +81,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
return AppI18n.get("none");
|
||||
}
|
||||
|
||||
return XPipeDaemon.getInstance().getStoreName(store).orElse("?");
|
||||
return DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProviders;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.CustomComboBoxBuilder;
|
||||
import io.xpipe.app.util.XPipeDaemon;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -27,7 +26,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
|
|||
var name = DataStorage.get().getUsableStores().stream()
|
||||
.filter(e -> e.equals(store))
|
||||
.findAny()
|
||||
.map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?"))
|
||||
.map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
|
||||
.orElse("?");
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
|
@ -9,9 +9,10 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.scene.control.Button;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class IconButtonComp extends Comp<CompStructure<JFXButton>> {
|
||||
public class IconButtonComp extends Comp<CompStructure<Button>> {
|
||||
|
||||
private final ObservableValue<String> icon;
|
||||
private final Runnable listener;
|
||||
|
@ -30,8 +31,9 @@ public class IconButtonComp extends Comp<CompStructure<JFXButton>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<JFXButton> createBase() {
|
||||
var button = new JFXButton();
|
||||
public CompStructure<Button> createBase() {
|
||||
var button = new Button();
|
||||
button.getStyleClass().add(Styles.FLAT);
|
||||
|
||||
var fi = new FontIcon(icon.getValue());
|
||||
fi.setFocusTraversable(false);
|
||||
|
|
|
@ -6,15 +6,12 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebView;
|
||||
|
||||
public class PrettyImageComp extends SimpleComp {
|
||||
|
||||
|
@ -31,6 +28,8 @@ public class PrettyImageComp extends SimpleComp {
|
|||
@Override
|
||||
protected Region createSimple() {
|
||||
var aspectRatioProperty = new SimpleDoubleProperty(1);
|
||||
var imageAspectRatioProperty = new SimpleDoubleProperty(1);
|
||||
var svgAspectRatioProperty = new SimpleDoubleProperty(1);
|
||||
var widthProperty = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
boolean widthLimited = width / height < aspectRatioProperty.doubleValue();
|
||||
|
@ -51,32 +50,17 @@ public class PrettyImageComp extends SimpleComp {
|
|||
}
|
||||
},
|
||||
aspectRatioProperty);
|
||||
|
||||
var image = new SimpleStringProperty();
|
||||
var currentNode = new SimpleObjectProperty<Node>();
|
||||
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
|
||||
image.set(val);
|
||||
var requiresChange = val == null
|
||||
|| (val.endsWith(".svg") && !(currentNode.get() instanceof WebView)
|
||||
|| !(currentNode.get() instanceof ImageView));
|
||||
if (!requiresChange) {
|
||||
return;
|
||||
var stack = new StackPane();
|
||||
|
||||
{
|
||||
var svgImageContent = new SimpleStringProperty();
|
||||
var storeIcon = SvgView.create(svgImageContent);
|
||||
SimpleChangeListener.apply(image, newValue -> {
|
||||
if (AppImages.hasSvgImage(newValue)) {
|
||||
svgImageContent.set(AppImages.svgImage(newValue));
|
||||
}
|
||||
|
||||
aspectRatioProperty.unbind();
|
||||
|
||||
if (val == null) {
|
||||
currentNode.set(new Region());
|
||||
} else if (val.endsWith(".svg")) {
|
||||
var storeIcon = SvgView.create(Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (!AppImages.hasSvgImage(image.getValue())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AppImages.svgImage(image.getValue());
|
||||
},
|
||||
image));
|
||||
});
|
||||
var ar = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return storeIcon.getWidth().getValue().doubleValue()
|
||||
|
@ -84,7 +68,7 @@ public class PrettyImageComp extends SimpleComp {
|
|||
},
|
||||
storeIcon.getWidth(),
|
||||
storeIcon.getHeight());
|
||||
aspectRatioProperty.bind(ar);
|
||||
svgAspectRatioProperty.bind(ar);
|
||||
var node = storeIcon.createWebview();
|
||||
node.prefWidthProperty().bind(widthProperty);
|
||||
node.maxWidthProperty().bind(widthProperty);
|
||||
|
@ -92,8 +76,10 @@ public class PrettyImageComp extends SimpleComp {
|
|||
node.prefHeightProperty().bind(heightProperty);
|
||||
node.maxHeightProperty().bind(heightProperty);
|
||||
node.minHeightProperty().bind(heightProperty);
|
||||
currentNode.set(node);
|
||||
} else {
|
||||
stack.getChildren().add(node);
|
||||
}
|
||||
|
||||
{
|
||||
var storeIcon = new ImageView();
|
||||
storeIcon.setFocusTraversable(false);
|
||||
storeIcon
|
||||
|
@ -117,23 +103,31 @@ public class PrettyImageComp extends SimpleComp {
|
|||
/ storeIcon.getImage().getHeight();
|
||||
},
|
||||
storeIcon.imageProperty());
|
||||
aspectRatioProperty.bind(ar);
|
||||
imageAspectRatioProperty.bind(ar);
|
||||
storeIcon.fitWidthProperty().bind(widthProperty);
|
||||
storeIcon.fitHeightProperty().bind(heightProperty);
|
||||
storeIcon.setSmooth(true);
|
||||
currentNode.set(storeIcon);
|
||||
stack.getChildren().add(storeIcon);
|
||||
}
|
||||
});
|
||||
|
||||
var stack = new StackPane();
|
||||
SimpleChangeListener.apply(currentNode, val -> {
|
||||
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
|
||||
image.set(val);
|
||||
aspectRatioProperty.unbind();
|
||||
|
||||
if (val == null) {
|
||||
stack.getChildren().clear();
|
||||
return;
|
||||
stack.getChildren().get(0).setOpacity(0.0);
|
||||
stack.getChildren().get(1).setOpacity(0.0);
|
||||
} else if (val.endsWith(".svg")) {
|
||||
aspectRatioProperty.bind(svgAspectRatioProperty);
|
||||
stack.getChildren().get(0).setOpacity(1.0);
|
||||
stack.getChildren().get(1).setOpacity(0.0);
|
||||
} else {
|
||||
aspectRatioProperty.bind(imageAspectRatioProperty);
|
||||
stack.getChildren().get(0).setOpacity(0.0);
|
||||
stack.getChildren().get(1).setOpacity(1.0);
|
||||
}
|
||||
|
||||
stack.getChildren().setAll(val);
|
||||
});
|
||||
|
||||
stack.setFocusTraversable(false);
|
||||
stack.setPrefWidth(width);
|
||||
stack.setMinWidth(width);
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class WrapperComp<S extends CompStructure<?>> extends Comp<S> {
|
||||
|
||||
private final Supplier<S> structureSupplier;
|
||||
|
||||
public WrapperComp(Supplier<S> structureSupplier) {
|
||||
this.structureSupplier = structureSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S createBase() {
|
||||
return structureSupplier.get();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue