Rework, add more comps, improve dialogs

This commit is contained in:
Christopher Schnick 2022-06-26 20:49:51 +02:00
parent b31ef8ed0e
commit f0f1417980
60 changed files with 967 additions and 177 deletions

View 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();
});
}
}

View file

@ -4,7 +4,7 @@ import io.xpipe.api.DataSource;
import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.connector.XPipeConnection; import io.xpipe.api.connector.XPipeConnection;
import io.xpipe.beacon.exchange.QueryDataSourceExchange; 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.beacon.exchange.StoreStreamExchange;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
@ -50,12 +50,12 @@ public abstract class DataSourceImpl implements DataSource {
var store = res.getStore(); var store = res.getStore();
var startReq = ReadPreparationExchange.Request.builder() var startReq = ReadExchange.Request.builder()
.provider(type) .provider(type)
.store(store) .store(store)
.build(); .build();
var startRes = XPipeConnection.execute(con -> { var startRes = XPipeConnection.execute(con -> {
ReadPreparationExchange.Response r = con.performSimpleExchange(startReq); ReadExchange.Response r = con.performSimpleExchange(startReq);
return r; return r;
}); });

View file

@ -5,7 +5,6 @@ import io.xpipe.api.DataTable;
import io.xpipe.api.DataTableAccumulator; import io.xpipe.api.DataTableAccumulator;
import io.xpipe.api.connector.XPipeConnection; import io.xpipe.api.connector.XPipeConnection;
import io.xpipe.api.util.TypeDescriptor; import io.xpipe.api.util.TypeDescriptor;
import io.xpipe.beacon.exchange.ReadExecuteExchange;
import io.xpipe.beacon.exchange.StoreStreamExchange; import io.xpipe.beacon.exchange.StoreStreamExchange;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.DataStructureNodeAcceptor; import io.xpipe.core.data.node.DataStructureNodeAcceptor;
@ -38,11 +37,11 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
StoreStreamExchange.Response res = connection.receiveResponse(); StoreStreamExchange.Response res = connection.receiveResponse();
connection.close(); connection.close();
var req = ReadExecuteExchange.Request.builder() // var req = ReadExecuteExchange.Request.builder()
.target(id).dataStore(res.getStore()).build(); // .target(id).dataStore(res.getStore()).build();
XPipeConnection.execute(con -> { // XPipeConnection.execute(con -> {
con.performSimpleExchange(req); // con.performSimpleExchange(req);
}); // });
return DataSource.get(DataSourceReference.id(id)).asTable(); return DataSource.get(DataSourceReference.id(id)).asTable();
} }

View 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());
}
}

View file

@ -126,7 +126,7 @@ public class BeaconClient implements AutoCloseable {
var in = socket.getInputStream(); var in = socket.getInputStream();
read = JacksonHelper.newMapper().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE).readTree(in); read = JacksonHelper.newMapper().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE).readTree(in);
} catch (SocketException ex) { } 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) { } catch (IOException ex) {
throw new ConnectorException("Couldn't read from socket", ex); throw new ConnectorException("Couldn't read from socket", ex);
} }

View file

