diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index f2e341003a5e1..bd2ebaee95e12 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.qute.deployment; -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import static java.util.stream.Collectors.toMap; @@ -9,7 +8,6 @@ import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Modifier; -import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -27,7 +25,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -78,8 +75,6 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.util.JandexUtil; -import io.quarkus.dev.console.DevConsoleManager; -import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.gizmo.ClassOutput; import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; import io.quarkus.qute.CheckedTemplate; @@ -116,20 +111,12 @@ import io.quarkus.qute.runtime.QuteRecorder; import io.quarkus.qute.runtime.QuteRecorder.QuteContext; import io.quarkus.qute.runtime.TemplateProducer; -import io.quarkus.qute.runtime.devmode.QuteDevConsoleRecorder; import io.quarkus.qute.runtime.extensions.CollectionTemplateExtensions; import io.quarkus.qute.runtime.extensions.ConfigTemplateExtensions; import io.quarkus.qute.runtime.extensions.MapTemplateExtensions; import io.quarkus.qute.runtime.extensions.NumberTemplateExtensions; import io.quarkus.qute.runtime.extensions.StringTemplateExtensions; import io.quarkus.qute.runtime.extensions.TimeTemplateExtensions; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.core.json.DecodeException; -import io.vertx.core.json.Json; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.RoutingContext; public class QuteProcessor { @@ -1235,54 +1222,6 @@ void initialize(BuildProducer syntheticBeans, QuteRecord .done()); } - @BuildStep - @Record(value = STATIC_INIT, optional = true) - DevConsoleRouteBuildItem invokeEndpoint(QuteDevConsoleRecorder recorder) { - recorder.setupRenderer(); - return new DevConsoleRouteBuildItem("preview", "POST", new Handler() { - @Override - public void handle(RoutingContext context) { - context.request().setExpectMultipart(true); - context.request().endHandler(new Handler() { - @Override - public void handle(Void ignore) { - MultiMap form = context.request().formAttributes(); - String templatePath = form.get("template-path"); - String testJsonData = form.get("template-data"); - String contentType = null; - String fileName = templatePath; - int slashIdx = fileName.lastIndexOf('/'); - if (slashIdx != -1) { - fileName = fileName.substring(slashIdx, fileName.length()); - } - int dotIdx = fileName.lastIndexOf('.'); - if (dotIdx != -1) { - String suffix = fileName.substring(dotIdx + 1, fileName.length()); - if (suffix.equalsIgnoreCase("json")) { - contentType = Variant.APPLICATION_JSON; - } else { - contentType = URLConnection.getFileNameMap().getContentTypeFor(fileName); - } - } - try { - BiFunction renderer = DevConsoleManager - .getGlobal(QuteDevConsoleRecorder.RENDER_HANDLER); - Object testData = Json.decodeValue(testJsonData); - testData = translate(testData); //translate it to JDK types - context.response().setStatusCode(200).putHeader(CONTENT_TYPE, contentType) - .end(renderer.apply(templatePath, testData)); - } catch (DecodeException e) { - context.response().setStatusCode(500).putHeader(CONTENT_TYPE, "text/plain; charset=UTF-8") - .end("Failed to parse JSON: " + e.getMessage()); - } catch (Throwable e) { - context.fail(e); - } - } - }); - } - }); - } - @BuildStep QualifierRegistrarBuildItem turnLocationIntoQualifier() { return new QualifierRegistrarBuildItem(new QualifierRegistrar() { @@ -1294,38 +1233,6 @@ public Map> getAdditionalQualifiers() { }); } - /** - * translates Json types to JDK types - * - * @param testData - * @return - */ - private Object translate(Object testData) { - if (testData instanceof JsonArray) { - return translate((JsonArray) testData); - } else if (testData instanceof JsonObject) { - return translate((JsonObject) testData); - } - return testData; - } - - private Object translate(JsonArray testData) { - List ret = new ArrayList<>(); - for (Object i : testData.getList()) { - ret.add(translate(i)); - } - return ret; - } - - private Object translate(JsonObject testData) { - Map map = new HashMap<>(); - Map data = testData.getMap(); - for (String i : testData.fieldNames()) { - map.put(i, translate(data.get(i))); - } - return map; - } - private static Type resolveType(AnnotationTarget member, Match match, IndexView index, TemplateExtensionMethodBuildItem extensionMethod) { Type matchType; @@ -1339,9 +1246,11 @@ private static Type resolveType(AnnotationTarget member, Match match, IndexView // If needed attempt to resolve the type variables using the declaring type if (Types.containsTypeVariable(matchType)) { // First get the type closure of the current match type - Set closure = Types.getTypeClosure(match.clazz, Types.buildResolvedMap( - match.getParameterizedTypeArguments(), match.getTypeParameters(), - new HashMap<>(), index), index); + Set closure = Types.getTypeClosure(match.clazz, + Types.buildResolvedMap( + match.getParameterizedTypeArguments(), match.getTypeParameters(), + new HashMap<>(), index), + index); DotName declaringClassName = null; Type extensionMatchBase = null; diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java index 277f8b26557e6..2ed8bce352807 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java @@ -24,7 +24,7 @@ public List getAnalysis() { /** * Analysis of a particular template found in the given path. */ - static class TemplateAnalysis { + public static final class TemplateAnalysis { // A user-defined id; may be null public final String id; diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/DevQuteTemplateInfo.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/DevQuteTemplateInfo.java index 6bb534b0e81bf..923a5e39d27b7 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/DevQuteTemplateInfo.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/DevQuteTemplateInfo.java @@ -1,16 +1,17 @@ package io.quarkus.qute.deployment.devconsole; -import java.util.List; import java.util.Map; public class DevQuteTemplateInfo implements Comparable { private final String path; - private final List variants; + // variant -> source + private final Map variants; private final String methodInfo; private final Map parameters; - public DevQuteTemplateInfo(String path, List variants, String methodInfo, Map parameters) { + public DevQuteTemplateInfo(String path, Map variants, String methodInfo, + Map parameters) { this.path = path; this.variants = variants; this.methodInfo = methodInfo; @@ -25,7 +26,7 @@ public String getPath() { return path; } - public List getVariants() { + public Map getVariants() { return variants; } @@ -37,4 +38,5 @@ public String getMethodInfo() { public int compareTo(DevQuteTemplateInfo o) { return path.compareTo(o.path); } + } diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java index 730c9fc40d5ff..9dee337d5a13c 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java @@ -1,38 +1,115 @@ package io.quarkus.qute.deployment.devconsole; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; + +import java.io.IOException; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.function.BiFunction; + +import org.jboss.logging.Logger; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; +import io.quarkus.qute.Variant; import io.quarkus.qute.deployment.CheckedTemplateBuildItem; import io.quarkus.qute.deployment.TemplatePathBuildItem; import io.quarkus.qute.deployment.TemplateVariantsBuildItem; +import io.quarkus.qute.runtime.devmode.QuteDevConsoleRecorder; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.core.json.DecodeException; +import io.vertx.core.json.Json; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; public class QuteDevConsoleProcessor { + private static final Logger LOG = Logger.getLogger(QuteDevConsoleProcessor.class); + + @BuildStep + @Record(value = STATIC_INIT, optional = true) + DevConsoleRouteBuildItem invokeEndpoint(QuteDevConsoleRecorder recorder) { + recorder.setupRenderer(); + return new DevConsoleRouteBuildItem("preview", "POST", new Handler() { + @Override + public void handle(RoutingContext context) { + context.request().setExpectMultipart(true); + context.request().endHandler(new Handler() { + @Override + public void handle(Void ignore) { + MultiMap form = context.request().formAttributes(); + String templatePath = form.get("template-select"); + String testJsonData = form.get("template-data"); + String contentType = null; + String fileName = templatePath; + int slashIdx = fileName.lastIndexOf('/'); + if (slashIdx != -1) { + fileName = fileName.substring(slashIdx, fileName.length()); + } + int dotIdx = fileName.lastIndexOf('.'); + if (dotIdx != -1) { + String suffix = fileName.substring(dotIdx + 1, fileName.length()); + if (suffix.equalsIgnoreCase("json")) { + contentType = Variant.APPLICATION_JSON; + } else { + contentType = URLConnection.getFileNameMap().getContentTypeFor(fileName); + } + } + try { + BiFunction renderer = DevConsoleManager + .getGlobal(QuteDevConsoleRecorder.RENDER_HANDLER); + Object testData = Json.decodeValue(testJsonData); + testData = translate(testData); //translate it to JDK types + context.response().setStatusCode(200).putHeader(CONTENT_TYPE, contentType) + .end(renderer.apply(templatePath, testData)); + } catch (DecodeException e) { + context.response().setStatusCode(500).putHeader(CONTENT_TYPE, "text/plain; charset=UTF-8") + .end("Failed to parse JSON: " + e.getMessage()); + } catch (Throwable e) { + context.fail(e); + } + } + }); + } + }); + } + @BuildStep(onlyIf = IsDevelopment.class) public DevConsoleTemplateInfoBuildItem collectTemplateInfo( List templatePaths, List checkedTemplates, TemplateVariantsBuildItem variants) { + DevQuteInfos quteInfos = new DevQuteInfos(); + for (Entry> entry : variants.getVariants().entrySet()) { CheckedTemplateBuildItem checkedTemplate = findCheckedTemplate(entry.getKey(), checkedTemplates); if (checkedTemplate != null) { quteInfos.addQuteTemplateInfo(new DevQuteTemplateInfo(checkedTemplate.templateId, - entry.getValue(), + processVariants(templatePaths, entry.getValue()), checkedTemplate.method.declaringClass().name() + "." + checkedTemplate.method.name() + "()", checkedTemplate.bindings)); } else { quteInfos.addQuteTemplateInfo(new DevQuteTemplateInfo(entry.getKey(), - entry.getValue(), + processVariants(templatePaths, entry.getValue()), null, null)); } } return new DevConsoleTemplateInfoBuildItem("devQuteInfos", quteInfos); - } private CheckedTemplateBuildItem findCheckedTemplate(String basePath, List checkedTemplates) { @@ -44,4 +121,57 @@ private CheckedTemplateBuildItem findCheckedTemplate(String basePath, List processVariants(List templatePaths, List variants) { + Map variantsMap = new HashMap<>(); + for (String variant : variants) { + String source = ""; + Path sourcePath = templatePaths.stream().filter(p -> p.getPath().equals(variant)) + .map(TemplatePathBuildItem::getFullPath).findFirst() + .orElse(null); + if (sourcePath != null) { + try { + byte[] content = Files.readAllBytes(sourcePath); + source = new String(content, StandardCharsets.UTF_8); + } catch (IOException e) { + LOG.warn("Unable to read the template from path: " + sourcePath, e); + } + } + source = source.replace("\n", "\\n"); + variantsMap.put(variant, source); + } + return variantsMap; + } + + /** + * translates Json types to JDK types + * + * @param testData + * @return + */ + private Object translate(Object testData) { + if (testData instanceof JsonArray) { + return translate((JsonArray) testData); + } else if (testData instanceof JsonObject) { + return translate((JsonObject) testData); + } + return testData; + } + + private Object translate(JsonArray testData) { + List ret = new ArrayList<>(); + for (Object i : testData.getList()) { + ret.add(translate(i)); + } + return ret; + } + + private Object translate(JsonObject testData) { + Map map = new HashMap<>(); + Map data = testData.getMap(); + for (String i : testData.fieldNames()) { + map.put(i, translate(data.get(i))); + } + return map; + } + } diff --git a/extensions/qute/deployment/src/main/resources/dev-templates/preview.html b/extensions/qute/deployment/src/main/resources/dev-templates/preview.html index befea543cec80..a0c2ae909fb7a 100644 --- a/extensions/qute/deployment/src/main/resources/dev-templates/preview.html +++ b/extensions/qute/deployment/src/main/resources/dev-templates/preview.html @@ -1,22 +1,108 @@ {#include main} {#title}Render Preview{/title} + {#style} + .CodeMirror { height: auto; border: 1px solid #ddd; } + #template-source-pre { margin: 0px; } + {/style} + {#styleref} + + + {/styleref} + {#body}
- - {#for template in info:devQuteInfos.templates} + {#let optionIndex=index} {#each template.variants} - + {/each} + {/let} {/for}
+
+ +
+
+

+            
+
+
- +
{/body} + + {#scriptref} + + + + + {/scriptref} + + {#script} + + const templateSelect = document.querySelector('#template-select'); + const templateDataMap = new Object(); + const templateSourceMap = new Object(); + + {! Init the template path -> initial data map !} + {#for template in info:devQuteInfos.templates} + {#let dataCount=count} + var testData{dataCount}; + {#if template.parameters} + testData{dataCount} = '{'; + {#each template.parameters} + testData{dataCount} += '\n // Template parameter {count}: {it.value.raw}\n'; + testData{dataCount} += ' "{it.key}" : null'; + {#if hasNext} + testData{dataCount} += ','; + {/if} + {/each} + testData{dataCount} += '\n}'; + {#else} + testData{dataCount} = '{}'; + {/if} + {#each template.variants} + templateDataMap['{it.key}'] = testData{dataCount}; + templateSourceMap['{it.key}'] = '{it.value}'; + {/each} + {/let} + {/for} + + const editor = CodeMirror.fromTextArea(document.querySelector('#template-data'), { + mode: { name: "javascript", json: true }, + styleActiveLine: true, + lineNumbers: true, + lineWrapping: true, + extraKeys: {"Ctrl-Space": "autocomplete"} + }); + + const initialTemplate = '{info:devQuteInfos.templates.get(0).variants.keySet.iterator.next}'; + editor.setValue(templateDataMap[initialTemplate]); + document.querySelector('#template-source-pre').innerHTML = templateSourceMap[initialTemplate]; + document.querySelector('#template-select').value = initialTemplate; + + editor.on("blur", function(codeMirror) { codeMirror.save(); }); + editor.refresh(); + + templateSelect.addEventListener('input', (event) => { + // Update the template data + const templateData = document.querySelector('#template-data'); + editor.setValue(templateDataMap[event.target.value]); + editor.refresh(); + // Update the list of expressions + document.querySelector('#template-source-pre').innerHTML = templateSourceMap[event.target.value]; + }); + + + {/script} {/include} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devconsole/DevConsolePreviewTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devconsole/DevConsolePreviewTest.java index ac03d0e190914..dc8c39f2fd4a2 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devconsole/DevConsolePreviewTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devconsole/DevConsolePreviewTest.java @@ -21,7 +21,7 @@ public class DevConsolePreviewTest { @Test public void testLoopPreview() { - RestAssured.with().formParam("template-path", "loop.txt").formParam("template-data", "{\"total\": [1 ,2 ,3]}") + RestAssured.with().formParam("template-select", "loop.txt").formParam("template-data", "{\"total\": [1 ,2 ,3]}") .post("q/dev/io.quarkus.quarkus-qute/preview") .then() .statusCode(200) diff --git a/extensions/vertx-http/deployment/pom.xml b/extensions/vertx-http/deployment/pom.xml index 1671e4ddc142b..a50d33017bba9 100644 --- a/extensions/vertx-http/deployment/pom.xml +++ b/extensions/vertx-http/deployment/pom.xml @@ -233,6 +233,18 @@ + + org.webjars + codemirror + ${webjar.codemirror.version} + jar + true + ${project.build.directory}/classes/dev-static/codemirror/mode/javascript + **/mode/javascript/**.js + + + + org.webjars codemirror