mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Add more exchanges + move some files
This commit is contained in:
parent
774689e42a
commit
caafb9d850
28 changed files with 775 additions and 76 deletions
|
@ -23,7 +23,7 @@ import java.util.Optional;
|
||||||
|
|
||||||
import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
||||||
|
|
||||||
public class BeaconClient {
|
public class BeaconClient implements AutoCloseable {
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface FailableBiConsumer<T, U, E extends Throwable> {
|
public interface FailableBiConsumer<T, U, E extends Throwable> {
|
||||||
|
@ -76,7 +76,7 @@ public class BeaconClient {
|
||||||
public <REQ extends RequestMessage, RES extends ResponseMessage> void exchange(
|
public <REQ extends RequestMessage, RES extends ResponseMessage> void exchange(
|
||||||
REQ req,
|
REQ req,
|
||||||
FailableConsumer<OutputStream, IOException> reqWriter,
|
FailableConsumer<OutputStream, IOException> reqWriter,
|
||||||
FailableBiPredicate<RES, InputStream, IOException> resReader)
|
FailableBiConsumer<RES, InputStream, IOException> resReader)
|
||||||
throws ConnectorException, ClientException, ServerException {
|
throws ConnectorException, ClientException, ServerException {
|
||||||
try {
|
try {
|
||||||
sendRequest(req);
|
sendRequest(req);
|
||||||
|
@ -91,23 +91,16 @@ public class BeaconClient {
|
||||||
throw new ConnectorException("Invalid body separator");
|
throw new ConnectorException("Invalid body separator");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resReader.test(res, in)) {
|
resReader.accept(res, in);
|
||||||
close();
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
close();
|
|
||||||
throw new ConnectorException("Couldn't communicate with socket", ex);
|
throw new ConnectorException("Couldn't communicate with socket", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <REQ extends RequestMessage, RES extends ResponseMessage> RES simpleExchange(REQ req)
|
public <REQ extends RequestMessage, RES extends ResponseMessage> RES simpleExchange(REQ req)
|
||||||
throws ServerException, ConnectorException, ClientException {
|
throws ServerException, ConnectorException, ClientException {
|
||||||
try {
|
sendRequest(req);
|
||||||
sendRequest(req);
|
return this.receiveResponse();
|
||||||
return this.receiveResponse();
|
|
||||||
} finally {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
|
private <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
|
||||||
|
|
|
@ -16,7 +16,7 @@ public abstract class BeaconConnector {
|
||||||
protected <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
|
protected <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
|
||||||
BeaconClient socket,
|
BeaconClient socket,
|
||||||
REQ req,
|
REQ req,
|
||||||
BeaconClient.FailableBiPredicate<RES, InputStream, IOException> responseConsumer) throws ServerException, ConnectorException, ClientException {
|
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) throws ServerException, ConnectorException, ClientException {
|
||||||
performInputOutputExchange(socket, req, null, responseConsumer);
|
performInputOutputExchange(socket, req, null, responseConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ public abstract class BeaconConnector {
|
||||||
BeaconClient socket,
|
BeaconClient socket,
|
||||||
REQ req,
|
REQ req,
|
||||||
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
|
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
|
||||||
BeaconClient.FailableBiPredicate<RES, InputStream, IOException> responseConsumer)
|
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer)
|
||||||
throws ServerException, ConnectorException, ClientException {
|
throws ServerException, ConnectorException, ClientException {
|
||||||
socket.exchange(req, reqWriter, responseConsumer);
|
socket.exchange(req, reqWriter, responseConsumer);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ public abstract class BeaconConnector {
|
||||||
AtomicReference<RES> response = new AtomicReference<>();
|
AtomicReference<RES> response = new AtomicReference<>();
|
||||||
socket.exchange(req, reqWriter, (RES res, InputStream in) -> {
|
socket.exchange(req, reqWriter, (RES res, InputStream in) -> {
|
||||||
response.set(res);
|
response.set(res);
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
return response.get();
|
return response.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class DialogExchange implements MessageExchange<DialogExchange.Request, DialogExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "dialog";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<DialogExchange.Request> getRequestClass() {
|
||||||
|
return DialogExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<DialogExchange.Response> getResponseClass() {
|
||||||
|
return DialogExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
DataSourceConfigInstance instance;
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
DataSourceConfigInstance instance;
|
||||||
|
String errorMsg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.core.source.DataSourceInfo;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class InfoExchange implements MessageExchange<InfoExchange.Request, InfoExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<InfoExchange.Request> getRequestClass() {
|
||||||
|
return InfoExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<InfoExchange.Response> getResponseClass() {
|
||||||
|
return InfoExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
DataSourceId id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
DataSourceInfo info;
|
||||||
|
DataStore store;
|
||||||
|
DataSourceConfigInstance config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class ReadExecuteExchange implements MessageExchange<ReadExecuteExchange.Request, ReadExecuteExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "readExecute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadExecuteExchange.Request> getRequestClass() {
|
||||||
|
return ReadExecuteExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadExecuteExchange.Response> getResponseClass() {
|
||||||
|
return ReadExecuteExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
@NonNull
|
||||||
|
DataStore dataStore;
|
||||||
|
@NonNull
|
||||||
|
DataSourceConfigInstance config;
|
||||||
|
@NonNull
|
||||||
|
DataSourceId targetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class ReadPreparationExchange implements MessageExchange<ReadPreparationExchange.Request, ReadPreparationExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "readPreparation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadPreparationExchange.Request> getRequestClass() {
|
||||||
|
return ReadPreparationExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadPreparationExchange.Response> getResponseClass() {
|
||||||
|
return ReadPreparationExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
String providerType;
|
||||||
|
String dataStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
DataSourceConfigInstance config;
|
||||||
|
DataStore dataStore;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class SelectExchange implements MessageExchange<SelectExchange.Request, SelectExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "select";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<SelectExchange.Request> getRequestClass() {
|
||||||
|
return SelectExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<SelectExchange.Response> getResponseClass() {
|
||||||
|
return SelectExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
DataSourceId id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class WriteExecuteExchange implements MessageExchange<WriteExecuteExchange.Request, WriteExecuteExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "writeExecute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<WriteExecuteExchange.Request> getRequestClass() {
|
||||||
|
return WriteExecuteExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<WriteExecuteExchange.Response> getResponseClass() {
|
||||||
|
return WriteExecuteExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
@NonNull
|
||||||
|
DataSourceId sourceId;
|
||||||
|
@NonNull
|
||||||
|
DataStore dataStore;
|
||||||
|
@NonNull
|
||||||
|
DataSourceConfigInstance config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class WritePreparationExchange implements MessageExchange<WritePreparationExchange.Request, WritePreparationExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "writePreparation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<WritePreparationExchange.Request> getRequestClass() {
|
||||||
|
return WritePreparationExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<WritePreparationExchange.Response> getResponseClass() {
|
||||||
|
return WritePreparationExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
String providerType;
|
||||||
|
String output;
|
||||||
|
@NonNull
|
||||||
|
DataSourceId sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
@NonNull
|
||||||
|
DataStore dataStore;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
DataSourceConfigInstance config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,5 +25,12 @@ module io.xpipe.beacon {
|
||||||
StatusExchange,
|
StatusExchange,
|
||||||
StopExchange,
|
StopExchange,
|
||||||
StoreResourceExchange,
|
StoreResourceExchange,
|
||||||
|
WritePreparationExchange,
|
||||||
|
WriteExecuteExchange,
|
||||||
|
SelectExchange,
|
||||||
|
ReadPreparationExchange,
|
||||||
|
ReadExecuteExchange,
|
||||||
|
DialogExchange,
|
||||||
|
InfoExchange,
|
||||||
VersionExchange;
|
VersionExchange;
|
||||||
}
|
}
|
|
@ -50,6 +50,10 @@ public class TupleType extends DataType {
|
||||||
return new TupleType(Collections.nCopies(types.size(), null), types);
|
return new TupleType(Collections.nCopies(types.size(), null), types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAllNames() {
|
||||||
|
return names.stream().allMatch(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "tuple";
|
return "tuple";
|
||||||
|
|
|
@ -1,49 +1,29 @@
|
||||||
package io.xpipe.core.source;
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Singular;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
public class DataSourceConfig {
|
public class DataSourceConfig {
|
||||||
|
String description;
|
||||||
|
|
||||||
private String description;
|
@Singular
|
||||||
private List<Option<?>> options;
|
List<Option> options;
|
||||||
|
|
||||||
public DataSourceConfig(String description, List<Option<?>> options) {
|
@Value
|
||||||
this.description = description;
|
@Builder
|
||||||
this.options = options;
|
@Jacksonized
|
||||||
}
|
@AllArgsConstructor
|
||||||
|
public static class Option {
|
||||||
public String getDescription() {
|
String name;
|
||||||
return description;
|
String key;
|
||||||
}
|
|
||||||
|
|
||||||
public List<Option<?>> getOptions() {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class Option<T> {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
protected T value;
|
|
||||||
|
|
||||||
public Option(String name) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Option(String name, T value) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String enterValue(String val);
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DataSourceConfigInstance {
|
||||||
|
|
||||||
|
String provider;
|
||||||
|
DataSourceConfig config;
|
||||||
|
Map<String, String> currentValues;
|
||||||
|
}
|
|
@ -22,8 +22,11 @@ import lombok.Getter;
|
||||||
public class DataSourceId {
|
public class DataSourceId {
|
||||||
|
|
||||||
public static final char SEPARATOR = ':';
|
public static final char SEPARATOR = ':';
|
||||||
|
public static final DataSourceId ANONYMOUS = new DataSourceId(null, null);
|
||||||
|
|
||||||
private final String collectionName;
|
private final String collectionName;
|
||||||
private final String entryName;
|
private final String entryName;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
private DataSourceId(String collectionName, String entryName) {
|
private DataSourceId(String collectionName, String entryName) {
|
||||||
this.collectionName = collectionName;
|
this.collectionName = collectionName;
|
||||||
|
@ -38,6 +41,10 @@ public class DataSourceId {
|
||||||
* @throws IllegalArgumentException if any name is not valid
|
* @throws IllegalArgumentException if any name is not valid
|
||||||
*/
|
*/
|
||||||
public static DataSourceId create(String collectionName, String entryName) {
|
public static DataSourceId create(String collectionName, String entryName) {
|
||||||
|
if (collectionName == null && entryName == null) {
|
||||||
|
return ANONYMOUS;
|
||||||
|
}
|
||||||
|
|
||||||
if (collectionName != null && collectionName.trim().length() == 0) {
|
if (collectionName != null && collectionName.trim().length() == 0) {
|
||||||
throw new IllegalArgumentException("Trimmed collection name is empty");
|
throw new IllegalArgumentException("Trimmed collection name is empty");
|
||||||
}
|
}
|
||||||
|
@ -70,6 +77,10 @@ public class DataSourceId {
|
||||||
throw new IllegalArgumentException("String is null");
|
throw new IllegalArgumentException("String is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s.equals(String.valueOf(SEPARATOR))) {
|
||||||
|
return ANONYMOUS;
|
||||||
|
}
|
||||||
|
|
||||||
var split = s.split(String.valueOf(SEPARATOR));
|
var split = s.split(String.valueOf(SEPARATOR));
|
||||||
if (split.length != 2) {
|
if (split.length != 2) {
|
||||||
throw new IllegalArgumentException("Data source id must contain exactly one " + SEPARATOR);
|
throw new IllegalArgumentException("Data source id must contain exactly one " + SEPARATOR);
|
||||||
|
@ -87,6 +98,6 @@ public class DataSourceId {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (collectionName != null ? collectionName : "") + SEPARATOR + entryName;
|
return (collectionName != null ? collectionName.toLowerCase() : "") + SEPARATOR + (entryName != null ? entryName.toLowerCase() : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,9 @@ public class InputStreamDataStore implements StreamDataStore {
|
||||||
public OutputStream openOutput() throws Exception {
|
public OutputStream openOutput() throws Exception {
|
||||||
throw new UnsupportedOperationException("No output available");
|
throw new UnsupportedOperationException("No output available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.core.store;
|
||||||
|
|
||||||
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 java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -13,6 +14,7 @@ import java.time.Instant;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@JsonTypeName("local")
|
@JsonTypeName("local")
|
||||||
|
@EqualsAndHashCode
|
||||||
public class LocalFileDataStore implements StreamDataStore {
|
public class LocalFileDataStore implements StreamDataStore {
|
||||||
|
|
||||||
private final Path file;
|
private final Path file;
|
||||||
|
@ -50,4 +52,9 @@ public class LocalFileDataStore implements StreamDataStore {
|
||||||
public OutputStream openOutput() throws Exception {
|
public OutputStream openOutput() throws Exception {
|
||||||
return Files.newOutputStream(file);
|
return Files.newOutputStream(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists() {
|
||||||
|
return Files.exists(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,9 @@ public class RemoteFileDataStore implements StreamDataStore {
|
||||||
public OutputStream openOutput() throws Exception {
|
public OutputStream openOutput() throws Exception {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data store that can be accessed using InputStreams and/or OutputStreams.
|
* A data store that can be accessed using InputStreams and/or OutputStreams.
|
||||||
|
@ -9,6 +16,21 @@ import java.io.OutputStream;
|
||||||
*/
|
*/
|
||||||
public interface StreamDataStore extends DataStore {
|
public interface StreamDataStore extends DataStore {
|
||||||
|
|
||||||
|
static Optional<StreamDataStore> fromString(@NonNull String s) {
|
||||||
|
try {
|
||||||
|
var path = Path.of(s);
|
||||||
|
return Optional.of(new LocalFileDataStore(path));
|
||||||
|
} catch (InvalidPathException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var path = new URL(s);
|
||||||
|
} catch (MalformedURLException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
@ -18,4 +40,6 @@ public interface StreamDataStore extends DataStore {
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
OutputStream openOutput() throws Exception;
|
OutputStream openOutput() throws Exception;
|
||||||
|
|
||||||
|
boolean exists();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
import io.xpipe.core.data.type.ArrayType;
|
import io.xpipe.core.data.type.ArrayType;
|
||||||
|
@ -35,6 +36,8 @@ public class CoreJacksonModule extends SimpleModule {
|
||||||
|
|
||||||
addSerializer(Path.class, new LocalPathSerializer());
|
addSerializer(Path.class, new LocalPathSerializer());
|
||||||
addDeserializer(Path.class, new LocalPathDeserializer());
|
addDeserializer(Path.class, new LocalPathDeserializer());
|
||||||
|
|
||||||
|
context.setMixInAnnotations(Throwable.class, ExceptionTypeMixIn.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CharsetSerializer extends JsonSerializer<Charset> {
|
public static class CharsetSerializer extends JsonSerializer<Charset> {
|
||||||
|
@ -70,4 +73,10 @@ public class CoreJacksonModule extends SimpleModule {
|
||||||
return Path.of(p.getValueAsString());
|
return Path.of(p.getValueAsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonSerialize(as = Throwable.class)
|
||||||
|
public abstract static class ExceptionTypeMixIn {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@ plugins {
|
||||||
|
|
||||||
apply from: "$rootDir/deps/java.gradle"
|
apply from: "$rootDir/deps/java.gradle"
|
||||||
apply from: "$rootDir/deps/javafx.gradle"
|
apply from: "$rootDir/deps/javafx.gradle"
|
||||||
|
apply from: "$rootDir/deps/richtextfx.gradle"
|
||||||
apply from: "$rootDir/deps/jackson.gradle"
|
apply from: "$rootDir/deps/jackson.gradle"
|
||||||
apply from: "$rootDir/deps/commons.gradle"
|
apply from: "$rootDir/deps/commons.gradle"
|
||||||
apply from: "$rootDir/deps/lombok.gradle"
|
apply from: "$rootDir/deps/lombok.gradle"
|
||||||
|
apply from: "$rootDir/deps/ikonli.gradle"
|
||||||
apply from: 'publish.gradle'
|
apply from: 'publish.gradle'
|
||||||
apply from: "$rootDir/deps/publish-base.gradle"
|
apply from: "$rootDir/deps/publish-base.gradle"
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.DataSourceConfig;
|
||||||
import io.xpipe.core.source.DataSourceDescriptor;
|
import io.xpipe.core.source.DataSourceDescriptor;
|
||||||
|
import io.xpipe.core.source.DataSourceInfo;
|
||||||
|
import io.xpipe.core.source.TableReadConnection;
|
||||||
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.beans.property.Property;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public interface DataSourceProvider {
|
public interface DataSourceProvider {
|
||||||
|
|
||||||
interface FileProvider {
|
interface FileProvider {
|
||||||
|
|
||||||
boolean supportsFile(Path file);
|
void write(StreamDataStore store, DataSourceDescriptor<StreamDataStore> desc, TableReadConnection con) throws Exception;
|
||||||
|
|
||||||
String getFileName();
|
String getFileName();
|
||||||
|
|
||||||
|
@ -33,8 +37,45 @@ public interface DataSourceProvider {
|
||||||
|
|
||||||
interface CliProvider {
|
interface CliProvider {
|
||||||
|
|
||||||
|
static String booleanName(String name) {
|
||||||
|
return name + " (y/n)";
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<String, Boolean> booleanConverter() {
|
||||||
|
return s -> {
|
||||||
|
if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.equalsIgnoreCase("n") || s.equalsIgnoreCase("no")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Invalid boolean: " + s);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<String, Character> characterConverter() {
|
||||||
|
return s -> {
|
||||||
|
if (s.length() != 1) {
|
||||||
|
throw new IllegalArgumentException("Invalid character: " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.toCharArray()[0];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSourceConfig getConfig();
|
||||||
|
|
||||||
|
DataSourceDescriptor<?> toDescriptor(Map<String, String> values);
|
||||||
|
|
||||||
|
Map<String, String> toConfigOptions(DataSourceDescriptor<?> desc);
|
||||||
|
|
||||||
|
Map<DataSourceConfig.Option, Function<String, ?>> getConverters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean supportsStore(DataStore store);
|
||||||
|
|
||||||
FileProvider getFileProvider();
|
FileProvider getFileProvider();
|
||||||
|
|
||||||
GuiProvider getGuiProvider();
|
GuiProvider getGuiProvider();
|
||||||
|
@ -49,5 +90,7 @@ public interface DataSourceProvider {
|
||||||
*/
|
*/
|
||||||
DataSourceDescriptor<?> createDefaultDescriptor(DataStore input) throws Exception;
|
DataSourceDescriptor<?> createDefaultDescriptor(DataStore input) throws Exception;
|
||||||
|
|
||||||
|
DataSourceDescriptor<?> createDefaultWriteDescriptor(DataStore input, DataSourceInfo info) throws Exception;
|
||||||
|
|
||||||
Class<? extends DataSourceDescriptor<?>> getDescriptorClass();
|
Class<? extends DataSourceDescriptor<?>> getDescriptorClass();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,10 @@ package io.xpipe.extension;
|
||||||
|
|
||||||
import io.xpipe.core.data.type.TupleType;
|
import io.xpipe.core.data.type.TupleType;
|
||||||
import io.xpipe.core.source.TableDataSourceDescriptor;
|
import io.xpipe.core.source.TableDataSourceDescriptor;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.LocalFileDataStore;
|
import io.xpipe.core.store.LocalFileDataStore;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -52,26 +50,13 @@ public class DataSourceProviders {
|
||||||
return ALL.stream().filter(d -> d.getId().equals(name)).findAny();
|
return ALL.stream().filter(d -> d.getId().equals(name)).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<DataSourceProvider> byFile(Path file) {
|
public static Optional<DataSourceProvider> byStore(DataStore store) {
|
||||||
if (ALL == null) {
|
if (ALL == null) {
|
||||||
throw new IllegalStateException("Not initialized");
|
throw new IllegalStateException("Not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ALL.stream().filter(d -> d.getFileProvider() != null)
|
return ALL.stream().filter(d -> d.getFileProvider() != null)
|
||||||
.filter(d -> d.getFileProvider().supportsFile(file)).findAny();
|
.filter(d -> d.supportsStore(store)).findAny();
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<DataSourceProvider> byURL(URL url) {
|
|
||||||
if (ALL == null) {
|
|
||||||
throw new IllegalStateException("Not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var path = Path.of(url.toURI());
|
|
||||||
return byFile(path);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<DataSourceProvider> getAll() {
|
public static Set<DataSourceProvider> getAll() {
|
||||||
|
|
|
@ -8,6 +8,11 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
public interface SupportedApplicationProvider {
|
public interface SupportedApplicationProvider {
|
||||||
|
|
||||||
|
enum Category {
|
||||||
|
PROGRAMMING_LANGUAGE,
|
||||||
|
APPLICATION
|
||||||
|
}
|
||||||
|
|
||||||
Region createTableRetrieveInstructions(ObservableValue<DataSourceId> id);
|
Region createTableRetrieveInstructions(ObservableValue<DataSourceId> id);
|
||||||
|
|
||||||
Region createStructureRetrieveInstructions(ObservableValue<DataSourceId> id);
|
Region createStructureRetrieveInstructions(ObservableValue<DataSourceId> id);
|
||||||
|
@ -19,4 +24,12 @@ public interface SupportedApplicationProvider {
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
Supplier<String> getName();
|
Supplier<String> getName();
|
||||||
|
|
||||||
|
Category getCategory();
|
||||||
|
|
||||||
|
String getSetupGuideURL();
|
||||||
|
|
||||||
|
default String getGraphicIcon() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||||
|
|
||||||
|
public String getRawString() {
|
||||||
|
return lines.stream().map(line -> line.elements().stream()
|
||||||
|
.map(Element::text).collect(Collectors.joining()))
|
||||||
|
.collect(Collectors.joining(System.lineSeparator()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(CodeSnippets.ColorScheme scheme) {
|
||||||
|
return new Builder(scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface Element {
|
||||||
|
|
||||||
|
String text();
|
||||||
|
|
||||||
|
Color color();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private CodeSnippets.ColorScheme scheme;
|
||||||
|
private List<Line> lines;
|
||||||
|
private List<Element> currentLine;
|
||||||
|
|
||||||
|
public Builder(CodeSnippets.ColorScheme scheme) {
|
||||||
|
this.scheme = scheme;
|
||||||
|
lines = new ArrayList<>();
|
||||||
|
currentLine = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder keyword(String text) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(text, scheme.keyword()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder string(String text) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(text, scheme.string()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder identifier(String text) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(text, scheme.identifier()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder type(String text) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(text, scheme.type()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder text(String text, Color c) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(text, c));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder space() {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(" ", Color.TRANSPARENT));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder space(int count) {
|
||||||
|
currentLine.add(new CodeSnippet.StaticElement(" ".repeat(count), Color.TRANSPARENT));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder newLine() {
|
||||||
|
lines.add(new Line(new ArrayList<>(currentLine)));
|
||||||
|
currentLine.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeSnippet build() {
|
||||||
|
if (currentLine.size() > 0) {
|
||||||
|
newLine();
|
||||||
|
}
|
||||||
|
return new CodeSnippet(lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record StaticElement(String value, Color color) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String text() {
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Line(List<CodeSnippet.Element> elements) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
|
import io.xpipe.fxcomps.Comp;
|
||||||
|
import io.xpipe.fxcomps.CompStructure;
|
||||||
|
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.input.ScrollEvent;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.fxmisc.richtext.InlineCssTextArea;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
|
||||||
|
|
||||||
|
public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
||||||
|
|
||||||
|
private final ObservableValue<CodeSnippet> value;
|
||||||
|
|
||||||
|
public CodeSnippetComp(ObservableValue<CodeSnippet> value) {
|
||||||
|
this.value = PlatformUtil.wrap(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toRGBCode(Color color) {
|
||||||
|
return String.format("#%02X%02X%02X",
|
||||||
|
(int) (color.getRed() * 255),
|
||||||
|
(int) (color.getGreen() * 255),
|
||||||
|
(int) (color.getBlue() * 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillArea(VBox lineNumbers, InlineCssTextArea s) {
|
||||||
|
lineNumbers.getChildren().clear();
|
||||||
|
s.clear();
|
||||||
|
|
||||||
|
int number = 1;
|
||||||
|
for (CodeSnippet.Line line : value.getValue().lines()) {
|
||||||
|
var numberLabel = new Label(String.valueOf(number));
|
||||||
|
numberLabel.getStyleClass().add("line-number");
|
||||||
|
lineNumbers.getChildren().add(numberLabel);
|
||||||
|
|
||||||
|
for (var el : line.elements()) {
|
||||||
|
String hex = toRGBCode(el.color());
|
||||||
|
s.append(el.text(), "-fx-fill: " + hex + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean last = number == value.getValue().lines().size();
|
||||||
|
if (!last) {
|
||||||
|
s.appendText("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
number++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region createCopyButton(Region container) {
|
||||||
|
var button = new Button();
|
||||||
|
button.setGraphic(new FontIcon("mdoal-content_copy"));
|
||||||
|
button.setOnAction(e -> {
|
||||||
|
var string = new StringSelection(value.getValue().getRawString());
|
||||||
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
clipboard.setContents(string, string);
|
||||||
|
});
|
||||||
|
button.getStyleClass().add("copy");
|
||||||
|
button.getStyleClass().add("button-comp");
|
||||||
|
button.visibleProperty().bind(container.hoverProperty());
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompStructure<StackPane> createBase() {
|
||||||
|
var s = new InlineCssTextArea();
|
||||||
|
s.setEditable(false);
|
||||||
|
s.setBackground(null);
|
||||||
|
s.getStyleClass().add("code-snippet");
|
||||||
|
s.addEventFilter(ScrollEvent.ANY, e -> {
|
||||||
|
s.getParent().fireEvent(e);
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
var lineNumbers = new VBox();
|
||||||
|
lineNumbers.getStyleClass().add("line-numbers");
|
||||||
|
fillArea(lineNumbers, s);
|
||||||
|
value.addListener((c,o,n) -> {
|
||||||
|
PlatformUtil.runLaterIfNeeded(() -> {
|
||||||
|
fillArea(lineNumbers, s);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var spacer = new Region();
|
||||||
|
spacer.getStyleClass().add("spacer");
|
||||||
|
|
||||||
|
var content = new HBox(lineNumbers, spacer, s);
|
||||||
|
HBox.setHgrow(s, Priority.ALWAYS);
|
||||||
|
var container = new ScrollPane(content);
|
||||||
|
container.setFitToWidth(true);
|
||||||
|
|
||||||
|
var c = new StackPane(container);
|
||||||
|
c.getStyleClass().add("code-snippet-container");
|
||||||
|
|
||||||
|
var copyButton = createCopyButton(c);
|
||||||
|
var pane = new AnchorPane(copyButton);
|
||||||
|
AnchorPane.setTopAnchor(copyButton, 10.0);
|
||||||
|
AnchorPane.setRightAnchor(copyButton, 10.0);
|
||||||
|
c.getChildren().add(pane);
|
||||||
|
|
||||||
|
return new CompStructure<>(c);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public class CodeSnippets {
|
||||||
|
|
||||||
|
public static final ColorScheme LIGHT_MODE = new ColorScheme(
|
||||||
|
Color.valueOf("0033B3"),
|
||||||
|
Color.valueOf("000000"),
|
||||||
|
Color.valueOf("000000"),
|
||||||
|
Color.valueOf("067D17")
|
||||||
|
);
|
||||||
|
|
||||||
|
public static record ColorScheme(
|
||||||
|
Color keyword, Color identifier, Color type, Color string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,4 +18,12 @@ module io.xpipe.extension {
|
||||||
uses SupportedApplicationProvider;
|
uses SupportedApplicationProvider;
|
||||||
uses io.xpipe.extension.I18n;
|
uses io.xpipe.extension.I18n;
|
||||||
uses io.xpipe.extension.event.EventHandler;
|
uses io.xpipe.extension.event.EventHandler;
|
||||||
|
|
||||||
|
requires java.desktop;
|
||||||
|
requires org.fxmisc.richtext;
|
||||||
|
requires org.fxmisc.flowless;
|
||||||
|
requires org.fxmisc.undofx;
|
||||||
|
requires org.fxmisc.wellbehavedfx;
|
||||||
|
requires org.reactfx;
|
||||||
|
requires org.kordamp.ikonli.javafx;
|
||||||
}
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
.code-snippet {
|
||||||
|
-fx-padding: 0.3em 0.5em 0.3em 0.5em;
|
||||||
|
-fx-border-width: 0 0 0 1px;
|
||||||
|
-fx-border-color: -xp-border;
|
||||||
|
-fx-spacing: 0;
|
||||||
|
-fx-font-family: Monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-snippet-container {
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
-fx-border-color: -xp-border;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
-fx-padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number {
|
||||||
|
-fx-alignment: center-right;
|
||||||
|
-fx-font-family: Monospace;
|
||||||
|
-fx-text-fill: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers {
|
||||||
|
-fx-padding: 0.3em 0.2em 0.3em 0.5em;
|
||||||
|
-fx-background-color: #073B4C11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-snippet .line {
|
||||||
|
-fx-padding: 0.1em 0 0 0;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-snippet .spacer {
|
||||||
|
-fx-pref-width: 1px;
|
||||||
|
-fx-background-color: #073B4C43;
|
||||||
|
}
|
Loading…
Reference in a new issue