@ -70,7 +70,7 @@ public abstract class BeaconConnection implements AutoCloseable {
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange( public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
REQ req, REQ req,
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) { BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed(); checkClosed();
performInputOutputExchange(req, null, responseConsumer); performInputOutputExchange(req, null, responseConsumer);
@ -79,7 +79,7 @@ public abstract class BeaconConnection implements AutoCloseable {
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputOutputExchange( public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputOutputExchange(
REQ req, REQ req,
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter, BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) { BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed(); checkClosed();
try { try {

View file

@ -5,7 +5,6 @@ import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo; import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.DataStore;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -35,13 +34,13 @@ public class QueryDataSourceExchange implements MessageExchange {
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
@NonNull
DataSourceId id; DataSourceId id;
boolean disabled;
@NonNull @NonNull
DataSourceInfo info; DataSourceInfo info;
@NonNull @NonNull
DataStore store; String storeDisplay;
@NonNull
String provider; String provider;
@NonNull @NonNull
Map<String, String> config; Map<String, String> config;

View file

@ -3,6 +3,7 @@ package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage; import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage; import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.dialog.DialogReference;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
@ -12,11 +13,11 @@ import lombok.extern.jackson.Jacksonized;
/** /**
* Prepares a client to send stream-based data to a daemon. * Prepares a client to send stream-based data to a daemon.
*/ */
public class ReadPreparationExchange implements MessageExchange { public class ReadExchange implements MessageExchange {
@Override @Override
public String getId() { public String getId() {
return "readPreparation"; return "read";
} }
@Jacksonized @Jacksonized
@ -28,6 +29,8 @@ public class ReadPreparationExchange implements MessageExchange {
@NonNull @NonNull
DataStore store; DataStore store;
DataSourceId target;
boolean configureAll; boolean configureAll;
} }
@ -35,8 +38,6 @@ public class ReadPreparationExchange implements MessageExchange {
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
String determinedType;
@NonNull @NonNull
DialogReference config; DialogReference config;
} }

View file

@ -28,7 +28,7 @@ public class ConvertExchange implements MessageExchange {
String newProvider; String newProvider;
DataSourceType newType; DataSourceType newCategory;
DataSourceId copyId; DataSourceId copyId;
} }

View file

@ -5,6 +5,7 @@ import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage; import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.dialog.DialogElement; import io.xpipe.core.dialog.DialogElement;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
@ -31,8 +32,10 @@ public class DialogExchange implements MessageExchange {
@Builder @Builder
@Value @Value
public static class Request implements RequestMessage { public static class Request implements RequestMessage {
@NonNull
UUID dialogKey; UUID dialogKey;
String value; String value;
boolean cancel;
} }
@Jacksonized @Jacksonized

View file

@ -9,6 +9,8 @@ import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
/** /**
* Output the data source contents. * Output the data source contents.
*/ */
@ -25,6 +27,9 @@ public class WriteExecuteExchange implements MessageExchange {
public static class Request implements RequestMessage { public static class Request implements RequestMessage {
@NonNull @NonNull
DataSourceReference ref; DataSourceReference ref;
@NonNull
UUID id;
} }
@Jacksonized @Jacksonized

View file

@ -1,11 +1,11 @@
package io.xpipe.beacon.exchange.cli; package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.RequestMessage; import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage; import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.dialog.DialogReference;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.DataStore;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -18,7 +18,7 @@ public class WritePreparationExchange implements MessageExchange {
@Override @Override
public String getId() { public String getId() {
return "writePreparation"; return "write";
} }
@Jacksonized @Jacksonized
@ -27,15 +27,17 @@ public class WritePreparationExchange implements MessageExchange {
public static class Request implements RequestMessage { public static class Request implements RequestMessage {
String type; String type;
@NonNull @NonNull
StreamDataStore output; DataStore output;
@NonNull @NonNull
DataSourceReference ref; DataSourceReference source;
} }
@Jacksonized @Jacksonized
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response implements ResponseMessage {
boolean hasBody;
@NonNull @NonNull
DialogReference config; DialogReference config;
} }

View file

@ -33,7 +33,7 @@ module io.xpipe.beacon {
WritePreparationExchange, WritePreparationExchange,
WriteExecuteExchange, WriteExecuteExchange,
SelectExchange, SelectExchange,
ReadPreparationExchange, ReadExchange,
QueryTextDataExchange, QueryTextDataExchange,
ReadExecuteExchange, ReadExecuteExchange,
ListStoresExchange, ListStoresExchange,

View file

@ -2,5 +2,11 @@ plugins {
id 'org.jreleaser' version '1.0.0' 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 version file('misc/version').text
apply from: 'jreleaser.gradle' apply from: 'jreleaser.gradle'

View file

@ -2,28 +2,39 @@ package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
@JsonTypeName("query") @JsonTypeName("query")
@EqualsAndHashCode(callSuper = true)
@ToString
@Getter @Getter
public class BaseQueryElement extends DialogElement { public class BaseQueryElement extends DialogElement {
private final String description; private final String description;
private final boolean newLine; private final boolean newLine;
private final boolean required; private final boolean required;
private final boolean hidden; private final boolean secret;
private final boolean quiet;
protected String value; protected String value;
@JsonCreator @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.description = description;
this.newLine = newLine; this.newLine = newLine;
this.required = required; this.required = required;
this.hidden = hidden; this.secret = secret;
this.quiet = quiet;
this.value = value; this.value = value;
} }
public boolean isNewLine() { public boolean isNewLine() {
return newLine; return newLine;
} }
@Override
public String toDisplayString() {
return description;
}
} }

View file

@ -1,10 +1,19 @@
package io.xpipe.core.dialog; package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@JsonTypeName("busy") @JsonTypeName("busy")
@EqualsAndHashCode(callSuper = true)
@ToString
public class BusyElement extends DialogElement { public class BusyElement extends DialogElement {
@Override
public String toDisplayString() {
return "busy";
}
@Override @Override
public boolean apply(String value) { public boolean apply(String value) {
return true; return true;

View file

@ -12,4 +12,23 @@ import lombok.extern.jackson.Jacksonized;
public class Choice { public class Choice {
Character character; Character character;
String description; 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;
}
} }

View file

@ -2,47 +2,67 @@ package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List; import java.util.List;
@JsonTypeName("choice") @JsonTypeName("choice")
@EqualsAndHashCode(callSuper = true)
@ToString
public class ChoiceElement extends DialogElement { public class ChoiceElement extends DialogElement {
private final String description; private final String description;
private final List<Choice> elements; private final List<Choice> elements;
private final boolean required;
private int selected; private int selected;
@Override
public String toDisplayString() {
return description;
}
@Override @Override
public boolean apply(String value) { public boolean apply(String value) {
if (value == null) { if (value == null) {
return true; return true;
} }
if (value.length() != 1) { if (value.length() == 1) {
return true; var c = value.charAt(0);
} if (Character.isDigit(c)) {
selected = Integer.parseInt(value) - 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; 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; return false;
} }
@JsonCreator @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.description = description;
this.elements = elements; this.elements = elements;
this.required = required;
this.selected = selected; this.selected = selected;
} }
@ -57,4 +77,8 @@ public class ChoiceElement extends DialogElement {
public String getDescription() { public String getDescription() {
return description; return description;
} }
public boolean isRequired() {
return required;
}
} }

View file

@ -14,11 +14,13 @@ public abstract class Dialog {
return new Dialog() { return new Dialog() {
@Override @Override
public DialogElement start() throws Exception { public DialogElement start() throws Exception {
complete();
return null; return null;
} }
@Override @Override
protected DialogElement next(String answer) throws Exception { protected DialogElement next(String answer) throws Exception {
complete();
return null; return null;
} }
}; };
@ -28,8 +30,8 @@ public abstract class Dialog {
private final ChoiceElement element; private final ChoiceElement element;
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, int selected) { private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
this.element = new ChoiceElement(description, elements, selected); this.element = new ChoiceElement(description, elements, required, selected);
} }
@Override @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) { 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, selected); Dialog.Choice c = new Dialog.Choice(description, elements, required, selected);
c.evaluateTo(c::getSelected); c.evaluateTo(c::getSelected);
return c; return c;
} }
@SafeVarargs @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 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 index = Arrays.asList(vals).indexOf(def);
var c = choice(description, elements, index); if (def != null && index == -1) {
c.evaluateTo(() -> vals[c.getSelected()]); 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; return c;
} }
@ -70,8 +81,8 @@ public abstract class Dialog {
private final QueryElement element; private final QueryElement element;
private Query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean 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,value, converter, hidden); this.element = new QueryElement(description, newLine, required, quiet, value, converter, hidden);
} }
@Override @Override
@ -98,13 +109,13 @@ public abstract class Dialog {
} }
} }
public static Dialog.Query query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter) { public static <T> Dialog.Query query(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter) {
var q = new Dialog.Query(description, newLine, required, value, converter, false); var q = new <T>Dialog.Query(description, newLine, required, quiet, value, converter, false);
q.evaluateTo(q::getConvertedValue); q.evaluateTo(q::getConvertedValue);
return q; return q;
} }
public static Dialog.Query querySecret(String description, boolean newLine, boolean required, Secret value) { 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); q.evaluateTo(q::getConvertedValue);
return q; return q;
} }
@ -118,7 +129,16 @@ public abstract class Dialog {
public DialogElement start() throws Exception { public DialogElement start() throws Exception {
current = 0; current = 0;
eval = null; 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 @Override
@ -127,7 +147,6 @@ public abstract class Dialog {
if (currentElement == null) { if (currentElement == null) {
DialogElement next = null; DialogElement next = null;
while (current < ds.length - 1 && (next = ds[++current].start()) == null) { while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
}; };
return next; return next;
} }
@ -200,8 +219,9 @@ public abstract class Dialog {
public DialogElement start() throws Exception { public DialogElement start() throws Exception {
eval = null; eval = null;
dialog = d.get(); dialog = d.get();
var start = dialog.start();
evaluateTo(dialog); evaluateTo(dialog);
return dialog.start(); return start;
} }
@Override @Override
@ -283,12 +303,16 @@ public abstract class Dialog {
}.evaluateTo(d.evaluation).onCompletion(d.completion); }.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) { 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, selected); var choice = new ChoiceElement(description, elements, required, selected);
return new Dialog() { return new Dialog() {
private Dialog choiceMade; private Dialog choiceMade;
{
evaluateTo(() -> choiceMade);
}
@Override @Override
public DialogElement start() throws Exception { public DialogElement start() throws Exception {
choiceMade = null; choiceMade = null;
@ -310,7 +334,7 @@ public abstract class Dialog {
return choice; return choice;
} }
}.evaluateTo(() -> choice.getSelected()); };
} }
protected Object eval; protected Object eval;
@ -324,7 +348,7 @@ public abstract class Dialog {
} }
public Dialog evaluateTo(Dialog d) { public Dialog evaluateTo(Dialog d) {
evaluation = d.evaluation; evaluation = () -> d.evaluation.get();
return this; return this;
} }

View file

@ -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);
}
}

