From 9a2a1a87457a6750c893f37e61fc972fb07d60d8 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Fri, 23 Dec 2022 10:29:08 +0100 Subject: [PATCH] Various small fixes --- .../io/xpipe/api/impl/DataSourceImpl.java | 34 ++-- .../api/impl/DataTableAccumulatorImpl.java | 18 +- .../io/xpipe/beacon/BeaconConnection.java | 12 ++ .../beacon/exchange/StoreStreamExchange.java | 31 ---- .../xpipe/beacon/util/QuietDialogHandler.java | 8 +- beacon/src/main/java/module-info.java | 1 - .../xpipe/core/charsetter/StreamCharset.java | 162 ++++++++++++++---- .../xpipe/core/impl/InternalStreamStore.java | 61 +++++++ .../java/io/xpipe/core/store/DataStore.java | 3 +- .../io/xpipe/core/util/DataStateProvider.java | 4 + .../java/io/xpipe/core/util/Identifiers.java | 25 +++ .../io/xpipe/extension/event/TrackEvent.java | 19 +- .../java/io/xpipe/extension/fxcomps/Comp.java | 4 + .../fxcomps/impl/DynamicOptionsComp.java | 58 +++++-- .../extension/fxcomps/impl/GrowPaneComp.java | 28 +++ .../extension/fxcomps/impl/PaneComp.java | 27 +++ .../fxcomps/impl/ToggleGroupComp.java | 8 +- .../extension/util/DynamicOptionsBuilder.java | 49 +++--- 18 files changed, 415 insertions(+), 137 deletions(-) delete mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java create mode 100644 core/src/main/java/io/xpipe/core/impl/InternalStreamStore.java create mode 100644 core/src/main/java/io/xpipe/core/util/Identifiers.java create mode 100644 extension/src/main/java/io/xpipe/extension/fxcomps/impl/GrowPaneComp.java create mode 100644 extension/src/main/java/io/xpipe/extension/fxcomps/impl/PaneComp.java diff --git a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java index 1994633a..4d3b904b 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java @@ -42,10 +42,8 @@ public abstract class DataSourceImpl implements DataSource { case RAW -> { yield new DataRawImpl(res.getId(), config, res.getInternalSource()); } - case COLLECTION -> throw new UnsupportedOperationException( - "Unimplemented case: " + res.getType()); - default -> throw new IllegalArgumentException( - "Unexpected value: " + res.getType()); + case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getType()); + default -> throw new IllegalArgumentException("Unexpected value: " + res.getType()); }; }); } @@ -64,17 +62,18 @@ public abstract class DataSourceImpl implements DataSource { public static DataSource create(DataSourceId id, String type, DataStore store) { if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) { - var res = XPipeApiConnection.execute(con -> { - var req = StoreStreamExchange.Request.builder().build(); - StoreStreamExchange.Response r = con.performOutputExchange(req, out -> { + store = XPipeApiConnection.execute(con -> { + var internal = con.createInternalStreamStore(); + var req = WriteStreamExchange.Request.builder() + .name(internal.getUuid().toString()) + .build(); + con.performOutputExchange(req, out -> { try (InputStream inputStream = s.openInput()) { inputStream.transferTo(out); } }); - return r; + return internal; }); - - store = res.getStore(); } var startReq = ReadExchange.Request.builder() @@ -96,14 +95,17 @@ public abstract class DataSourceImpl implements DataSource { } public static DataSource create(DataSourceId id, String type, InputStream in) { - var res = XPipeApiConnection.execute(con -> { - var req = StoreStreamExchange.Request.builder().build(); - StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out)); - return r; + var store = XPipeApiConnection.execute(con -> { + var internal = con.createInternalStreamStore(); + var req = WriteStreamExchange.Request.builder() + .name(internal.getUuid().toString()) + .build(); + con.performOutputExchange(req, out -> { + in.transferTo(out); + }); + return internal; }); - var store = res.getStore(); - var startReq = ReadExchange.Request.builder() .provider(type) .store(store) diff --git a/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java index 8b28837e..0dee378a 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java @@ -7,12 +7,15 @@ import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.api.util.TypeDescriptor; import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.exchange.ReadExchange; -import io.xpipe.beacon.exchange.StoreStreamExchange; +import io.xpipe.beacon.exchange.WriteStreamExchange; +import io.xpipe.beacon.exchange.cli.StoreAddExchange; +import io.xpipe.beacon.util.QuietDialogHandler; import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNodeAcceptor; import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.typed.TypedDataStreamWriter; +import io.xpipe.core.impl.InternalStreamStore; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceReference; @@ -25,13 +28,20 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator { private final XPipeApiConnection connection; private final TupleType type; private int rows; + private InternalStreamStore store; private TupleType writtenDescriptor; private OutputStream bodyOutput; public DataTableAccumulatorImpl(TupleType type) { this.type = type; connection = XPipeApiConnection.open(); - connection.sendRequest(StoreStreamExchange.Request.builder().build()); + + store = new InternalStreamStore(); + var addReq = StoreAddExchange.Request.builder().storeInput(store).name(store.getUuid().toString()).build(); + StoreAddExchange.Response addRes = connection.performSimpleExchange(addReq); + QuietDialogHandler.handle(addRes.getConfig(), connection); + + connection.sendRequest(WriteStreamExchange.Request.builder().name(store.getUuid().toString()).build()); bodyOutput = connection.sendBody(); } @@ -43,12 +53,12 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator { throw new BeaconException(e); } - StoreStreamExchange.Response res = connection.receiveResponse(); + WriteStreamExchange.Response res = connection.receiveResponse(); connection.close(); var req = ReadExchange.Request.builder() .target(id) - .store(res.getStore()) + .store(store) .provider("xpbt") .configureAll(false) .build(); diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java index 141c13e4..99cfa6e0 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java @@ -1,5 +1,9 @@ package io.xpipe.beacon; +import io.xpipe.beacon.exchange.cli.StoreAddExchange; +import io.xpipe.beacon.util.QuietDialogHandler; +import io.xpipe.core.impl.InternalStreamStore; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -169,6 +173,14 @@ public abstract class BeaconConnection implements AutoCloseable { } } + public InternalStreamStore createInternalStreamStore() { + var store = new InternalStreamStore(); + var addReq = StoreAddExchange.Request.builder().storeInput(store).name(store.getUuid().toString()).build(); + StoreAddExchange.Response addRes = performSimpleExchange(addReq); + QuietDialogHandler.handle(addRes.getConfig(), this); + return store; + } + private BeaconException unwrapException(Exception exception) { if (exception instanceof ServerException s) { return new BeaconException("An internal server error occurred", s); diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java deleted file mode 100644 index 42d87e40..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.xpipe.beacon.exchange; - -import io.xpipe.beacon.RequestMessage; -import io.xpipe.beacon.ResponseMessage; -import io.xpipe.core.impl.FileStore; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -/** - * Stores a stream of data in a storage. - */ -public class StoreStreamExchange implements MessageExchange { - - @Override - public String getId() { - return "storeStream"; - } - - @Jacksonized - @Builder - @Value - public static class Request implements RequestMessage {} - - @Jacksonized - @Builder - @Value - public static class Response implements ResponseMessage { - FileStore store; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java b/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java index 898f1f9f..33838c75 100644 --- a/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java +++ b/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java @@ -1,7 +1,7 @@ package io.xpipe.beacon.util; import io.xpipe.beacon.BeaconConnection; -import io.xpipe.beacon.ClientException; +import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.exchange.cli.DialogExchange; import io.xpipe.core.dialog.BaseQueryElement; import io.xpipe.core.dialog.ChoiceElement; @@ -13,7 +13,7 @@ import java.util.UUID; public class QuietDialogHandler { - public static void handle(DialogReference ref, BeaconConnection connection) throws ClientException { + public static void handle(DialogReference ref, BeaconConnection connection) { new QuietDialogHandler(ref, connection, Map.of()).handle(); } @@ -29,7 +29,7 @@ public class QuietDialogHandler { this.overrides = overrides; } - public void handle() throws ClientException { + public void handle() { String response = null; if (element instanceof ChoiceElement c) { @@ -45,7 +45,7 @@ public class QuietDialogHandler { .value(response) .build()); if (res.getElement() != null && element.equals(res.getElement())) { - throw new ClientException( + throw new BeaconException( "Invalid value for key " + res.getElement().toDisplayString()); } diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index b35b06a9..5b33ba8c 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -62,7 +62,6 @@ module io.xpipe.beacon { ListStoresExchange, DialogExchange, QueryDataSourceExchange, - StoreStreamExchange, EditExchange, RemoveEntryExchange, RemoveCollectionExchange, diff --git a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java index 373b06ed..f31a250a 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java +++ b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java @@ -1,49 +1,111 @@ package io.xpipe.core.charsetter; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; +import io.xpipe.core.util.Identifiers; import lombok.Value; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.stream.Stream; @Value public class StreamCharset { - public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null); - public static final StreamCharset UTF8_BOM = - new StreamCharset(StandardCharsets.UTF_8, new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}); - public static final StreamCharset UTF16_BE = new StreamCharset(StandardCharsets.UTF_16BE, null); - public static final StreamCharset UTF16_BE_BOM = - new StreamCharset(StandardCharsets.UTF_16BE, new byte[] {(byte) 0xFE, (byte) 0xFF}); - public static final StreamCharset UTF16_LE = new StreamCharset(StandardCharsets.UTF_16LE, null); - public static final StreamCharset UTF16_LE_BOM = - new StreamCharset(StandardCharsets.UTF_16LE, new byte[] {(byte) 0xFF, (byte) 0xFE}); + public static final StreamCharset UTF8 = + new StreamCharset(StandardCharsets.UTF_8, null, Identifiers.get("utf", "8")); - public static final StreamCharset UTF32_LE = new StreamCharset(Charset.forName("utf-32le"), null); - public static final StreamCharset UTF32_LE_BOM = - new StreamCharset(Charset.forName("utf-32le"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF}); - public static final StreamCharset UTF32_BE = new StreamCharset(Charset.forName("utf-32be"), null); - public static final StreamCharset UTF32_BE_BOM = - new StreamCharset(Charset.forName("utf-32be"), new byte[] {(byte) 0xFF, (byte) 0xFE, 0x00, 0x00, }); + public static final StreamCharset UTF8_BOM = new StreamCharset( + StandardCharsets.UTF_8, + new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}, + Identifiers.get("utf", "8", "bom")); + + // ====== + // UTF-16 + // ====== + + public static final StreamCharset UTF16_BE = + new StreamCharset(StandardCharsets.UTF_16BE, null, Identifiers.get("utf", "16", "be")); + + public static final StreamCharset UTF16_BE_BOM = new StreamCharset( + StandardCharsets.UTF_16BE, + new byte[] {(byte) 0xFE, (byte) 0xFF}, + Identifiers.get("utf", "16", "be", "bom")); + + public static final StreamCharset UTF16_LE = + new StreamCharset(StandardCharsets.UTF_16LE, null, Identifiers.get("utf", "16", "le")); + + public static final StreamCharset UTF16_LE_BOM = new StreamCharset( + StandardCharsets.UTF_16LE, + new byte[] {(byte) 0xFF, (byte) 0xFE}, + Identifiers.get("utf", "16", "le", "bom")); + + public static final StreamCharset UTF16 = + new StreamCharset(StandardCharsets.UTF_16, null, Identifiers.get("utf", "16")); + + public static final StreamCharset UTF16_BOM = new StreamCharset( + StandardCharsets.UTF_16, + ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN) + ? UTF16_BE_BOM.getByteOrderMark() + : UTF16_LE_BOM.getByteOrderMark(), + Identifiers.get("utf", "16", "bom")); + + // ====== + // UTF-32 + // ====== + + public static final StreamCharset UTF32_LE = + new StreamCharset(Charset.forName("utf-32le"), null, Identifiers.get("utf", "32", "le")); + + public static final StreamCharset UTF32_LE_BOM = new StreamCharset( + Charset.forName("utf-32le"), + new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF}, + Identifiers.get("utf", "32", "le", "bom")); + + public static final StreamCharset UTF32_BE = + new StreamCharset(Charset.forName("utf-32be"), null, Identifiers.get("utf", "32", "be")); + + public static final StreamCharset UTF32_BE_BOM = new StreamCharset( + Charset.forName("utf-32be"), + new byte[] { + (byte) 0xFF, (byte) 0xFE, 0x00, 0x00, + }, + Identifiers.get("utf", "32", "be", "bom")); public static final List COMMON = List.of( UTF8, UTF8_BOM, - UTF16_BE, - UTF16_BE_BOM, - UTF16_LE, - UTF16_LE_BOM, - new StreamCharset(StandardCharsets.US_ASCII, null), - new StreamCharset(StandardCharsets.ISO_8859_1, null), - new StreamCharset(Charset.forName("Windows-1251"), null), - new StreamCharset(Charset.forName("Windows-1252"), null)); - private static final List RARE_KNOWN = List.of(UTF32_LE, UTF32_LE_BOM, UTF32_BE, UTF32_BE_BOM); + UTF16, + UTF16_BOM, + new StreamCharset( + StandardCharsets.US_ASCII, + null, + Identifiers.join(Identifiers.get("ascii"), Identifiers.get("us", "ascii"))), + new StreamCharset( + StandardCharsets.ISO_8859_1, + null, + Identifiers.join( + Identifiers.get("iso", "8859"), + Identifiers.get("iso", "8859", "1"), + Identifiers.get("8859"), + Identifiers.get("8859", "1"))), + new StreamCharset( + Charset.forName("Windows-1251"), + null, + Identifiers.join(Identifiers.get("windows", "1251"), Identifiers.get("1251"))), + new StreamCharset( + Charset.forName("Windows-1252"), + null, + Identifiers.join(Identifiers.get("windows", "1252"), Identifiers.get("1252")))); + + private static final List RARE_NAMED = + List.of(UTF16_LE, UTF16_LE_BOM, UTF16_BE, UTF16_BE_BOM, UTF32_LE, UTF32_LE_BOM, UTF32_BE, UTF32_BE_BOM); + public static final List RARE = Stream.concat( - RARE_KNOWN.stream(), + RARE_NAMED.stream(), Charset.availableCharsets().values().stream() .filter(charset -> !charset.equals(StandardCharsets.UTF_16) && !charset.equals(Charset.forName("utf-32")) @@ -52,31 +114,59 @@ public class StreamCharset { && !charset.displayName().endsWith("-BOM") && COMMON.stream() .noneMatch(c -> c.getCharset().equals(charset)) - && RARE_KNOWN.stream() + && RARE_NAMED.stream() .noneMatch(c -> c.getCharset().equals(charset))) - .map(charset -> new StreamCharset(charset, null))) + .map(charset -> new StreamCharset( + charset, + null, + Identifiers.get(charset.name().split("-"))))) .toList(); + + public static final List ALL = Stream.concat(COMMON.stream(), RARE.stream()).toList(); + + + Charset charset; byte[] byteOrderMark; + List names; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof StreamCharset that)) { + return false; + } + return charset.equals(that.charset) && Arrays.equals(byteOrderMark, that.byteOrderMark); + } + + @Override + public int hashCode() { + int result = Objects.hash(charset); + result = 31 * result + Arrays.hashCode(byteOrderMark); + return result; + } public static StreamCharset get(Charset charset, boolean byteOrderMark) { - return Stream.concat(COMMON.stream(), RARE.stream()) + return ALL.stream() .filter(streamCharset -> streamCharset.getCharset().equals(charset) && streamCharset.hasByteOrderMark() == byteOrderMark) .findFirst() .orElseThrow(); } - @JsonCreator public static StreamCharset get(String s) { - var byteOrderMark = s.endsWith("-bom"); - var charset = Charset.forName(s.substring(0, s.length() - (byteOrderMark ? 4 : 0))); - return StreamCharset.get(charset, byteOrderMark); + var found = ALL.stream().filter(streamCharset -> streamCharset.getNames().contains(s.toLowerCase(Locale.ROOT))).findFirst(); + if (found.isEmpty()) { + throw new IllegalArgumentException("Unknown charset name: " + s); + } + + return found.get(); } - @JsonValue public String toString() { - return getCharset().name().toLowerCase(Locale.ROOT) + (hasByteOrderMark() ? "-bom" : ""); + return getNames().get(0); } public boolean hasByteOrderMark() { diff --git a/core/src/main/java/io/xpipe/core/impl/InternalStreamStore.java b/core/src/main/java/io/xpipe/core/impl/InternalStreamStore.java new file mode 100644 index 00000000..4fc37f41 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/InternalStreamStore.java @@ -0,0 +1,61 @@ +package io.xpipe.core.impl; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.store.DataFlow; +import io.xpipe.core.store.StreamDataStore; +import io.xpipe.core.util.DataStateProvider; +import io.xpipe.core.util.JacksonizedValue; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@JsonTypeName("internalStream") +@SuperBuilder +@Jacksonized +@Getter +public class InternalStreamStore extends JacksonizedValue implements StreamDataStore { + + private final UUID uuid; + + public InternalStreamStore() { + this.uuid = UUID.randomUUID(); + } + + @Override + public DataFlow getFlow() { + return DataFlow.INPUT_OUTPUT; + } + + @Override + public Optional determineDefaultName() { + return Optional.of(uuid.toString()); + } + + private Path getFile() { + return DataStateProvider.get().getInternalStreamStore(uuid); + } + + @Override + public Optional determineLastModified() throws IOException { + return Optional.of(Files.getLastModifiedTime(getFile()).toInstant()); + } + + @Override + public InputStream openInput() throws Exception { + return Files.newInputStream(getFile()); + } + + @Override + public OutputStream openOutput() throws Exception { + return Files.newOutputStream(getFile()); + } +} diff --git a/core/src/main/java/io/xpipe/core/store/DataStore.java b/core/src/main/java/io/xpipe/core/store/DataStore.java index 1fa1a139..ec75ece6 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStore.java +++ b/core/src/main/java/io/xpipe/core/store/DataStore.java @@ -5,6 +5,7 @@ import io.xpipe.core.impl.StdinDataStore; import io.xpipe.core.impl.StdoutDataStore; import io.xpipe.core.source.DataSource; +import java.io.IOException; import java.time.Instant; import java.util.Optional; @@ -101,7 +102,7 @@ public interface DataStore { /** * Determines the last modified of this data store if this data store supports it. */ - default Optional determineLastModified() { + default Optional determineLastModified() throws IOException { return Optional.empty(); } } diff --git a/core/src/main/java/io/xpipe/core/util/DataStateProvider.java b/core/src/main/java/io/xpipe/core/util/DataStateProvider.java index 24d8e400..a0e16b89 100644 --- a/core/src/main/java/io/xpipe/core/util/DataStateProvider.java +++ b/core/src/main/java/io/xpipe/core/util/DataStateProvider.java @@ -2,7 +2,9 @@ package io.xpipe.core.util; import io.xpipe.core.store.DataStore; +import java.nio.file.Path; import java.util.ServiceLoader; +import java.util.UUID; import java.util.function.Supplier; public abstract class DataStateProvider { @@ -22,4 +24,6 @@ public abstract class DataStateProvider { public abstract void putState(DataStore store, String key, Object value); public abstract T getState(DataStore store, String key, Class c, Supplier def); + + public abstract Path getInternalStreamStore(UUID id); } diff --git a/core/src/main/java/io/xpipe/core/util/Identifiers.java b/core/src/main/java/io/xpipe/core/util/Identifiers.java new file mode 100644 index 00000000..d965ef57 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/Identifiers.java @@ -0,0 +1,25 @@ +package io.xpipe.core.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class Identifiers { + + @SafeVarargs + public static List join(List... s) { + return Arrays.stream(s).flatMap(Collection::stream).toList(); + } + + public static List get(String... s) { + return nameAlternatives(Arrays.asList(s)); + } + + private static List nameAlternatives(List split) { + return List.of( + String.join("", split), + String.join(" ", split), + String.join("_", split), + String.join("-", split)); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java b/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java index b365534d..4939d6c4 100644 --- a/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java +++ b/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java @@ -8,11 +8,16 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Builder @Getter public class TrackEvent { + public static TrackEventBuilder storage() { + return TrackEvent.builder().category("storage"); + } + private final Thread thread = Thread.currentThread(); private final Instant instant = Instant.now(); private String type; @@ -107,10 +112,17 @@ public class TrackEvent { if (tags.size() > 0) { s.append(" {\n"); for (var e : tags.entrySet()) { + var value = e.toString().contains("\n") + ? "\n" + + (e.toString() + .lines() + .map(line -> " | " + line) + .collect(Collectors.joining("\n"))) + : e.toString(); s.append(" ") .append(e.getKey()) .append("=") - .append(e.getValue()) + .append(value) .append("\n"); } s.append("}"); @@ -120,6 +132,11 @@ public class TrackEvent { public static class TrackEventBuilder { + public TrackEventBuilder trace() { + this.type("trace"); + return this; + } + public TrackEventBuilder windowCategory() { this.category("window"); return this; diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/Comp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/Comp.java index 94bc3c16..d0e206a1 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/Comp.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/Comp.java @@ -47,6 +47,10 @@ public abstract class Comp> { return (T) this; } + public Comp visible(ObservableValue o) { + return apply(struc -> struc.get().visibleProperty().bind(o)); + } + public Comp disable(ObservableValue o) { return apply(struc -> struc.get().disableProperty().bind(o)); } diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/DynamicOptionsComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/DynamicOptionsComp.java index 0b3b03e5..2f61fbcc 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/DynamicOptionsComp.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/DynamicOptionsComp.java @@ -3,21 +3,19 @@ package io.xpipe.extension.fxcomps.impl; import io.xpipe.extension.fxcomps.Comp; import io.xpipe.extension.fxcomps.CompStructure; import io.xpipe.extension.fxcomps.SimpleCompStructure; +import io.xpipe.extension.fxcomps.util.PlatformThread; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; import javafx.geometry.Orientation; 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 javafx.scene.layout.*; import java.util.ArrayList; import java.util.List; -public class DynamicOptionsComp extends Comp> { +public class DynamicOptionsComp extends Comp> { private final List entries; private final boolean wrap; @@ -27,12 +25,28 @@ public class DynamicOptionsComp extends Comp> { this.wrap = wrap; } + public Entry queryEntry(String key) { + return entries.stream() + .filter(entry -> entry.key != null && entry.key.equals(key)) + .findAny() + .orElseThrow(); + } + @Override - public CompStructure createBase() { - var flow = new FlowPane(Orientation.HORIZONTAL); - flow.setAlignment(Pos.CENTER); - flow.setHgap(14); - flow.setVgap(7); + public CompStructure createBase() { + Pane pane; + if (wrap) { + var content = new FlowPane(Orientation.HORIZONTAL); + content.setAlignment(Pos.CENTER); + content.setHgap(14); + content.setVgap(7); + pane = content; + } else { + var content = new VBox(); + content.setAlignment(Pos.CENTER); + content.setSpacing(7); + pane = content; + } var nameRegions = new ArrayList(); var compRegions = new ArrayList(); @@ -41,30 +55,38 @@ public class DynamicOptionsComp extends Comp> { var line = new HBox(); line.setFillHeight(true); if (!wrap) { - line.prefWidthProperty().bind(flow.widthProperty()); + line.prefWidthProperty().bind(pane.widthProperty()); } line.setSpacing(8); + Region compRegion = null; + if (entry.comp() != null) { + compRegion = entry.comp().createRegion(); + } + if (entry.name() != null) { var name = new Label(); name.textProperty().bind(entry.name()); name.prefHeightProperty().bind(line.heightProperty()); name.setMinWidth(Region.USE_PREF_SIZE); name.setAlignment(Pos.CENTER_LEFT); + if (compRegion != null) { + name.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty())); + name.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty())); + } nameRegions.add(name); line.getChildren().add(name); } if (entry.comp() != null) { - var r = entry.comp().createRegion(); - compRegions.add(r); - line.getChildren().add(r); + compRegions.add(compRegion); + line.getChildren().add(compRegion); if (!wrap) { - HBox.setHgrow(r, Priority.ALWAYS); + HBox.setHgrow(compRegion, Priority.ALWAYS); } } - flow.getChildren().add(line); + pane.getChildren().add(line); } if (wrap) { @@ -101,12 +123,12 @@ public class DynamicOptionsComp extends Comp> { nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding)); } - return new SimpleCompStructure<>(flow); + return new SimpleCompStructure<>(pane); } public List getEntries() { return entries; } - public record Entry(ObservableValue name, Comp comp) {} + public record Entry(String key, ObservableValue name, Comp comp) {} } diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/GrowPaneComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/GrowPaneComp.java new file mode 100644 index 00000000..27ac8bd9 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/GrowPaneComp.java @@ -0,0 +1,28 @@ +package io.xpipe.extension.fxcomps.impl; + +import io.xpipe.extension.fxcomps.Comp; +import io.xpipe.extension.fxcomps.CompStructure; +import io.xpipe.extension.fxcomps.SimpleCompStructure; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; + +import java.util.List; + +public class GrowPaneComp extends Comp> { + + private final List> comps; + + public GrowPaneComp(List> comps) { + this.comps = List.copyOf(comps); + } + + @Override + public CompStructure createBase() { + var pane = new BorderPane(); + for (var c : comps) { + pane.setCenter(c.createRegion()); + } + pane.setPickOnBounds(false); + return new SimpleCompStructure<>(pane); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/PaneComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/PaneComp.java new file mode 100644 index 00000000..844bbb86 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/PaneComp.java @@ -0,0 +1,27 @@ +package io.xpipe.extension.fxcomps.impl; + +import io.xpipe.extension.fxcomps.Comp; +import io.xpipe.extension.fxcomps.CompStructure; +import io.xpipe.extension.fxcomps.SimpleCompStructure; +import javafx.scene.layout.Pane; + +import java.util.List; + +public class PaneComp extends Comp> { + + private final List> comps; + + public PaneComp(List> comps) { + this.comps = List.copyOf(comps); + } + + @Override + public CompStructure createBase() { + var pane = new Pane(); + for (var c : comps) { + pane.getChildren().add(c.createRegion()); + } + pane.setPickOnBounds(false); + return new SimpleCompStructure<>(pane); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ToggleGroupComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ToggleGroupComp.java index a02acb20..d7c088d6 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ToggleGroupComp.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ToggleGroupComp.java @@ -43,10 +43,14 @@ public class ToggleGroupComp extends Comp> { box.getChildren().add(b); b.setToggleGroup(group); value.addListener((c, o, n) -> { - PlatformThread.runLaterIfNeeded(() -> b.setSelected(entry.equals(n))); + PlatformThread.runLaterIfNeeded(() -> { + if (entry.getKey().equals(n)) { + group.selectToggle(b); + } + }); }); if (entry.getKey().equals(value.getValue())) { - b.setSelected(true); + group.selectToggle(b); } } diff --git a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java index c5441a2a..9ed0ac6f 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java @@ -5,7 +5,6 @@ import io.xpipe.core.charsetter.StreamCharset; import io.xpipe.core.util.SecretValue; import io.xpipe.extension.I18n; import io.xpipe.extension.fxcomps.Comp; -import io.xpipe.extension.fxcomps.CompStructure; import io.xpipe.extension.fxcomps.impl.*; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; @@ -43,12 +42,14 @@ public class DynamicOptionsBuilder { } public DynamicOptionsBuilder addTitle(String titleKey) { - return addTitle(I18n.observable(titleKey)); + entries.add(new DynamicOptionsComp.Entry( + titleKey, null, new LabelComp(I18n.observable(titleKey)).styleClass("title-header"))); + return this; } public DynamicOptionsBuilder addTitle(ObservableValue title) { entries.add(new DynamicOptionsComp.Entry( - null, Comp.of(() -> new Label(title.getValue())).styleClass("title-header"))); + null, null, Comp.of(() -> new Label(title.getValue())).styleClass("title-header"))); return this; } @@ -69,7 +70,7 @@ public class DynamicOptionsBuilder { map.put(e, I18n.observable("extension." + e.getId())); } var comp = new ChoiceComp<>(prop, map, false); - entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp)); + entries.add(new DynamicOptionsComp.Entry("newLine", I18n.observable("extension.newLine"), comp)); props.add(prop); return this; } @@ -77,7 +78,7 @@ public class DynamicOptionsBuilder { public DynamicOptionsBuilder addCharacter( Property prop, ObservableValue name, Map> names) { var comp = new CharChoiceComp(prop, names, null); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } @@ -88,7 +89,7 @@ public class DynamicOptionsBuilder { Map> names, ObservableValue customName) { var comp = new CharChoiceComp(prop, names, customName); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } @@ -96,7 +97,7 @@ public class DynamicOptionsBuilder { public DynamicOptionsBuilder addToggle(String nameKey, Property prop) { var comp = new ToggleGroupComp<>(prop, new SimpleObjectProperty<>(Map.of(Boolean.TRUE, I18n.observable("extension.yes"), Boolean.FALSE, I18n.observable("extension.no")))); - entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); props.add(prop); return this; } @@ -104,7 +105,7 @@ public class DynamicOptionsBuilder { public DynamicOptionsBuilder addToggle( Property prop, ObservableValue name, Map> names) { var comp = new ToggleGroupComp<>(prop, new SimpleObjectProperty<>(names)); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } @@ -113,7 +114,7 @@ public class DynamicOptionsBuilder { Property prop, ObservableValue name, Map> names, boolean includeNone ) { var comp = new ChoiceComp<>(prop, names, includeNone); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } @@ -122,49 +123,49 @@ public class DynamicOptionsBuilder { Property prop, ObservableValue name, ObservableValue>> names, boolean includeNone ) { var comp = new ChoiceComp<>(prop, names, includeNone); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } public DynamicOptionsBuilder addCharset(Property prop) { var comp = new CharsetChoiceComp(prop); - entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.charset"), comp)); + entries.add(new DynamicOptionsComp.Entry("charset", I18n.observable("extension.charset"), comp)); props.add(prop); return this; } public DynamicOptionsBuilder addStringArea(String nameKey, Property prop, boolean lazy) { var comp = new TextAreaComp(prop, lazy); - entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); props.add(prop); return this; } public DynamicOptionsBuilder addString(String nameKey, Property prop) { var comp = new TextFieldComp(prop); - entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); props.add(prop); return this; } public DynamicOptionsBuilder addString(String nameKey, Property prop, boolean lazy) { var comp = new TextFieldComp(prop, lazy); - entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); props.add(prop); return this; } public DynamicOptionsBuilder addString(ObservableValue name, Property prop) { var comp = new TextFieldComp(prop); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } public DynamicOptionsBuilder addString(ObservableValue name, Property prop, boolean lazy) { var comp = new TextFieldComp(prop, lazy); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } @@ -178,11 +179,13 @@ public class DynamicOptionsBuilder { } public DynamicOptionsBuilder addComp(String nameKey, Comp comp, Property prop) { - return addComp(I18n.observable(nameKey), comp, prop); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); + if (prop != null) props.add(prop); + return this; } public DynamicOptionsBuilder addComp(ObservableValue name, Comp comp, Property prop) { - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); if (prop != null) props.add(prop); return this; } @@ -193,21 +196,21 @@ public class DynamicOptionsBuilder { public DynamicOptionsBuilder addSecret(ObservableValue name, Property prop) { var comp = new SecretFieldComp(prop); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } public DynamicOptionsBuilder addInteger(ObservableValue name, Property prop) { var comp = new IntFieldComp(prop); - entries.add(new DynamicOptionsComp.Entry(name, comp)); + entries.add(new DynamicOptionsComp.Entry(null, name, comp)); props.add(prop); return this; } public DynamicOptionsBuilder addInteger(String nameKey, Property prop) { var comp = new IntFieldComp(prop); - entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); + entries.add(new DynamicOptionsComp.Entry(nameKey, I18n.observable(nameKey), comp)); props.add(prop); return this; } @@ -239,12 +242,12 @@ public class DynamicOptionsBuilder { return this; } - public Comp> buildComp() { + public DynamicOptionsComp buildComp() { if (title != null) { entries.add( 0, new DynamicOptionsComp.Entry( - null, Comp.of(() -> new Label(title.getValue())).styleClass("title-header"))); + null, null, Comp.of(() -> new Label(title.getValue())).styleClass("title-header"))); } return new DynamicOptionsComp(entries, wrap); }