mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Rework, add more comps, improve dialogs
This commit is contained in:
parent
b31ef8ed0e
commit
f0f1417980
60 changed files with 967 additions and 177 deletions
21
api/src/main/java/io/xpipe/api/DataStores.java
Normal file
21
api/src/main/java/io/xpipe/api/DataStores.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.api.util.QuietDialogHandler;
|
||||
import io.xpipe.beacon.exchange.cli.StoreAddExchange;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DataStores {
|
||||
|
||||
public static void addNamedStore(DataStore store, String name) {
|
||||
XPipeConnection.execute(con -> {
|
||||
var req = StoreAddExchange.Request.builder()
|
||||
.storeInput(store).name(name).build();
|
||||
StoreAddExchange.Response res = con.performSimpleExchange(req);
|
||||
|
||||
new QuietDialogHandler(res.getConfig(), con, Map.of()).handle();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import io.xpipe.api.DataSource;
|
|||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.beacon.exchange.QueryDataSourceExchange;
|
||||
import io.xpipe.beacon.exchange.ReadPreparationExchange;
|
||||
import io.xpipe.beacon.exchange.ReadExchange;
|
||||
import io.xpipe.beacon.exchange.StoreStreamExchange;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
|
@ -50,12 +50,12 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
|
||||
var store = res.getStore();
|
||||
|
||||
var startReq = ReadPreparationExchange.Request.builder()
|
||||
var startReq = ReadExchange.Request.builder()
|
||||
.provider(type)
|
||||
.store(store)
|
||||
.build();
|
||||
var startRes = XPipeConnection.execute(con -> {
|
||||
ReadPreparationExchange.Response r = con.performSimpleExchange(startReq);
|
||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||
return r;
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.api.DataTable;
|
|||
import io.xpipe.api.DataTableAccumulator;
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.api.util.TypeDescriptor;
|
||||
import io.xpipe.beacon.exchange.ReadExecuteExchange;
|
||||
import io.xpipe.beacon.exchange.StoreStreamExchange;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
|
@ -38,11 +37,11 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
|||
StoreStreamExchange.Response res = connection.receiveResponse();
|
||||
connection.close();
|
||||
|
||||
var req = ReadExecuteExchange.Request.builder()
|
||||
.target(id).dataStore(res.getStore()).build();
|
||||
XPipeConnection.execute(con -> {
|
||||
con.performSimpleExchange(req);
|
||||
});
|
||||
// var req = ReadExecuteExchange.Request.builder()
|
||||
// .target(id).dataStore(res.getStore()).build();
|
||||
// XPipeConnection.execute(con -> {
|
||||
// con.performSimpleExchange(req);
|
||||
// });
|
||||
return DataSource.get(DataSourceReference.id(id)).asTable();
|
||||
}
|
||||
|
||||
|
|
67
api/src/main/java/io/xpipe/api/util/QuietDialogHandler.java
Normal file
67
api/src/main/java/io/xpipe/api/util/QuietDialogHandler.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
package io.xpipe.api.util;
|
||||
|
||||
import io.xpipe.beacon.BeaconConnection;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
import io.xpipe.beacon.exchange.cli.DialogExchange;
|
||||
import io.xpipe.core.dialog.BaseQueryElement;
|
||||
import io.xpipe.core.dialog.ChoiceElement;
|
||||
import io.xpipe.core.dialog.DialogElement;
|
||||
import io.xpipe.core.dialog.DialogReference;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class QuietDialogHandler {
|
||||
|
||||
private final UUID dialogKey;
|
||||
private DialogElement element;
|
||||
private final BeaconConnection connection;
|
||||
private final Map<String, String> overrides;
|
||||
|
||||
public QuietDialogHandler(DialogReference ref, BeaconConnection connection, Map<String, String> overrides) {
|
||||
this.dialogKey = ref.getDialogId();
|
||||
this.element = ref.getStart();
|
||||
this.connection = connection;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
public void handle() throws ClientException {
|
||||
String response = null;
|
||||
|
||||
if (element instanceof ChoiceElement c) {
|
||||
response = handleChoice(c);
|
||||
}
|
||||
|
||||
if (element instanceof BaseQueryElement q) {
|
||||
response = handleQuery(q);
|
||||
}
|
||||
|
||||
DialogExchange.Response res = connection.performSimpleExchange(
|
||||
DialogExchange.Request.builder().dialogKey(dialogKey).value(response).build());
|
||||
if (res.getElement() != null && element.equals(res.getElement())) {
|
||||
throw new ClientException("Invalid value for key " + res.getElement().toDisplayString());
|
||||
}
|
||||
|
||||
element = res.getElement();
|
||||
|
||||
if (element != null) {
|
||||
handle();
|
||||
}
|
||||
}
|
||||
|
||||
private String handleQuery(BaseQueryElement q) {
|
||||
if (q.isRequired() && !overrides.containsKey(q.getDescription())) {
|
||||
throw new IllegalStateException("Missing required config parameter: " + q.getDescription());
|
||||
}
|
||||
|
||||
return overrides.get(q.getDescription());
|
||||
}
|
||||
|
||||
private String handleChoice(ChoiceElement c) {
|
||||
if (c.isRequired() && !overrides.containsKey(c.getDescription())) {
|
||||
throw new IllegalStateException("Missing required config parameter: " + c.getDescription());
|
||||
}
|
||||
|
||||
return overrides.get(c.getDescription());
|
||||
}
|
||||
}
|
|
@ -126,7 +126,7 @@ public class BeaconClient implements AutoCloseable {
|
|||
var in = socket.getInputStream();
|
||||
read = JacksonHelper.newMapper().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE).readTree(in);
|
||||
} catch (SocketException ex) {
|
||||
throw new ConnectorException("Connection to xpipe daemon closed unexpectedly");
|
||||
throw new ConnectorException("Connection to xpipe daemon closed unexpectedly", ex);
|
||||
} catch (IOException ex) {
|
||||
throw new ConnectorException("Couldn't read from socket", ex);
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ public abstract class BeaconConnection implements AutoCloseable {
|
|||
|
||||
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
|
||||
REQ req,
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) {
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
|
||||
checkClosed();
|
||||
|
||||
performInputOutputExchange(req, null, responseConsumer);
|
||||
|
@ -79,7 +79,7 @@ public abstract class BeaconConnection implements AutoCloseable {
|
|||
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputOutputExchange(
|
||||
REQ req,
|
||||
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) {
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
|
||||
checkClosed();
|
||||
|
||||
try {
|
||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.beacon.ResponseMessage;
|
|||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
@ -35,13 +34,13 @@ public class QueryDataSourceExchange implements MessageExchange {
|
|||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
@NonNull
|
||||
DataSourceId id;
|
||||
boolean disabled;
|
||||
@NonNull
|
||||
DataSourceInfo info;
|
||||
@NonNull
|
||||
DataStore store;
|
||||
@NonNull
|
||||
String storeDisplay;
|
||||
|
||||
String provider;
|
||||
@NonNull
|
||||
Map<String, String> config;
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.beacon.exchange;
|
|||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.core.dialog.DialogReference;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
|
@ -12,11 +13,11 @@ import lombok.extern.jackson.Jacksonized;
|
|||
/**
|
||||
* Prepares a client to send stream-based data to a daemon.
|
||||
*/
|
||||
public class ReadPreparationExchange implements MessageExchange {
|
||||
public class ReadExchange implements MessageExchange {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "readPreparation";
|
||||
return "read";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
@ -28,6 +29,8 @@ public class ReadPreparationExchange implements MessageExchange {
|
|||
@NonNull
|
||||
DataStore store;
|
||||
|
||||
DataSourceId target;
|
||||
|
||||
boolean configureAll;
|
||||
}
|
||||
|
||||
|
@ -35,8 +38,6 @@ public class ReadPreparationExchange implements MessageExchange {
|
|||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
String determinedType;
|
||||
|
||||
@NonNull
|
||||
DialogReference config;
|
||||
}
|
|
@ -28,7 +28,7 @@ public class ConvertExchange implements MessageExchange {
|
|||
|
||||
String newProvider;
|
||||
|
||||
DataSourceType newType;
|
||||
DataSourceType newCategory;
|
||||
|
||||
DataSourceId copyId;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.beacon.RequestMessage;
|
|||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.core.dialog.DialogElement;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
|
@ -31,8 +32,10 @@ public class DialogExchange implements MessageExchange {
|
|||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
@NonNull
|
||||
UUID dialogKey;
|
||||
String value;
|
||||
boolean cancel;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
|
|
@ -9,6 +9,8 @@ import lombok.NonNull;
|
|||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Output the data source contents.
|
||||
*/
|
||||
|
@ -25,6 +27,9 @@ public class WriteExecuteExchange implements MessageExchange {
|
|||
public static class Request implements RequestMessage {
|
||||
@NonNull
|
||||
DataSourceReference ref;
|
||||
|
||||
@NonNull
|
||||
UUID id;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.beacon.exchange.cli;
|
||||
|
||||
import io.xpipe.beacon.exchange.MessageExchange;
|
||||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.beacon.exchange.MessageExchange;
|
||||
import io.xpipe.core.dialog.DialogReference;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
@ -18,7 +18,7 @@ public class WritePreparationExchange implements MessageExchange {
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "writePreparation";
|
||||
return "write";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
@ -27,15 +27,17 @@ public class WritePreparationExchange implements MessageExchange {
|
|||
public static class Request implements RequestMessage {
|
||||
String type;
|
||||
@NonNull
|
||||
StreamDataStore output;
|
||||
DataStore output;
|
||||
@NonNull
|
||||
DataSourceReference ref;
|
||||
DataSourceReference source;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
boolean hasBody;
|
||||
|
||||
@NonNull
|
||||
DialogReference config;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ module io.xpipe.beacon {
|
|||
WritePreparationExchange,
|
||||
WriteExecuteExchange,
|
||||
SelectExchange,
|
||||
ReadPreparationExchange,
|
||||
ReadExchange,
|
||||
QueryTextDataExchange,
|
||||
ReadExecuteExchange,
|
||||
ListStoresExchange,
|
||||
|
|
|
@ -2,5 +2,11 @@ plugins {
|
|||
id 'org.jreleaser' version '1.0.0'
|
||||
}
|
||||
|
||||
if (project == rootProject) {
|
||||
plugins {
|
||||
id "io.codearte.nexus-staging" version "0.30.0"
|
||||
}
|
||||
}
|
||||
|
||||
version file('misc/version').text
|
||||
apply from: 'jreleaser.gradle'
|
|
@ -2,28 +2,39 @@ package io.xpipe.core.dialog;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@JsonTypeName("query")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
@Getter
|
||||
public class BaseQueryElement extends DialogElement {
|
||||
|
||||
private final String description;
|
||||
private final boolean newLine;
|
||||
private final boolean required;
|
||||
private final boolean hidden;
|
||||
private final boolean secret;
|
||||
private final boolean quiet;
|
||||
protected String value;
|
||||
|
||||
@JsonCreator
|
||||
public BaseQueryElement(String description, boolean newLine, boolean required, boolean hidden, String value) {
|
||||
public BaseQueryElement(String description, boolean newLine, boolean required, boolean secret, boolean quiet, String value) {
|
||||
this.description = description;
|
||||
this.newLine = newLine;
|
||||
this.required = required;
|
||||
this.hidden = hidden;
|
||||
this.secret = secret;
|
||||
this.quiet = quiet;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
package io.xpipe.core.dialog;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@JsonTypeName("busy")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
public class BusyElement extends DialogElement {
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return "busy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(String value) {
|
||||
return true;
|
||||
|
|
|
@ -12,4 +12,23 @@ import lombok.extern.jackson.Jacksonized;
|
|||
public class Choice {
|
||||
Character character;
|
||||
String description;
|
||||
boolean disabled;
|
||||
|
||||
public Choice(String description) {
|
||||
this.description = description;
|
||||
this.character = null;
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
public Choice(String description, boolean disabled) {
|
||||
this.character = null;
|
||||
this.description = description;
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public Choice(Character character, String description) {
|
||||
this.character = character;
|
||||
this.description = description;
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,47 +2,67 @@ package io.xpipe.core.dialog;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonTypeName("choice")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
public class ChoiceElement extends DialogElement {
|
||||
|
||||
private final String description;
|
||||
private final List<Choice> elements;
|
||||
private final boolean required;
|
||||
|
||||
private int selected;
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(String value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.length() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var c = value.charAt(0);
|
||||
if (Character.isDigit(c)) {
|
||||
selected = Integer.parseInt(value) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
if (elements.get(i).getCharacter() != null && elements.get(i).getCharacter().equals(c)) {
|
||||
selected = i;
|
||||
if (value.length() == 1) {
|
||||
var c = value.charAt(0);
|
||||
if (Character.isDigit(c)) {
|
||||
selected = Integer.parseInt(value) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
if (elements.get(i).getCharacter() != null && elements.get(i).getCharacter().equals(c)) {
|
||||
selected = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
if (elements.get(i).getDescription().equalsIgnoreCase(value)) {
|
||||
selected = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public ChoiceElement(String description, List<Choice> elements, int selected) {
|
||||
public ChoiceElement(String description, List<Choice> elements, boolean required, int selected) {
|
||||
if (elements.stream().allMatch(Choice::isDisabled)) {
|
||||
throw new IllegalArgumentException("All choices are disabled");
|
||||
}
|
||||
|
||||
this.description = description;
|
||||
this.elements = elements;
|
||||
this.required = required;
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
|
@ -57,4 +77,8 @@ public class ChoiceElement extends DialogElement {
|
|||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,13 @@ public abstract class Dialog {
|
|||
return new Dialog() {
|
||||
@Override
|
||||
public DialogElement start() throws Exception {
|
||||
complete();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DialogElement next(String answer) throws Exception {
|
||||
complete();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -28,8 +30,8 @@ public abstract class Dialog {
|
|||
|
||||
private final ChoiceElement element;
|
||||
|
||||
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, int selected) {
|
||||
this.element = new ChoiceElement(description, elements, selected);
|
||||
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
||||
this.element = new ChoiceElement(description, elements, required, selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,18 +53,27 @@ public abstract class Dialog {
|
|||
}
|
||||
}
|
||||
|
||||
public static Dialog.Choice choice(String description, List<io.xpipe.core.dialog.Choice> elements, int selected) {
|
||||
Dialog.Choice c = new Dialog.Choice(description, elements, selected);
|
||||
public static Dialog.Choice choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
||||
Dialog.Choice c = new Dialog.Choice(description, elements, required, selected);
|
||||
c.evaluateTo(c::getSelected);
|
||||
return c;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Dialog.Choice choice(String description, Function<T, String> toString, T def, T... vals) {
|
||||
public static <T> Dialog.Choice choice(String description, Function<T, String> toString, boolean required, T def, T... vals) {
|
||||
var elements = Arrays.stream(vals).map(v -> new io.xpipe.core.dialog.Choice(null, toString.apply(v))).toList();
|
||||
var index = Arrays.asList(vals).indexOf(def);
|
||||
var c = choice(description, elements, index);
|
||||
c.evaluateTo(() -> vals[c.getSelected()]);
|
||||
if (def != null && index == -1) {
|
||||
throw new IllegalArgumentException("Default value " + def.toString() + " is not in possible values");
|
||||
}
|
||||
|
||||
var c = choice(description, elements, required, index);
|
||||
c.evaluateTo(() -> {
|
||||
if (c.getSelected() == -1) {
|
||||
return null;
|
||||
}
|
||||
return vals[c.getSelected()];
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
|
@ -70,8 +81,8 @@ public abstract class Dialog {
|
|||
|
||||
private final QueryElement element;
|
||||
|
||||
private Query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean hidden) {
|
||||
this.element = new QueryElement(description, newLine, required,value, converter, hidden);
|
||||
private <T> Query(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter, boolean hidden) {
|
||||
this.element = new QueryElement(description, newLine, required, quiet, value, converter, hidden);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,13 +109,13 @@ public abstract class Dialog {
|
|||
}
|
||||
}
|
||||
|
||||
public static Dialog.Query query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter) {
|
||||
var q = new Dialog.Query(description, newLine, required, value, converter, false);
|
||||
public static <T> Dialog.Query query(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter) {
|
||||
var q = new <T>Dialog.Query(description, newLine, required, quiet, value, converter, false);
|
||||
q.evaluateTo(q::getConvertedValue);
|
||||
return q;
|
||||
}
|
||||
public static Dialog.Query querySecret(String description, boolean newLine, boolean required, Secret value) {
|
||||
var q = new Dialog.Query(description, newLine, required, value, QueryConverter.SECRET, true);
|
||||
var q = new Dialog.Query(description, newLine, required, false, value, QueryConverter.SECRET, true);
|
||||
q.evaluateTo(q::getConvertedValue);
|
||||
return q;
|
||||
}
|
||||
|
@ -118,7 +129,16 @@ public abstract class Dialog {
|
|||
public DialogElement start() throws Exception {
|
||||
current = 0;
|
||||
eval = null;
|
||||
return ds[0].start();
|
||||
DialogElement start;
|
||||
do {
|
||||
start = ds[current].start();
|
||||
if (start != null) {
|
||||
return start;
|
||||
}
|
||||
} while (++current < ds.length);
|
||||
|
||||
current = ds.length - 1;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -127,7 +147,6 @@ public abstract class Dialog {
|
|||
if (currentElement == null) {
|
||||
DialogElement next = null;
|
||||
while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
|
||||
|
||||
};
|
||||
return next;
|
||||
}
|
||||
|
@ -200,8 +219,9 @@ public abstract class Dialog {
|
|||
public DialogElement start() throws Exception {
|
||||
eval = null;
|
||||
dialog = d.get();
|
||||
var start = dialog.start();
|
||||
evaluateTo(dialog);
|
||||
return dialog.start();
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -283,12 +303,16 @@ public abstract class Dialog {
|
|||
}.evaluateTo(d.evaluation).onCompletion(d.completion);
|
||||
}
|
||||
|
||||
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, int selected, Function<Integer, Dialog> c) {
|
||||
var choice = new ChoiceElement(description, elements, selected);
|
||||
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected, Function<Integer, Dialog> c) {
|
||||
var choice = new ChoiceElement(description, elements, required, selected);
|
||||
return new Dialog() {
|
||||
|
||||
private Dialog choiceMade;
|
||||
|
||||
{
|
||||
evaluateTo(() -> choiceMade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialogElement start() throws Exception {
|
||||
choiceMade = null;
|
||||
|
@ -310,7 +334,7 @@ public abstract class Dialog {
|
|||
|
||||
return choice;
|
||||
}
|
||||
}.evaluateTo(() -> choice.getSelected());
|
||||
};
|
||||
}
|
||||
|
||||
protected Object eval;
|
||||
|
@ -324,7 +348,7 @@ public abstract class Dialog {
|
|||
}
|
||||
|
||||
public Dialog evaluateTo(Dialog d) {
|
||||
evaluation = d.evaluation;
|
||||
evaluation = () -> d.evaluation.get();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package io.xpipe.core.dialog;
|
||||
|
||||
public class DialogCancelException extends Exception {
|
||||
|
||||
public DialogCancelException() {
|
||||
}
|
||||
|
||||
public DialogCancelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DialogCancelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DialogCancelException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DialogCancelException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
|
@ -2,10 +2,12 @@ package io.xpipe.core.dialog;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public abstract class DialogElement {
|
||||
|
||||
|
@ -15,6 +17,8 @@ public abstract class DialogElement {
|
|||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public abstract String toDisplayString();
|
||||
|
||||
public boolean apply(String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
package io.xpipe.core.dialog;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@AllArgsConstructor
|
||||
public class DialogReference {
|
||||
|
||||
@NonNull
|
||||
UUID dialogId;
|
||||
|
||||
DialogElement start;
|
||||
|
||||
@JsonCreator
|
||||
public DialogReference(UUID dialogId, DialogElement start) {
|
||||
this.dialogId = dialogId;
|
||||
this.start = start;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@ package io.xpipe.core.dialog;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@JsonTypeName("header")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
public class HeaderElement extends DialogElement {
|
||||
|
||||
protected String header;
|
||||
|
@ -13,6 +17,11 @@ public class HeaderElement extends DialogElement {
|
|||
this.header = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(String value) {
|
||||
return true;
|
||||
|
|
|
@ -37,12 +37,12 @@ public abstract class QueryConverter<T> {
|
|||
public static final QueryConverter<Secret> SECRET = new QueryConverter<Secret>() {
|
||||
@Override
|
||||
protected Secret fromString(String s) {
|
||||
return Secret.parse(s);
|
||||
return new Secret(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Secret value) {
|
||||
return value.getValue();
|
||||
return value.getEncryptedValue();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ public class QueryElement extends BaseQueryElement {
|
|||
|
||||
private final QueryConverter<?> converter;
|
||||
|
||||
public QueryElement(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean hidden) {
|
||||
super(description, newLine, required, hidden, value != null ? value.toString() : null);
|
||||
public <T> QueryElement(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter, boolean hidden) {
|
||||
super(description, newLine, required, hidden, quiet, value != null ? converter.toString(value) : null);
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
|||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Value
|
||||
|
@ -27,6 +29,16 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
|||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInput() throws Exception {
|
||||
return machine.openInput(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutput() throws Exception {
|
||||
return machine.openOutput(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOpen() {
|
||||
return machine.exists(file);
|
||||
|
@ -44,6 +56,6 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
|||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return file;
|
||||
return Path.of(file).getFileName().toString();
|
||||
}
|
||||
}
|
||||
|
|
50
core/src/main/java/io/xpipe/core/store/NamedStore.java
Normal file
50
core/src/main/java/io/xpipe/core/store/NamedStore.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
@JsonTypeName("named")
|
||||
public final class NamedStore implements DataStore {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
|
||||
@JsonCreator
|
||||
public NamedStore(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDisplay() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <DS extends DataStore> DS asNeeded() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> determineDefaultName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Instant> determineLastModified() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -9,6 +10,7 @@ import java.io.OutputStream;
|
|||
|
||||
@EqualsAndHashCode
|
||||
@Value
|
||||
@JsonTypeName("stdin")
|
||||
public class StdinDataStore implements StreamDataStore {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -8,6 +9,7 @@ import java.io.OutputStream;
|
|||
|
||||
@EqualsAndHashCode
|
||||
@Value
|
||||
@JsonTypeName("stdout")
|
||||
public class StdoutDataStore implements StreamDataStore {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
@ -9,6 +10,10 @@ import java.io.OutputStream;
|
|||
*/
|
||||
public interface StreamDataStore extends DataStore {
|
||||
|
||||
default boolean isLocalOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an input stream. This input stream does not necessarily have to be a new instance.
|
||||
*/
|
||||
|
@ -16,6 +21,15 @@ public interface StreamDataStore extends DataStore {
|
|||
throw new UnsupportedOperationException("Can't open store input");
|
||||
}
|
||||
|
||||
default InputStream openBufferedInput() throws Exception {
|
||||
var in = openInput();
|
||||
if (in.markSupported()) {
|
||||
return in;
|
||||
}
|
||||
|
||||
return new BufferedInputStream(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an output stream. This output stream does not necessarily have to be a new instance.
|
||||
*/
|
||||
|
|
31
core/src/main/java/io/xpipe/core/store/StringStore.java
Normal file
31
core/src/main/java/io/xpipe/core/store/StringStore.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Value
|
||||
@JsonTypeName("string")
|
||||
@AllArgsConstructor
|
||||
public class StringStore implements StreamDataStore {
|
||||
|
||||
byte[] value;
|
||||
|
||||
public StringStore(String s) {
|
||||
value = s.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInput() throws Exception {
|
||||
return new ByteArrayInputStream(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDisplay() {
|
||||
return "string";
|
||||
}
|
||||
}
|
|
@ -21,10 +21,7 @@ import io.xpipe.core.dialog.ChoiceElement;
|
|||
import io.xpipe.core.dialog.HeaderElement;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.CollectionEntryDataStore;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.LocalDirectoryDataStore;
|
||||
import io.xpipe.core.store.LocalMachineStore;
|
||||
import io.xpipe.core.store.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -36,21 +33,28 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
public void setupModule(SetupContext context) {
|
||||
context.registerSubtypes(
|
||||
new NamedType(FileStore.class),
|
||||
new NamedType(StdinDataStore.class),
|
||||
new NamedType(StdoutDataStore.class),
|
||||
new NamedType(LocalDirectoryDataStore.class),
|
||||
new NamedType(CollectionEntryDataStore.class),
|
||||
new NamedType(StringStore.class),
|
||||
new NamedType(LocalMachineStore.class),
|
||||
new NamedType(NamedStore.class),
|
||||
|
||||
new NamedType(ValueType.class),
|
||||
new NamedType(TupleType.class),
|
||||
new NamedType(ArrayType.class),
|
||||
new NamedType(WildcardType.class),
|
||||
|
||||
new NamedType(DataSourceInfo.Table.class),
|
||||
new NamedType(DataSourceInfo.Structure.class),
|
||||
new NamedType(DataSourceInfo.Text.class),
|
||||
new NamedType(DataSourceInfo.Collection.class),
|
||||
new NamedType(DataSourceInfo.Raw.class),
|
||||
|
||||
new NamedType(BaseQueryElement.class),
|
||||
new NamedType(ChoiceElement.class),
|
||||
new NamedType(BusyElement.class),
|
||||
new NamedType(LocalMachineStore.class),
|
||||
new NamedType(HeaderElement.class)
|
||||
);
|
||||
|
||||
|
@ -129,7 +133,7 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
@Override
|
||||
public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
jgen.writeString(value.getValue());
|
||||
jgen.writeString(value.getEncryptedValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +141,7 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
|
||||
@Override
|
||||
public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Secret.parse(p.getValueAsString());
|
||||
return Secret.create(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,15 @@ import java.util.Base64;
|
|||
@EqualsAndHashCode
|
||||
public class Secret {
|
||||
|
||||
public static Secret parse(String s) {
|
||||
public static Secret create(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (s.length() < 2) {
|
||||
return new Secret(s);
|
||||
}
|
||||
|
||||
return new Secret(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
|
@ -20,11 +28,15 @@ public class Secret {
|
|||
return "*".repeat(value.length());
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
public String getEncryptedValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
public String getSecretValue() {
|
||||
if (value.length() < 2) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
|
2
deps
2
deps
|
@ -1 +1 @@
|
|||
Subproject commit e2af7576c19161cdbb08d64b498b7e6c9ae7e4c6
|
||||
Subproject commit 49a1ad06bc6872f72c1d20ea864d24f3df59b7c5
|
|
@ -31,9 +31,9 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
||||
implementation project(':core')
|
||||
|
||||
implementation 'io.xpipe:fxcomps:0.1'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'io.xpipe:fxcomps:0.2'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.1'
|
||||
}
|
||||
|
|
|
@ -8,10 +8,8 @@ import io.xpipe.core.source.DataSourceType;
|
|||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -22,6 +20,11 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
DATABASE;
|
||||
}
|
||||
|
||||
default void validate() throws Exception {
|
||||
getGeneralType();
|
||||
getSourceClass();
|
||||
}
|
||||
|
||||
default GeneralType getGeneralType() {
|
||||
if (getFileProvider() != null) {
|
||||
return GeneralType.FILE;
|
||||
|
@ -34,12 +37,6 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
throw new ExtensionException("Provider has no general type");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
default T createDefault() {
|
||||
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance();
|
||||
}
|
||||
|
||||
default boolean supportsConversion(T in, DataSourceType t) {
|
||||
return false;
|
||||
}
|
||||
|
@ -90,12 +87,16 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
|
||||
}
|
||||
|
||||
public static Dialog charset(Charset c) {
|
||||
return Dialog.query("charset", false, false, c, QueryConverter.CHARSET);
|
||||
public static Dialog charset(Charset c, boolean all) {
|
||||
return Dialog.query("charset", false, false, c != null &&!all, c, QueryConverter.CHARSET);
|
||||
}
|
||||
|
||||
public static Dialog newLine(NewLine l) {
|
||||
return Dialog.query("newline", false, false, l, NEW_LINE_CONVERTER);
|
||||
public static Dialog newLine(NewLine l, boolean all) {
|
||||
return Dialog.query("newline", false, false, l != null &&!all, l, NEW_LINE_CONVERTER);
|
||||
}
|
||||
|
||||
static <T> Dialog query(String desc, T value, boolean required, QueryConverter<T> c, boolean all) {
|
||||
return Dialog.query(desc, false, required, value != null && !all, value, c);
|
||||
}
|
||||
|
||||
public static final QueryConverter<NewLine> NEW_LINE_CONVERTER = new QueryConverter<NewLine>() {
|
||||
|
@ -174,12 +175,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
return createDefaultSource(input);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Class<T> getSourceClass() {
|
||||
return (Class<T>) Arrays.stream(getClass().getDeclaredClasses())
|
||||
.filter(c -> c.getName().endsWith("Source")).findFirst()
|
||||
.orElseThrow(() -> new ExtensionException("Descriptor class not found for " + getId()));
|
||||
}
|
||||
Class<T> getSourceClass();
|
||||
|
||||
|
||||
List<String> getPossibleNames();
|
||||
|
|
|
@ -19,11 +19,14 @@ public class DataSourceProviders {
|
|||
if (ALL == null) {
|
||||
ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream()
|
||||
.map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet());
|
||||
ALL.forEach(p -> {
|
||||
ALL.removeIf(p -> {
|
||||
try {
|
||||
p.init();
|
||||
p.validate();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,12 +2,32 @@ package io.xpipe.extension;
|
|||
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
public interface DataStoreProvider {
|
||||
|
||||
enum Category {
|
||||
STREAM,
|
||||
DATABASE;
|
||||
}
|
||||
|
||||
default Category getCategory() {
|
||||
if (StreamDataStore.class.isAssignableFrom(getStoreClasses().get(0))) {
|
||||
return Category.STREAM;
|
||||
}
|
||||
|
||||
throw new ExtensionException("Provider " + getId() + " has no set category");
|
||||
}
|
||||
|
||||
default Region createConfigGui(Property<DataStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default void init() throws Exception {
|
||||
}
|
||||
|
||||
|
@ -34,17 +54,17 @@ public interface DataStoreProvider {
|
|||
}
|
||||
|
||||
default String getDisplayIconFileName() {
|
||||
return getModuleName() + ":" + getId() + ".png";
|
||||
}
|
||||
|
||||
default Dialog dialogForURL(URL url) {
|
||||
return null;
|
||||
return getModuleName() + ":" + getId() + "_icon.png";
|
||||
}
|
||||
|
||||
default Dialog dialogForString(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Dialog dialogForURI(URI uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Dialog defaultDialog();
|
||||
|
||||
default String display(DataStore store) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.extension;
|
|||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
|
@ -37,12 +37,12 @@ public class DataStoreProviders {
|
|||
.anyMatch(s -> s.equalsIgnoreCase(name))).findAny();
|
||||
}
|
||||
|
||||
public static Optional<Dialog> byURL(URL url) {
|
||||
public static Optional<Dialog> byURI(URI uri) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream().map(d -> d.dialogForURL(url)).filter(Objects::nonNull).findAny();
|
||||
return ALL.stream().map(d -> d.dialogForURI(uri)).filter(Objects::nonNull).findAny();
|
||||
}
|
||||
|
||||
public static Optional<Dialog> byString(String s) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
@ -11,6 +12,16 @@ import java.util.Map;
|
|||
|
||||
public interface SimpleFileDataSourceProvider<T extends DataSource<?>> extends DataSourceProvider<T> {
|
||||
|
||||
@Override
|
||||
default boolean supportsConversion(T in, DataSourceType t) {
|
||||
return t == DataSourceType.RAW;
|
||||
}
|
||||
|
||||
@Override
|
||||
default DataSource<?> convert(T in, DataSourceType t) throws Exception {
|
||||
return DataSourceProviders.byId("binary").createDefaultSource(in.getStore());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean prefersStore(DataStore store) {
|
||||
for (var e : getSupportedExtensions().entrySet()) {
|
||||
|
|
|
@ -12,7 +12,7 @@ public interface SupportedApplicationProvider {
|
|||
APPLICATION
|
||||
}
|
||||
|
||||
Region createRetrieveInstructions(DataSourceProvider<?> provider, ObservableValue<String> id);
|
||||
Region createRetrieveInstructions(ObservableValue<String> id);
|
||||
|
||||
String getId();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.extension.comp;
|
|||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -45,6 +46,6 @@ public class CharChoiceComp extends Comp<CompStructure<HBox>> {
|
|||
box.setAlignment(Pos.CENTER);
|
||||
choiceR.prefWidthProperty().bind(box.widthProperty().subtract(charChoiceR.widthProperty()));
|
||||
box.getStyleClass().add("char-choice-comp");
|
||||
return new CompStructure<>(box);
|
||||
return new SimpleCompStructure<>(box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
|
|||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
|
@ -24,10 +25,10 @@ public class CharComp extends Comp<CompStructure<TextField>> {
|
|||
value.setValue(n != null && n.length() > 0 ? n.charAt(0) : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformUtil.runLaterIfNeeded(() -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
text.setText(n != null ? n.toString() : null);
|
||||
});
|
||||
});
|
||||
return new CompStructure<>(text);
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -28,7 +30,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
@Override
|
||||
public String toString(T object) {
|
||||
if (object == null) {
|
||||
return "null";
|
||||
return I18n.get("extension.none");
|
||||
}
|
||||
|
||||
return range.get(object).getValue();
|
||||
|
@ -39,8 +41,8 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
PlatformUtil.connect(value, cb.valueProperty());
|
||||
PlatformThread.connect(value, cb.valueProperty());
|
||||
cb.getStyleClass().add("choice-comp");
|
||||
return new CompStructure<>(cb);
|
||||
return new SimpleCompStructure<>(cb);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Value
|
||||
public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
||||
|
||||
List<Entry> entries;
|
||||
Property<Entry> selected;
|
||||
Function<ComboBox<Entry>, Region> transformer = c -> c;
|
||||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var list = FXCollections.observableArrayList(entries);
|
||||
var cb = new ComboBox<Entry>(list);
|
||||
cb.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Entry object) {
|
||||
if (object == null) {
|
||||
return I18n.get("extension.none");
|
||||
}
|
||||
|
||||
return object.name().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
var vbox = new VBox(transformer.apply(cb));
|
||||
vbox.setFillWidth(true);
|
||||
cb.prefWidthProperty().bind(vbox.widthProperty());
|
||||
cb.valueProperty().addListener((c,o,n) -> {
|
||||
if (n == null) {
|
||||
vbox.getChildren().remove(1);
|
||||
} else {
|
||||
if (vbox.getChildren().size() == 1) {
|
||||
vbox.getChildren().add(n.comp().createRegion());
|
||||
} else {
|
||||
vbox.getChildren().set(1, n.comp().createRegion());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
PlatformThread.connect(selected, cb.valueProperty());
|
||||
vbox.getStyleClass().add("choice-pane-comp");
|
||||
|
||||
return new SimpleCompStructure<>(vbox);
|
||||
}
|
||||
|
||||
public static record Entry(ObservableValue<String> name, Comp<?> comp) {
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
|
|||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -26,12 +27,12 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
|||
|
||||
public CodeSnippetComp(boolean showLineNumbers, ObservableValue<CodeSnippet> value) {
|
||||
this.showLineNumbers = new SimpleBooleanProperty(showLineNumbers);
|
||||
this.value = PlatformUtil.wrap(value);
|
||||
this.value = PlatformThread.sync(value);
|
||||
}
|
||||
|
||||
public CodeSnippetComp(ObservableValue<Boolean> showLineNumbers, ObservableValue<CodeSnippet> value) {
|
||||
this.showLineNumbers = PlatformUtil.wrap(showLineNumbers);
|
||||
this.value = PlatformUtil.wrap(value);
|
||||
this.showLineNumbers = PlatformThread.sync(showLineNumbers);
|
||||
this.value = PlatformThread.sync(value);
|
||||
}
|
||||
|
||||
private static String toRGBCode(Color color) {
|
||||
|
@ -131,6 +132,6 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
|||
AnchorPane.setRightAnchor(copyButton, 10.0);
|
||||
c.getChildren().add(pane);
|
||||
|
||||
return new CompStructure<>(c);
|
||||
return new SimpleCompStructure<>(c);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.util.Secret;
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
||||
|
||||
|
@ -20,18 +17,36 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DynamicOptionsBuilder<T extends DataSource<?>> {
|
||||
public class DynamicOptionsBuilder<T> {
|
||||
|
||||
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
|
||||
private final List<Property<?>> props = new ArrayList<>();
|
||||
|
||||
private final ObservableValue<String> title;
|
||||
private final boolean wrap;
|
||||
|
||||
public DynamicOptionsBuilder() {
|
||||
this.wrap = true;
|
||||
this.title = null;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder(boolean wrap) {
|
||||
this.wrap = wrap;
|
||||
this.title = null;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder(ObservableValue<String> title) {
|
||||
this.wrap = false;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addNewLine(Property<NewLine> prop) {
|
||||
var map = new LinkedHashMap<NewLine, ObservableValue<String>>();
|
||||
for (var e : NewLine.values()) {
|
||||
map.put(e, new SimpleStringProperty(e.getId()));
|
||||
map.put(e, I18n.observable("extension." + e.getId()));
|
||||
}
|
||||
var comp = new ChoiceComp<>(prop, new DualLinkedHashBidiMap<>(map));
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("newLine"), comp));
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
@ -59,29 +74,50 @@ public class DynamicOptionsBuilder<T extends DataSource<?>> {
|
|||
|
||||
public DynamicOptionsBuilder<T> addCharset(Property<Charset> prop) {
|
||||
var comp = new CharsetChoiceComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("charset"), comp));
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.charset"), comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
|
||||
var comp = Comp.of(() -> {
|
||||
var tf = new TextField(prop.getValue());
|
||||
tf.textProperty().addListener((c, o, n) -> {
|
||||
prop.setValue(n.length() > 0 ? n : null);
|
||||
});
|
||||
return tf;
|
||||
});
|
||||
var comp = new TextFieldComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Region build(Function<T, T> creator, Property<T> toBind) {
|
||||
var bind = Bindings.createObjectBinding(() -> creator.apply(toBind.getValue()), props.toArray(Observable[]::new));
|
||||
bind.addListener((c,o, n) -> {
|
||||
toBind.setValue(n);
|
||||
public DynamicOptionsBuilder<T> addComp(Comp<?> comp) {
|
||||
entries.add(new DynamicOptionsComp.Entry(null, comp));
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addSecret(ObservableValue<String> name, Property<Secret> prop) {
|
||||
var comp = new SecretFieldComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addInteger(ObservableValue<String> name, Property<Integer> prop) {
|
||||
var comp = new IntFieldComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <V extends T> Comp<?> buildComp(Function<T, V> creator, Property<T> toSet) {
|
||||
props.forEach(prop -> {
|
||||
prop.addListener((c,o,n) -> {
|
||||
toSet.setValue(creator.apply(toSet.getValue()));
|
||||
});
|
||||
});
|
||||
return new DynamicOptionsComp(entries).createRegion();
|
||||
if (title != null) {
|
||||
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
||||
}
|
||||
return new DynamicOptionsComp(entries, wrap);
|
||||
}
|
||||
|
||||
public <V extends T> Region build(Function<T, V> creator, Property<T> toSet) {
|
||||
return buildComp(creator, toSet).createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.extension.comp;
|
|||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -10,6 +11,7 @@ import javafx.geometry.Pos;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -18,9 +20,11 @@ import java.util.List;
|
|||
public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
||||
|
||||
private final List<Entry> entries;
|
||||
private final boolean wrap;
|
||||
|
||||
public DynamicOptionsComp(List<Entry> entries) {
|
||||
public DynamicOptionsComp(List<Entry> entries, boolean wrap) {
|
||||
this.entries = entries;
|
||||
this.wrap = wrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,32 +39,42 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
|||
|
||||
for (var entry : getEntries()) {
|
||||
var line = new HBox();
|
||||
line.setSpacing(5);
|
||||
if (!wrap) {
|
||||
line.prefWidthProperty().bind(flow.widthProperty());
|
||||
}
|
||||
line.setSpacing(8);
|
||||
|
||||
var name = new Label();
|
||||
name.textProperty().bind(entry.name());
|
||||
name.prefHeightProperty().bind(line.heightProperty());
|
||||
name.setMinWidth(Region.USE_PREF_SIZE);
|
||||
name.setAlignment(Pos.CENTER_LEFT);
|
||||
nameRegions.add(name);
|
||||
line.getChildren().add(name);
|
||||
if (entry.name() != null) {
|
||||
var name = new Label();
|
||||
name.textProperty().bind(entry.name());
|
||||
name.prefHeightProperty().bind(line.heightProperty());
|
||||
name.setMinWidth(Region.USE_PREF_SIZE);
|
||||
name.setAlignment(Pos.CENTER_LEFT);
|
||||
nameRegions.add(name);
|
||||
line.getChildren().add(name);
|
||||
}
|
||||
|
||||
var r = entry.comp().createRegion();
|
||||
compRegions.add(r);
|
||||
line.getChildren().add(r);
|
||||
if (!wrap) {
|
||||
HBox.setHgrow(r, Priority.ALWAYS);
|
||||
}
|
||||
|
||||
flow.getChildren().add(line);
|
||||
}
|
||||
|
||||
var compWidthBinding = Bindings.createDoubleBinding(() -> {
|
||||
if (compRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
if (wrap) {
|
||||
var compWidthBinding = Bindings.createDoubleBinding(() -> {
|
||||
if (compRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
var m = compRegions.stream().map(Region::getWidth).max(Double::compareTo).orElse(0.0);
|
||||
return m;
|
||||
}, compRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
compRegions.forEach(r -> r.prefWidthProperty().bind(compWidthBinding));
|
||||
var m = compRegions.stream().map(Region::getWidth).max(Double::compareTo).orElse(0.0);
|
||||
return m;
|
||||
}, compRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
compRegions.forEach(r -> r.prefWidthProperty().bind(compWidthBinding));
|
||||
}
|
||||
|
||||
var nameWidthBinding = Bindings.createDoubleBinding(() -> {
|
||||
if (nameRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
|
@ -72,7 +86,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
|||
}, nameRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding));
|
||||
|
||||
return new CompStructure<>(flow);
|
||||
return new SimpleCompStructure<>(flow);
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
Property<Integer> value;
|
||||
int minValue;
|
||||
int maxValue;
|
||||
|
||||
public IntFieldComp(Property<Integer> value) {
|
||||
this.value = value;
|
||||
this.minValue = 0;
|
||||
this.maxValue = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public IntFieldComp(Property<Integer> value, int minValue, int maxValue) {
|
||||
this.value = value;
|
||||
this.minValue = minValue;
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var text = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
|
||||
value.addListener((ChangeListener<Number>) (observableValue, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (newValue == null) {
|
||||
text.setText("");
|
||||
} else {
|
||||
if (newValue.intValue() < minValue) {
|
||||
value.setValue(minValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() > maxValue) {
|
||||
value.setValue(maxValue);
|
||||
return;
|
||||
}
|
||||
|
||||
text.setText(newValue.toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
text.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
|
||||
if (minValue < 0) {
|
||||
if (!"-0123456789".contains(keyEvent.getCharacter())) {
|
||||
keyEvent.consume();
|
||||
}
|
||||
} else {
|
||||
if (!"0123456789".contains(keyEvent.getCharacter())) {
|
||||
keyEvent.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
text.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
if (newValue == null || "".equals(newValue) || (minValue < 0 && "-".equals(newValue))) {
|
||||
value.setValue(0);
|
||||
return;
|
||||
}
|
||||
|
||||
int intValue = Integer.parseInt(newValue);
|
||||
if (minValue > intValue || intValue > maxValue) {
|
||||
text.textProperty().setValue(oldValue);
|
||||
}
|
||||
|
||||
value.setValue(Integer.parseInt(text.textProperty().get()));
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.core.util.Secret;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
private final Property<Secret> value;
|
||||
|
||||
public SecretFieldComp(Property<Secret> value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var text = new PasswordField();
|
||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n.length() > 0 ? Secret.create(n) : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
text.setText(n != null ? n.getSecretValue() : null);
|
||||
});
|
||||
});
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
|
||||
|
||||
@Override
|
||||
public CompStructure<JFXTabPane> createBase() {
|
||||
JFXTabPane tabPane = new JFXTabPane();
|
||||
tabPane.getStyleClass().add("tab-pane-comp");
|
||||
|
||||
for (var e : entries) {
|
||||
Tab tab = new Tab();
|
||||
var ll = new Label(null, new FontIcon(e.graphic()));
|
||||
ll.textProperty().bind(e.name());
|
||||
ll.getStyleClass().add("name");
|
||||
ll.setAlignment(Pos.CENTER);
|
||||
tab.setGraphic(ll);
|
||||
var content = e.comp().createRegion();
|
||||
tab.setContent(content);
|
||||
tabPane.getTabs().add(tab);
|
||||
content.prefWidthProperty().bind(tabPane.widthProperty());
|
||||
}
|
||||
|
||||
return new SimpleCompStructure<>(tabPane);
|
||||
}
|
||||
|
||||
private final List<Entry> entries;
|
||||
|
||||
public TabPaneComp(List<Entry> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public static record Entry(ObservableValue<String> name, String graphic, Comp<?> comp) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
private final Property<String> value;
|
||||
|
||||
public TextFieldComp(Property<String> value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var text = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? n : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
text.setText(n);
|
||||
});
|
||||
});
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
|
|||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
|
@ -38,7 +39,7 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
|||
box.getChildren().add(b);
|
||||
b.setToggleGroup(group);
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformUtil.runLaterIfNeeded(() -> b.setSelected(entry.equals(n)));
|
||||
PlatformThread.runLaterIfNeeded(() -> b.setSelected(entry.equals(n)));
|
||||
});
|
||||
if (entry.getKey().equals(value.getValue())) {
|
||||
b.setSelected(true);
|
||||
|
@ -55,6 +56,6 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
|||
oldVal.setSelected(true);
|
||||
});
|
||||
|
||||
return new CompStructure<>(box);
|
||||
return new SimpleCompStructure<>(box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import lombok.Getter;
|
|||
import lombok.Singular;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
|
@ -49,10 +50,16 @@ public class ErrorEvent {
|
|||
private Throwable throwable;
|
||||
|
||||
@Singular
|
||||
private List<Path> diagnostics;
|
||||
|
||||
@Singular
|
||||
private List<Path> sensitiveDiagnostics;
|
||||
private List<Path> attachments;
|
||||
|
||||
private final List<TrackEvent> trackEvents = EventHandler.get().snapshotEvents();
|
||||
|
||||
public void addAttachment(Path file) {
|
||||
attachments = new ArrayList<>(attachments);
|
||||
attachments.add(file);
|
||||
}
|
||||
|
||||
public void clearAttachments() {
|
||||
attachments = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ public class ExceptionConverter {
|
|||
return I18n.get("classNotFound", msg);
|
||||
}
|
||||
|
||||
if (ex instanceof NullPointerException) {
|
||||
return I18n.get("extension.nullPointer", msg);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import lombok.Singular;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Builder
|
||||
|
@ -110,6 +111,9 @@ public class TrackEvent {
|
|||
@Singular
|
||||
private Map<String, Object> tags;
|
||||
|
||||
@Singular
|
||||
private List<String> elements;
|
||||
|
||||
public void handle() {
|
||||
EventHandler.get().handle(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
public class NamedCharacter {
|
||||
|
||||
public static QueryConverter<Character> converter(List<NamedCharacter> chars, boolean allowOthers) {
|
||||
return new QueryConverter<Character>() {
|
||||
@Override
|
||||
protected Character fromString(String s) {
|
||||
if (s.length() == 0) {
|
||||
throw new IllegalArgumentException("No character");
|
||||
}
|
||||
|
||||
var byName = chars.stream().filter(nc -> nc.getNames().stream()
|
||||
.anyMatch(n -> n.toLowerCase().contains(s.toLowerCase())))
|
||||
.findFirst().orElse(null);
|
||||
if (byName != null) {
|
||||
return byName.getCharacter();
|
||||
}
|
||||
|
||||
var byChar = chars.stream().filter(nc -> String.valueOf(nc.getCharacter()).equalsIgnoreCase(s))
|
||||
.findFirst().orElse(null);
|
||||
if (byChar != null) {
|
||||
return byChar.getCharacter();
|
||||
}
|
||||
|
||||
if (!allowOthers) {
|
||||
throw new IllegalArgumentException("Unknown character: " + s);
|
||||
}
|
||||
|
||||
return QueryConverter.CHARACTER.convertFromString(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Character value) {
|
||||
var byChar = chars.stream().filter(nc -> value.equals(nc.getCharacter()))
|
||||
.findFirst().orElse(null);
|
||||
if (byChar != null) {
|
||||
return byChar.getNames().get(0);
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
char character;
|
||||
List<String> names;
|
||||
String translationKey;
|
||||
}
|
|
@ -6,18 +6,18 @@ module io.xpipe.extension {
|
|||
exports io.xpipe.extension.comp;
|
||||
exports io.xpipe.extension.event;
|
||||
exports io.xpipe.extension.prefs;
|
||||
exports io.xpipe.extension.util;
|
||||
|
||||
requires transitive io.xpipe.core;
|
||||
requires transitive javafx.base;
|
||||
requires javafx.graphics;
|
||||
requires transitive javafx.controls;
|
||||
requires io.xpipe.fxcomps;
|
||||
requires org.apache.commons.collections4;
|
||||
requires static org.apache.commons.collections4;
|
||||
requires static lombok;
|
||||
requires static com.dlsc.preferencesfx;
|
||||
requires static com.dlsc.formsfx;
|
||||
requires static org.slf4j;
|
||||
requires static com.google.gson;
|
||||
requires static org.controlsfx.controls;
|
||||
requires java.desktop;
|
||||
requires org.fxmisc.richtext;
|
||||
|
@ -28,6 +28,7 @@ module io.xpipe.extension {
|
|||
requires org.kordamp.ikonli.javafx;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires static org.junit.jupiter.api;
|
||||
requires static com.jfoenix;
|
||||
|
||||
uses DataSourceProvider;
|
||||
uses SupportedApplicationProvider;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
displayName=Mein Dateiformat
|
||||
description=Meine Dateiformat-Beschreibung
|
||||
fileName=Mein Dateiformat Datei
|
|
@ -0,0 +1,6 @@
|
|||
charset=Charset
|
||||
newLine=Newline
|
||||
crlf=CRLF (Windows)
|
||||
lf=LF (Linux)
|
||||
none=None
|
||||
nullPointer=Null Pointer: $MSG$
|
Loading…
Reference in a new issue