View file

@ -2,10 +2,12 @@ package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.UUID; import java.util.UUID;
@EqualsAndHashCode @EqualsAndHashCode
@ToString
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public abstract class DialogElement { public abstract class DialogElement {
@ -15,6 +17,8 @@ public abstract class DialogElement {
this.id = UUID.randomUUID().toString(); this.id = UUID.randomUUID().toString();
} }
public abstract String toDisplayString();
public boolean apply(String value) { public boolean apply(String value) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -1,18 +1,22 @@
package io.xpipe.core.dialog; package io.xpipe.core.dialog;
import lombok.AllArgsConstructor; import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Builder; import lombok.NonNull;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID; import java.util.UUID;
@Value @Value
@Builder
@Jacksonized
@AllArgsConstructor
public class DialogReference { public class DialogReference {
@NonNull
UUID dialogId; UUID dialogId;
DialogElement start; DialogElement start;
@JsonCreator
public DialogReference(UUID dialogId, DialogElement start) {
this.dialogId = dialogId;
this.start = start;
}
} }

View file

@ -2,8 +2,12 @@ package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@JsonTypeName("header") @JsonTypeName("header")
@EqualsAndHashCode(callSuper = true)
@ToString
public class HeaderElement extends DialogElement { public class HeaderElement extends DialogElement {
protected String header; protected String header;
@ -13,6 +17,11 @@ public class HeaderElement extends DialogElement {
this.header = header; this.header = header;
} }
@Override
public String toDisplayString() {
return header;
}
@Override @Override
public boolean apply(String value) { public boolean apply(String value) {
return true; return true;

View file

@ -37,12 +37,12 @@ public abstract class QueryConverter<T> {
public static final QueryConverter<Secret> SECRET = new QueryConverter<Secret>() { public static final QueryConverter<Secret> SECRET = new QueryConverter<Secret>() {
@Override @Override
protected Secret fromString(String s) { protected Secret fromString(String s) {
return Secret.parse(s); return new Secret(s);
} }
@Override @Override
protected String toString(Secret value) { protected String toString(Secret value) {
return value.getValue(); return value.getEncryptedValue();
} }
}; };

View file

@ -7,8 +7,8 @@ public class QueryElement extends BaseQueryElement {
private final QueryConverter<?> converter; private final QueryConverter<?> converter;
public QueryElement(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean hidden) { public <T> QueryElement(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter, boolean hidden) {
super(description, newLine, required, hidden, value != null ? value.toString() : null); super(description, newLine, required, hidden, quiet, value != null ? converter.toString(value) : null);
this.converter = converter; this.converter = converter;
} }

View file

@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value; import lombok.Value;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path; import java.nio.file.Path;
@Value @Value
@ -27,6 +29,16 @@ public class FileStore implements StreamDataStore, FilenameStore {
this.file = file; this.file = file;
} }
@Override
public InputStream openInput() throws Exception {
return machine.openInput(file);
}
@Override
public OutputStream openOutput() throws Exception {
return machine.openOutput(file);
}
@Override @Override
public boolean canOpen() { public boolean canOpen() {
return machine.exists(file); return machine.exists(file);
@ -44,6 +56,6 @@ public class FileStore implements StreamDataStore, FilenameStore {
@Override @Override
public String getFileName() { public String getFileName() {
return file; return Path.of(file).getFileName().toString();
} }
} }

View 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();
}
}

View file

@ -1,5 +1,6 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
@ -9,6 +10,7 @@ import java.io.OutputStream;
@EqualsAndHashCode @EqualsAndHashCode
@Value @Value
@JsonTypeName("stdin")
public class StdinDataStore implements StreamDataStore { public class StdinDataStore implements StreamDataStore {
@Override @Override

View file

@ -1,5 +1,6 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
@ -8,6 +9,7 @@ import java.io.OutputStream;
@EqualsAndHashCode @EqualsAndHashCode
@Value @Value
@JsonTypeName("stdout")
public class StdoutDataStore implements StreamDataStore { public class StdoutDataStore implements StreamDataStore {
@Override @Override

View file

@ -1,5 +1,6 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import java.io.BufferedInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -9,6 +10,10 @@ import java.io.OutputStream;
*/ */
public interface StreamDataStore extends DataStore { 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. * 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"); 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. * Opens an output stream. This output stream does not necessarily have to be a new instance.
*/ */

View 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";
}
}

View file

@ -21,10 +21,7 @@ import io.xpipe.core.dialog.ChoiceElement;
import io.xpipe.core.dialog.HeaderElement; import io.xpipe.core.dialog.HeaderElement;
import io.xpipe.core.source.DataSourceInfo; import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.CollectionEntryDataStore; import io.xpipe.core.store.*;
import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.LocalDirectoryDataStore;
import io.xpipe.core.store.LocalMachineStore;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -36,21 +33,28 @@ public class CoreJacksonModule extends SimpleModule {
public void setupModule(SetupContext context) { public void setupModule(SetupContext context) {
context.registerSubtypes( context.registerSubtypes(
new NamedType(FileStore.class), new NamedType(FileStore.class),
new NamedType(StdinDataStore.class),
new NamedType(StdoutDataStore.class),
new NamedType(LocalDirectoryDataStore.class), new NamedType(LocalDirectoryDataStore.class),
new NamedType(CollectionEntryDataStore.class), new NamedType(CollectionEntryDataStore.class),
new NamedType(StringStore.class),
new NamedType(LocalMachineStore.class),
new NamedType(NamedStore.class),
new NamedType(ValueType.class), new NamedType(ValueType.class),
new NamedType(TupleType.class), new NamedType(TupleType.class),
new NamedType(ArrayType.class), new NamedType(ArrayType.class),
new NamedType(WildcardType.class), new NamedType(WildcardType.class),
new NamedType(DataSourceInfo.Table.class), new NamedType(DataSourceInfo.Table.class),
new NamedType(DataSourceInfo.Structure.class), new NamedType(DataSourceInfo.Structure.class),
new NamedType(DataSourceInfo.Text.class), new NamedType(DataSourceInfo.Text.class),
new NamedType(DataSourceInfo.Collection.class), new NamedType(DataSourceInfo.Collection.class),
new NamedType(DataSourceInfo.Raw.class), new NamedType(DataSourceInfo.Raw.class),
new NamedType(BaseQueryElement.class), new NamedType(BaseQueryElement.class),
new NamedType(ChoiceElement.class), new NamedType(ChoiceElement.class),
new NamedType(BusyElement.class), new NamedType(BusyElement.class),
new NamedType(LocalMachineStore.class),
new NamedType(HeaderElement.class) new NamedType(HeaderElement.class)
); );
@ -129,7 +133,7 @@ public class CoreJacksonModule extends SimpleModule {
@Override @Override
public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider) public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider)
throws IOException { throws IOException {
jgen.writeString(value.getValue()); jgen.writeString(value.getEncryptedValue());
} }
} }
@ -137,7 +141,7 @@ public class CoreJacksonModule extends SimpleModule {
@Override @Override
public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Secret.parse(p.getValueAsString()); return Secret.create(p.getValueAsString());
} }
} }

View file

@ -10,7 +10,15 @@ import java.util.Base64;
@EqualsAndHashCode @EqualsAndHashCode
public class Secret { 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))); return new Secret(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8)));
} }
@ -20,11 +28,15 @@ public class Secret {
return "*".repeat(value.length()); return "*".repeat(value.length());
} }
public String getValue() { public String getEncryptedValue() {
return value; return value;
} }
public String getSecret() { public String getSecretValue() {
if (value.length() < 2) {
return value;
}
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
} }
} }

