Implement retained target sessions, runtime sizing and Component navigation
This commit is contained in:
+19
-1
@@ -13,8 +13,26 @@ public record DesignerConfiguration(String schemaVersion, Map<String, Context> c
|
||||
String module,
|
||||
String provider,
|
||||
String defaultExample,
|
||||
List<String> examples) {
|
||||
List<String> examples,
|
||||
int requestTimeoutSeconds,
|
||||
int readinessTimeoutSeconds) {
|
||||
public Preview(
|
||||
String sourceSet,
|
||||
String module,
|
||||
String provider,
|
||||
String defaultExample,
|
||||
List<String> examples) {
|
||||
this(sourceSet, module, provider, defaultExample, examples, 30, 20);
|
||||
}
|
||||
|
||||
public Preview {
|
||||
if (requestTimeoutSeconds == 0) requestTimeoutSeconds = 30;
|
||||
if (readinessTimeoutSeconds == 0) readinessTimeoutSeconds = 20;
|
||||
if (requestTimeoutSeconds < 1
|
||||
|| requestTimeoutSeconds > 300
|
||||
|| readinessTimeoutSeconds < 1
|
||||
|| readinessTimeoutSeconds > requestTimeoutSeconds)
|
||||
throw new IllegalArgumentException("Invalid preview deadlines");
|
||||
examples = List.copyOf(examples);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.neitzel.fxdesigner.protocol;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** Logical target pixels; finite Double.MAX_VALUE represents an unbounded maximum. */
|
||||
public record LayoutMetrics(
|
||||
String occurrence,
|
||||
double minWidth,
|
||||
double prefWidth,
|
||||
double maxWidth,
|
||||
double minHeight,
|
||||
double prefHeight,
|
||||
double maxHeight,
|
||||
double baseline,
|
||||
String bias) {
|
||||
public LayoutMetrics {
|
||||
if (occurrence == null || occurrence.isBlank() || occurrence.length() > 4096)
|
||||
throw new IllegalArgumentException("Invalid occurrence");
|
||||
for (double value :
|
||||
new double[] {minWidth, prefWidth, maxWidth, minHeight, prefHeight, maxHeight, baseline})
|
||||
if (!Double.isFinite(value) || value < 0)
|
||||
throw new IllegalArgumentException("Invalid target measurement");
|
||||
if (!java.util.Set.of("NONE", "HORIZONTAL", "VERTICAL").contains(bias))
|
||||
throw new IllegalArgumentException("Invalid content bias");
|
||||
}
|
||||
|
||||
public Map<String, String> encode() {
|
||||
return Map.of(
|
||||
"occurrence",
|
||||
occurrence,
|
||||
"minWidth",
|
||||
"" + minWidth,
|
||||
"prefWidth",
|
||||
"" + prefWidth,
|
||||
"maxWidth",
|
||||
"" + maxWidth,
|
||||
"minHeight",
|
||||
"" + minHeight,
|
||||
"prefHeight",
|
||||
"" + prefHeight,
|
||||
"maxHeight",
|
||||
"" + maxHeight,
|
||||
"baseline",
|
||||
"" + baseline,
|
||||
"bias",
|
||||
bias);
|
||||
}
|
||||
|
||||
public static LayoutMetrics decode(Map<String, String> m) {
|
||||
return new LayoutMetrics(
|
||||
m.get("occurrence"),
|
||||
number(m, "minWidth"),
|
||||
number(m, "prefWidth"),
|
||||
number(m, "maxWidth"),
|
||||
number(m, "minHeight"),
|
||||
number(m, "prefHeight"),
|
||||
number(m, "maxHeight"),
|
||||
number(m, "baseline"),
|
||||
m.get("bias"));
|
||||
}
|
||||
|
||||
private static double number(Map<String, String> m, String key) {
|
||||
return Double.parseDouble(m.get(key));
|
||||
}
|
||||
}
|
||||
@@ -72,11 +72,23 @@ public final class ProtocolSession implements AutoCloseable {
|
||||
if (hello.preview().major() == 1
|
||||
&& supported.contains("render-rgba-v1")
|
||||
&& hello.capabilities().contains("render-rgba-v1")) usable.add(Operation.RENDER);
|
||||
if (hello.preview().major() == 1
|
||||
&& supported.contains("layout-v1")
|
||||
&& hello.capabilities().contains("layout-v1")) {
|
||||
usable.add(Operation.MEASURE);
|
||||
usable.add(Operation.OCCURRENCES);
|
||||
}
|
||||
if (hello.preview().major() == 1
|
||||
&& supported.contains("interactive-v1")
|
||||
&& hello.capabilities().contains("interactive-v1")) usable.add(Operation.INTERACTIVE);
|
||||
var requiredByEither = new HashSet<>(required);
|
||||
requiredByEither.addAll(hello.required());
|
||||
if ((requiredByEither.contains("discover-v1") && !usable.contains(Operation.DISCOVER))
|
||||
|| (requiredByEither.contains("render-rgba-v1") && !usable.contains(Operation.RENDER)))
|
||||
throw new IllegalArgumentException("Required capability version incompatible");
|
||||
if ((requiredByEither.contains("layout-v1") && !usable.contains(Operation.MEASURE))
|
||||
|| (requiredByEither.contains("interactive-v1") && !usable.contains(Operation.INTERACTIVE)))
|
||||
throw new IllegalArgumentException("Required capability version incompatible");
|
||||
operations = Set.copyOf(usable);
|
||||
state = State.READY;
|
||||
return operations;
|
||||
|
||||
@@ -73,7 +73,10 @@ public final class WireProtocol {
|
||||
|
||||
public enum Operation {
|
||||
DISCOVER,
|
||||
RENDER
|
||||
RENDER,
|
||||
MEASURE,
|
||||
OCCURRENCES,
|
||||
INTERACTIVE
|
||||
}
|
||||
|
||||
public record Identity(
|
||||
@@ -111,14 +114,14 @@ public final class WireProtocol {
|
||||
Objects.requireNonNull(operation);
|
||||
Objects.requireNonNull(identity);
|
||||
Objects.requireNonNull(payload);
|
||||
boolean render = operation == Operation.RENDER;
|
||||
boolean render = operation != Operation.DISCOVER;
|
||||
if (render
|
||||
&& (identity.width() == 0 || identity.height() == 0 || identity.revisions().isEmpty()))
|
||||
throw new IllegalArgumentException("Rendering needs dimensions and document revisions");
|
||||
if (!render
|
||||
&& (identity.width() != 0 || identity.height() != 0 || !identity.revisions().isEmpty()))
|
||||
throw new IllegalArgumentException("Discovery does not carry render identity");
|
||||
boolean image = render && kind == Kind.RESULT;
|
||||
boolean image = operation == Operation.RENDER && kind == Kind.RESULT;
|
||||
if (image
|
||||
? payload.length != (long) identity.width() * identity.height() * 4
|
||||
: payload.length > MAX_METADATA)
|
||||
|
||||
@@ -100,6 +100,16 @@
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"maxItems": 128
|
||||
},
|
||||
"requestTimeoutSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 300
|
||||
},
|
||||
"readinessTimeoutSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 300
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package de.neitzel.fxdesigner.protocol;
|
||||
|
||||
import static de.neitzel.fxdesigner.protocol.WireProtocol.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LayoutProtocolTest {
|
||||
@Test
|
||||
void negotiatedMetadataCarriesRenderIdentityButNotPixelLength() throws Exception {
|
||||
var token = ProtocolSession.newToken();
|
||||
try (var session =
|
||||
new ProtocolSession("s", 42, token, Set.of("layout-v1"), Set.of("layout-v1"))) {
|
||||
var operations =
|
||||
session.authenticate(
|
||||
InetAddress.getLoopbackAddress(),
|
||||
new Hello(
|
||||
"s",
|
||||
42,
|
||||
token,
|
||||
new Version(1, 0),
|
||||
new Version(1, 0),
|
||||
new Version(1, 0),
|
||||
Set.of("layout-v1"),
|
||||
Set.of()));
|
||||
assertEquals(Set.of(Operation.MEASURE, Operation.OCCURRENCES), operations);
|
||||
var metrics =
|
||||
new LayoutMetrics(
|
||||
"occurrence-1", 0, 100, Double.MAX_VALUE, 0, 40, Double.MAX_VALUE, 12, "HORIZONTAL");
|
||||
var packet =
|
||||
new Packet(
|
||||
Kind.RESULT,
|
||||
Operation.MEASURE,
|
||||
new Identity("s", "r", "c", "b", Map.of("file", 3L), 100, 50),
|
||||
PreviewMessages.encode(metrics.encode()));
|
||||
var out = new java.io.ByteArrayOutputStream();
|
||||
FrameCodec.write(out, packet);
|
||||
var decoded = (Packet) FrameCodec.read(new java.io.ByteArrayInputStream(out.toByteArray()));
|
||||
assertEquals(metrics, LayoutMetrics.decode(PreviewMessages.decode(decoded.payload())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidMeasurementsAndMissingCapabilitiesFail() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new LayoutMetrics("a", 0, Double.NaN, 1, 0, 1, 1, 0, "NONE"));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> new LayoutMetrics("a", 0, 1, 1, 0, 1, 1, -1, "NONE"));
|
||||
var token = ProtocolSession.newToken();
|
||||
try (var session =
|
||||
new ProtocolSession("s", 42, token, Set.of("layout-v1"), Set.of("layout-v1"))) {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
session.authenticate(
|
||||
InetAddress.getLoopbackAddress(),
|
||||
new Hello(
|
||||
"s",
|
||||
42,
|
||||
token,
|
||||
new Version(1, 0),
|
||||
new Version(1, 0),
|
||||
new Version(2, 0),
|
||||
Set.of("layout-v1"),
|
||||
Set.of())));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user