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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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'

View file

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

View file

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

View file

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

View file

@ -2,27 +2,34 @@ 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;
}
if (value.length() == 1) {
var c = value.charAt(0);
if (Character.isDigit(c)) {
selected = Integer.parseInt(value) - 1;
@ -35,14 +42,27 @@ public class ChoiceElement extends DialogElement {
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;
}
}

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -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.
*/

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

View file

@ -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 getSecretValue() {
if (value.length() < 2) {
return value;
}
public String getSecret() {
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 {
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'
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -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,8 +39,12 @@ 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);
if (entry.name() != null) {
var name = new Label();
name.textProperty().bind(entry.name());
name.prefHeightProperty().bind(line.heightProperty());
@ -44,14 +52,19 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
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);
}
if (wrap) {
var compWidthBinding = Bindings.createDoubleBinding(() -> {
if (compRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
return Region.USE_COMPUTED_SIZE;
@ -61,6 +74,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
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() {

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

View file

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

View file

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

View file

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

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.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;

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$