2
deps

@ -1 +1 @@
Subproject commit e2af7576c19161cdbb08d64b498b7e6c9ae7e4c6 Subproject commit 49a1ad06bc6872f72c1d20ea864d24f3df59b7c5

View file

@ -31,9 +31,9 @@ repositories {
dependencies { dependencies {
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2' compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
compileOnly 'com.jfoenix:jfoenix:9.0.10'
implementation project(':core') implementation project(':core')
implementation 'io.xpipe:fxcomps:0.1' implementation 'io.xpipe:fxcomps:0.2'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.controlsfx:controlsfx:11.1.1' implementation 'org.controlsfx:controlsfx:11.1.1'
} }

View file

@ -8,10 +8,8 @@ import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -22,6 +20,11 @@ public interface DataSourceProvider<T extends DataSource<?>> {
DATABASE; DATABASE;
} }
default void validate() throws Exception {
getGeneralType();
getSourceClass();
}
default GeneralType getGeneralType() { default GeneralType getGeneralType() {
if (getFileProvider() != null) { if (getFileProvider() != null) {
return GeneralType.FILE; return GeneralType.FILE;
@ -34,12 +37,6 @@ public interface DataSourceProvider<T extends DataSource<?>> {
throw new ExtensionException("Provider has no general type"); 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) { default boolean supportsConversion(T in, DataSourceType t) {
return false; return false;
} }
@ -90,12 +87,16 @@ public interface DataSourceProvider<T extends DataSource<?>> {
} }
public static Dialog charset(Charset c) { public static Dialog charset(Charset c, boolean all) {
return Dialog.query("charset", false, false, c, QueryConverter.CHARSET); return Dialog.query("charset", false, false, c != null &&!all, c, QueryConverter.CHARSET);
} }
public static Dialog newLine(NewLine l) { public static Dialog newLine(NewLine l, boolean all) {
return Dialog.query("newline", false, false, l, NEW_LINE_CONVERTER); 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>() { public static final QueryConverter<NewLine> NEW_LINE_CONVERTER = new QueryConverter<NewLine>() {
@ -174,12 +175,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return createDefaultSource(input); return createDefaultSource(input);
} }
@SuppressWarnings("unchecked") Class<T> getSourceClass();
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()));
}
List<String> getPossibleNames(); List<String> getPossibleNames();

View file

@ -19,11 +19,14 @@ public class DataSourceProviders {
if (ALL == null) { if (ALL == null) {
ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream() ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream()
.map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet()); .map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet());
ALL.forEach(p -> { ALL.removeIf(p -> {
try { try {
p.init(); p.init();
p.validate();
return false;
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).handle(); ErrorEvent.fromThrowable(e).handle();
return true;
} }
}); });
} }

View file

@ -2,12 +2,32 @@ package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore; 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; import java.util.List;
public interface DataStoreProvider { 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 { default void init() throws Exception {
} }
@ -34,17 +54,17 @@ public interface DataStoreProvider {
} }
default String getDisplayIconFileName() { default String getDisplayIconFileName() {
return getModuleName() + ":" + getId() + ".png"; return getModuleName() + ":" + getId() + "_icon.png";
}
default Dialog dialogForURL(URL url) {
return null;
} }
default Dialog dialogForString(String s) { default Dialog dialogForString(String s) {
return null; return null;
} }
default Dialog dialogForURI(URI uri) {
return null;
}
Dialog defaultDialog(); Dialog defaultDialog();
default String display(DataStore store) { default String display(DataStore store) {

View file

@ -3,7 +3,7 @@ package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.extension.event.ErrorEvent; import io.xpipe.extension.event.ErrorEvent;
import java.net.URL; import java.net.URI;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ServiceLoader; import java.util.ServiceLoader;
@ -37,12 +37,12 @@ public class DataStoreProviders {
.anyMatch(s -> s.equalsIgnoreCase(name))).findAny(); .anyMatch(s -> s.equalsIgnoreCase(name))).findAny();
} }
public static Optional<Dialog> byURL(URL url) { public static Optional<Dialog> byURI(URI uri) {
if (ALL == null) { if (ALL == null) {
throw new IllegalStateException("Not initialized"); 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) { public static Optional<Dialog> byString(String s) {

View file

@ -1,6 +1,7 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileStore; import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
@ -11,6 +12,16 @@ import java.util.Map;
public interface SimpleFileDataSourceProvider<T extends DataSource<?>> extends DataSourceProvider<T> { 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 @Override
default boolean prefersStore(DataStore store) { default boolean prefersStore(DataStore store) {
for (var e : getSupportedExtensions().entrySet()) { for (var e : getSupportedExtensions().entrySet()) {

View file

@ -12,7 +12,7 @@ public interface SupportedApplicationProvider {
APPLICATION APPLICATION
} }
Region createRetrieveInstructions(DataSourceProvider<?> provider, ObservableValue<String> id); Region createRetrieveInstructions(ObservableValue<String> id);
String getId(); String getId();

View file

@ -2,6 +2,7 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
@ -45,6 +46,6 @@ public class CharChoiceComp extends Comp<CompStructure<HBox>> {
box.setAlignment(Pos.CENTER); box.setAlignment(Pos.CENTER);
choiceR.prefWidthProperty().bind(box.widthProperty().subtract(charChoiceR.widthProperty())); choiceR.prefWidthProperty().bind(box.widthProperty().subtract(charChoiceR.widthProperty()));
box.getStyleClass().add("char-choice-comp"); box.getStyleClass().add("char-choice-comp");
return new CompStructure<>(box); return new SimpleCompStructure<>(box);
} }
} }

View file

@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; 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.property.Property;
import javafx.scene.control.TextField; 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.setValue(n != null && n.length() > 0 ? n.charAt(0) : null);
}); });
value.addListener((c, o, n) -> { value.addListener((c, o, n) -> {
PlatformUtil.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {
text.setText(n != null ? n.toString() : null); text.setText(n != null ? n.toString() : null);
}); });
}); });
return new CompStructure<>(text); return new SimpleCompStructure<>(text);
} }
} }

View file

@ -1,8 +1,10 @@
package io.xpipe.extension.comp; package io.xpipe.extension.comp;
import io.xpipe.extension.I18n;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; 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.property.Property;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -28,7 +30,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
@Override @Override
public String toString(T object) { public String toString(T object) {
if (object == null) { if (object == null) {
return "null"; return I18n.get("extension.none");
} }
return range.get(object).getValue(); return range.get(object).getValue();
@ -39,8 +41,8 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
}); });
PlatformUtil.connect(value, cb.valueProperty()); PlatformThread.connect(value, cb.valueProperty());
cb.getStyleClass().add("choice-comp"); cb.getStyleClass().add("choice-comp");
return new CompStructure<>(cb); return new SimpleCompStructure<>(cb);
} }
} }

View file

@ -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) {
}
}

View file

@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; 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.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@ -26,12 +27,12 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
public CodeSnippetComp(boolean showLineNumbers, ObservableValue<CodeSnippet> value) { public CodeSnippetComp(boolean showLineNumbers, ObservableValue<CodeSnippet> value) {
this.showLineNumbers = new SimpleBooleanProperty(showLineNumbers); this.showLineNumbers = new SimpleBooleanProperty(showLineNumbers);
this.value = PlatformUtil.wrap(value); this.value = PlatformThread.sync(value);
} }
public CodeSnippetComp(ObservableValue<Boolean> showLineNumbers, ObservableValue<CodeSnippet> value) { public CodeSnippetComp(ObservableValue<Boolean> showLineNumbers, ObservableValue<CodeSnippet> value) {
this.showLineNumbers = PlatformUtil.wrap(showLineNumbers); this.showLineNumbers = PlatformThread.sync(showLineNumbers);
this.value = PlatformUtil.wrap(value); this.value = PlatformThread.sync(value);
} }
private static String toRGBCode(Color color) { private static String toRGBCode(Color color) {
@ -131,6 +132,6 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
AnchorPane.setRightAnchor(copyButton, 10.0); AnchorPane.setRightAnchor(copyButton, 10.0);
c.getChildren().add(pane); c.getChildren().add(pane);
return new CompStructure<>(c); return new SimpleCompStructure<>(c);
} }
} }

View file

@ -1,15 +1,12 @@
package io.xpipe.extension.comp; package io.xpipe.extension.comp;
import io.xpipe.core.charsetter.NewLine; 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.extension.I18n;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextField; import javafx.scene.control.Label;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
@ -20,18 +17,36 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; 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<DynamicOptionsComp.Entry> entries = new ArrayList<>();
private final List<Property<?>> props = 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) { public DynamicOptionsBuilder<T> addNewLine(Property<NewLine> prop) {
var map = new LinkedHashMap<NewLine, ObservableValue<String>>(); var map = new LinkedHashMap<NewLine, ObservableValue<String>>();
for (var e : NewLine.values()) { 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)); 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); props.add(prop);
return this; return this;
} }
@ -59,29 +74,50 @@ public class DynamicOptionsBuilder<T extends DataSource<?>> {
public DynamicOptionsBuilder<T> addCharset(Property<Charset> prop) { public DynamicOptionsBuilder<T> addCharset(Property<Charset> prop) {
var comp = new CharsetChoiceComp(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); props.add(prop);
return this; return this;
} }
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) { public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
var comp = Comp.of(() -> { var comp = new TextFieldComp(prop);
var tf = new TextField(prop.getValue());
tf.textProperty().addListener((c, o, n) -> {
prop.setValue(n.length() > 0 ? n : null);
});
return tf;
});
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop); props.add(prop);
return this; return this;
} }
public Region build(Function<T, T> creator, Property<T> toBind) { public DynamicOptionsBuilder<T> addComp(Comp<?> comp) {
var bind = Bindings.createObjectBinding(() -> creator.apply(toBind.getValue()), props.toArray(Observable[]::new)); entries.add(new DynamicOptionsComp.Entry(null, comp));
bind.addListener((c,o, n) -> { return this;
toBind.setValue(n); }
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();
} }
} }

View file

@ -2,6 +2,7 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
@ -10,6 +11,7 @@ import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane; import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import java.util.ArrayList; import java.util.ArrayList;
@ -18,9 +20,11 @@ import java.util.List;
public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> { public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
private final List<Entry> entries; 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.entries = entries;
this.wrap = wrap;
} }
@Override @Override
@ -35,32 +39,42 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
for (var entry : getEntries()) { for (var entry : getEntries()) {
var line = new HBox(); var line = new HBox();
line.setSpacing(5); if (!wrap) {
line.prefWidthProperty().bind(flow.widthProperty());
}
line.setSpacing(8);
var name = new Label(); if (entry.name() != null) {
name.textProperty().bind(entry.name()); var name = new Label();
name.prefHeightProperty().bind(line.heightProperty()); name.textProperty().bind(entry.name());
name.setMinWidth(Region.USE_PREF_SIZE); name.prefHeightProperty().bind(line.heightProperty());
name.setAlignment(Pos.CENTER_LEFT); name.setMinWidth(Region.USE_PREF_SIZE);
nameRegions.add(name); name.setAlignment(Pos.CENTER_LEFT);
line.getChildren().add(name); nameRegions.add(name);
line.getChildren().add(name);
}
var r = entry.comp().createRegion(); var r = entry.comp().createRegion();
compRegions.add(r); compRegions.add(r);
line.getChildren().add(r); line.getChildren().add(r);
if (!wrap) {
HBox.setHgrow(r, Priority.ALWAYS);
}
flow.getChildren().add(line); flow.getChildren().add(line);
} }
var compWidthBinding = Bindings.createDoubleBinding(() -> { if (wrap) {
if (compRegions.stream().anyMatch(r -> r.getWidth() == 0)) { var compWidthBinding = Bindings.createDoubleBinding(() -> {
return Region.USE_COMPUTED_SIZE; 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); var m = compRegions.stream().map(Region::getWidth).max(Double::compareTo).orElse(0.0);
return m; return m;
}, compRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0])); }, compRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
compRegions.forEach(r -> r.prefWidthProperty().bind(compWidthBinding)); compRegions.forEach(r -> r.prefWidthProperty().bind(compWidthBinding));
}
var nameWidthBinding = Bindings.createDoubleBinding(() -> { var nameWidthBinding = Bindings.createDoubleBinding(() -> {
if (nameRegions.stream().anyMatch(r -> r.getWidth() == 0)) { 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.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding)); nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding));
return new CompStructure<>(flow); return new SimpleCompStructure<>(flow);
} }
public List<Entry> getEntries() { public List<Entry> getEntries() {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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) {
}
}

View file

@ -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);
}
}

View file

@ -2,7 +2,8 @@ package io.xpipe.extension.comp;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; 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.property.Property;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
@ -38,7 +39,7 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
box.getChildren().add(b); box.getChildren().add(b);
b.setToggleGroup(group); b.setToggleGroup(group);
value.addListener((c, o, n) -> { 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())) { if (entry.getKey().equals(value.getValue())) {
b.setSelected(true); b.setSelected(true);
@ -55,6 +56,6 @@ public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
oldVal.setSelected(true); oldVal.setSelected(true);
}); });
return new CompStructure<>(box); return new SimpleCompStructure<>(box);
} }
} }

View file

@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.Singular; import lombok.Singular;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Builder @Builder
@ -49,10 +50,16 @@ public class ErrorEvent {
private Throwable throwable; private Throwable throwable;
@Singular @Singular
private List<Path> diagnostics; private List<Path> attachments;
@Singular
private List<Path> sensitiveDiagnostics;
private final List<TrackEvent> trackEvents = EventHandler.get().snapshotEvents(); 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<>();
}
} }

View file

@ -16,6 +16,10 @@ public class ExceptionConverter {
return I18n.get("classNotFound", msg); return I18n.get("classNotFound", msg);
} }
if (ex instanceof NullPointerException) {
return I18n.get("extension.nullPointer", msg);
}
return msg; return msg;
} }
} }

View file

@ -6,6 +6,7 @@ import lombok.Singular;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
@Builder @Builder
@ -110,6 +111,9 @@ public class TrackEvent {
@Singular @Singular
private Map<String, Object> tags; private Map<String, Object> tags;
@Singular
private List<String> elements;
public void handle() { public void handle() {
EventHandler.get().handle(this); EventHandler.get().handle(this);
} }

View file

@ -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;
}

View file

@ -6,18 +6,18 @@ module io.xpipe.extension {
exports io.xpipe.extension.comp; exports io.xpipe.extension.comp;
exports io.xpipe.extension.event; exports io.xpipe.extension.event;
exports io.xpipe.extension.prefs; exports io.xpipe.extension.prefs;
exports io.xpipe.extension.util;
requires transitive io.xpipe.core; requires transitive io.xpipe.core;
requires transitive javafx.base; requires transitive javafx.base;
requires javafx.graphics; requires javafx.graphics;
requires transitive javafx.controls; requires transitive javafx.controls;
requires io.xpipe.fxcomps; requires io.xpipe.fxcomps;
requires org.apache.commons.collections4; requires static org.apache.commons.collections4;
requires static lombok; requires static lombok;
requires static com.dlsc.preferencesfx; requires static com.dlsc.preferencesfx;
requires static com.dlsc.formsfx; requires static com.dlsc.formsfx;
requires static org.slf4j; requires static org.slf4j;
requires static com.google.gson;
requires static org.controlsfx.controls; requires static org.controlsfx.controls;
requires java.desktop; requires java.desktop;
requires org.fxmisc.richtext; requires org.fxmisc.richtext;
@ -28,6 +28,7 @@ module io.xpipe.extension {
requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.javafx;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;
requires static org.junit.jupiter.api; requires static org.junit.jupiter.api;
requires static com.jfoenix;
uses DataSourceProvider; uses DataSourceProvider;
uses SupportedApplicationProvider; uses SupportedApplicationProvider;

View file

@ -0,0 +1,3 @@
displayName=Mein Dateiformat
description=Meine Dateiformat-Beschreibung
fileName=Mein Dateiformat Datei

View file

@ -0,0 +1,6 @@
charset=Charset
newLine=Newline
crlf=CRLF (Windows)
lf=LF (Linux)
none=None
nullPointer=Null Pointer: $MSG$