diff --git a/build.gradle.kts b/build.gradle.kts index 73f03bbd..35502755 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ val fxVersion by extra("11") allprojects { group = "space.kscience" - version = "0.3.0-dev-11" + version = "0.3.0-dev-12" } subprojects { diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index 77dd565e..36bd6b7b 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -51,7 +51,7 @@ kotlin { implementation(projects.visionforgeMarkdown) implementation(projects.visionforgeTables) implementation(projects.cernRootLoader) - implementation(projects.visionforgeJupyter.visionforgeJupyterCommon) + api(projects.visionforgeJupyter.visionforgeJupyterCommon) } } diff --git a/demo/playground/notebooks/common-demo.ipynb b/demo/playground/notebooks/common-demo.ipynb index 2e4b6110..78797545 100644 --- a/demo/playground/notebooks/common-demo.ipynb +++ b/demo/playground/notebooks/common-demo.ipynb @@ -2,77 +2,31 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { - "ExecuteTime": { - "end_time": "2023-07-20T06:12:13.305060400Z", - "start_time": "2023-07-20T06:12:13.011273800Z" - } + "tags": [] }, "outputs": [], "source": [ "@file:Repository(\"*mavenLocal\")\n", "@file:Repository(\"https://repo.kotlin.link\")\n", "@file:Repository(\"https://maven.pkg.jetbrains.space/spc/p/sci/dev\")\n", - "@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-11\")" + "@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-12\")\n", + "//import space.kscience.visionforge.jupyter.JupyterCommonIntegration\n", + "//\n", + "//val integration = JupyterCommonIntegration()\n", + "//USE(integration.getDefinitions(notebook).first())" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "ExecuteTime": { - "end_time": "2023-07-20T06:12:19.603077Z", - "start_time": "2023-07-20T06:12:19.419504300Z" - } + "tags": [] }, - "outputs": [ - { - "data": { - "text/html": "

Starting VisionForge server on http://localhost:7777

\n" - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "vf.startServer()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "ExecuteTime": { - "end_time": "2023-07-20T06:12:21.490069100Z", - "start_time": "2023-07-20T06:12:20.694188600Z" - } - }, - "outputs": [ - { - "data": { - "text/html": "
\n

AAA

\n
\n \n
\n
\n \n
\n
\n\n" - }, - "execution_count": 4, - "metadata": { - "text/html": { - "isolated": true - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "vf.page {\n", + "vf.fragment {\n", " h1 { +\"AAA\" }\n", " vision {\n", " solid {\n", @@ -100,24 +54,27 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "jupyter": { "outputs_hidden": false - } + }, + "tags": [] }, "outputs": [], "source": [ - "vf.stopServer()" + "Plotly.plot { \n", + " scatter{\n", + " x(1,2,3)\n", + " y(1,2,3)\n", + " }\n", + "}" ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } + "source": [] } ], "metadata": { diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt index 50c8a60a..8cfa515d 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt @@ -2,7 +2,6 @@ package space.kscience.visionforge.react import kotlinx.css.* import org.w3c.dom.Element -import org.w3c.dom.HTMLElement import react.* import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.request @@ -29,7 +28,7 @@ public val ThreeCanvasComponent: FC = fc("ThreeCanvasComponent useEffect(props.solid, props.options, elementRef) { if (canvas == null) { - val element = elementRef.current as? HTMLElement ?: error("Canvas element not found") + val element = elementRef.current ?: error("Canvas element not found") canvas = ThreeCanvas(three, element, props.options ?: Canvas3DOptions()) } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt index b733e6a4..ec3f3605 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt @@ -4,19 +4,28 @@ import kotlinx.html.FlowContent import kotlinx.html.TagConsumer import kotlinx.html.stream.createHTML -public typealias HtmlFragment = TagConsumer<*>.() -> Unit - -public fun HtmlFragment.renderToString(): String = createHTML().apply(this).finalize() - -public fun TagConsumer<*>.fragment(fragment: HtmlFragment) { - fragment() +/** + * A standalone HTML fragment + */ +public fun interface HtmlFragment { + public fun TagConsumer<*>.append() } -public fun FlowContent.fragment(fragment: HtmlFragment) { - fragment(consumer) -} +/** + * Convenience method to append fragment to the given [consumer] + */ +public fun HtmlFragment.appendTo(consumer: TagConsumer<*>): Unit = consumer.append() -public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = { - this@plus() - other() +/** + * Create a string from this [HtmlFragment] + */ +public fun HtmlFragment.renderToString(): String = createHTML().apply { append() }.finalize() + +public fun TagConsumer<*>.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(this) + +public fun FlowContent.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(consumer) + +public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = HtmlFragment { + this@plus.appendTo(this) + other.appendTo(this) } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt index d3c2427f..c02b6e64 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionRenderer.kt @@ -2,18 +2,17 @@ package space.kscience.visionforge.html import kotlinx.html.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.asName import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager -public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit - -@DFExperimental -public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content +public fun interface HtmlVisionFragment{ + public fun VisionTagConsumer<*>.append() +} +public fun HtmlVisionFragment.appendTo(consumer: VisionTagConsumer<*>): Unit = consumer.append() /** * Render a fragment in the given consumer and return a map of extracted visions @@ -84,7 +83,7 @@ public fun TagConsumer<*>.visionFragment( } } - fragment(consumer) + fragment.appendTo(consumer) } public fun FlowContent.visionFragment( diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt index f92454f0..1ea216c5 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionPage.kt @@ -17,7 +17,7 @@ public data class VisionPage( /** * Use a script with given [src] as a global header for all pages. */ - public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = { + public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = HtmlFragment{ script { type = "text/javascript" this.src = src @@ -28,7 +28,7 @@ public data class VisionPage( /** * Use css with the given stylesheet link as a global header for all pages. */ - public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = { + public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = HtmlFragment{ link { rel = "stylesheet" this.href = href @@ -36,7 +36,7 @@ public data class VisionPage( } } - public fun title(title:String): HtmlFragment = { + public fun title(title:String): HtmlFragment = HtmlFragment{ title(title) } } diff --git a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt index efc7cfd0..eaea0a4e 100644 --- a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt +++ b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt @@ -24,7 +24,7 @@ fun FlowContent.renderVisionFragment( renderer(name, vision, outputMeta) } } - fragment(consumer) + fragment.appendTo(consumer) return visionMap } @@ -35,7 +35,7 @@ private fun VisionOutput.base(block: VisionGroup.() -> Unit) = context.visionMan @DFExperimental class HtmlTagTest { - val fragment: HtmlVisionFragment = { + val fragment = HtmlVisionFragment{ div { h1 { +"Head" } vision("ddd") { diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt index 7d61ce24..c9146efd 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -286,8 +286,8 @@ public fun VisionClient.renderAllVisionsIn(element: Element) { /** * Render all visions in an element with a given [id] */ -public fun VisionClient.renderAllVisionsById(id: String): Unit = whenDocumentLoaded { - val element = getElementById(id) +public fun VisionClient.renderAllVisionsById(document: Document, id: String): Unit { + val element = document.getElementById(id) if (element != null) { renderAllVisionsIn(element) } else { diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt index 7d82da49..3796d436 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/HtmlVisionContext.kt @@ -34,10 +34,10 @@ public interface HtmlVisionContext : ContextAware { public typealias HtmlVisionContextFragment = context(HtmlVisionContext) TagConsumer<*>.() -> Unit -context(HtmlVisionContext) -public fun HtmlVisionFragment( - content: TagConsumer<*>.() -> Unit, -): HtmlVisionFragment = content +//context(HtmlVisionContext) +//public fun HtmlVisionFragment( +// content: TagConsumer<*>.() -> Unit, +//): HtmlVisionFragment = HtmlVisionFragment { } context(HtmlVisionContext) private fun TagConsumer.vision( diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt index 4c4ab90a..bf880e84 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/headers.kt @@ -91,14 +91,14 @@ internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String, */ internal fun fileScriptHeader( path: Path, -): HtmlFragment = { +): HtmlFragment = HtmlFragment{ script { type = "text/javascript" src = path.toString() } } -internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = { +internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = HtmlFragment{ script { type = "text/javascript" unsafe { @@ -113,7 +113,7 @@ internal fun fileCssHeader( cssPath: Path, resource: String, classLoader: ClassLoader, -): HtmlFragment = { +): HtmlFragment = HtmlFragment{ val relativePath = checkOrStoreFile(basePath, cssPath, resource, classLoader) link { rel = "stylesheet" diff --git a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt index ec31b7b4..e45dd9b5 100644 --- a/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt +++ b/visionforge-core/src/jvmMain/kotlin/space/kscience/visionforge/html/htmlExport.kt @@ -78,7 +78,7 @@ public fun VisionPage.makeFile( charset = "utf-8" } actualHeaders.values.forEach { - fragment(it) + appendFragment(it) } } body { diff --git a/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt index 24304136..84c6908b 100644 --- a/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt +++ b/visionforge-jupyter/src/jsMain/kotlin/VFNotebookClient.kt @@ -1,6 +1,7 @@ package space.kscience.visionforge.jupyter import kotlinx.browser.window +import org.w3c.dom.Document import org.w3c.dom.Element import space.kscience.dataforge.context.AbstractPlugin import space.kscience.dataforge.context.Context @@ -20,8 +21,8 @@ public class VFNotebookClient : AbstractPlugin() { client.renderAllVisionsIn(element) } - public fun renderAllVisionsById(id: String) { - client.renderAllVisionsById(id) + public fun renderAllVisionsById(document: Document, id: String) { + client.renderAllVisionsById(document, id) } public fun renderAllVisions() { diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt index 0a538a3e..d2acefde 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForge.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.html.* import kotlinx.html.stream.createHTML import org.jetbrains.kotlinx.jupyter.api.HTML +import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.ContextAware @@ -20,24 +21,33 @@ import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.html.HtmlVisionFragment -import space.kscience.visionforge.html.visionFragment +import space.kscience.visionforge.html.* import space.kscience.visionforge.server.VisionRoute import space.kscience.visionforge.server.serveVisionData import kotlin.coroutines.CoroutineContext import kotlin.random.Random import kotlin.random.nextUInt -internal fun TagConsumer<*>.renderScriptForId(id: String) { - script { - type = "text/javascript" - unsafe { +"VisionForge.renderAllVisionsById(\"$id\");" } - } + +@Suppress("FunctionName") +internal inline fun HTML(isolated: Boolean = false, block: TagConsumer<*>.() -> Unit): MimeTypedResult = + HTML(createHTML().apply(block).finalize(), isolated) + +internal fun KotlinKernelHost.displayHtml(block: TagConsumer<*>.() -> Unit) { + display(HTML(false, block), null) +} + +public enum class VisionForgeCompatibility { + JUPYTER, + JUPYTER_LAB, + DATALORE, + IDEA } /** * A handler class that includes a server and common utilities */ +@Suppress("ExtractKtorModule") public class VisionForge( public val visionManager: VisionManager, meta: Meta = Meta.EMPTY, @@ -51,29 +61,28 @@ public class VisionForge( private var engine: ApplicationEngine? = null - public var isolateFragments: Boolean = false + public var notebookMode: VisionForgeCompatibility = VisionForgeCompatibility.IDEA override val coroutineContext: CoroutineContext get() = context.coroutineContext - public fun legacyMode() { - isolateFragments = true - } public fun isServerRunning(): Boolean = engine != null - public fun html(block: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(createHTML().apply(block).finalize()) - public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name] - public fun startServer( + internal fun startServer( + kernel: KotlinKernelHost, host: String = getProperty("visionforge.host").string ?: "localhost", port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT, - ): MimeTypedResult = html { + ) { if (engine != null) { - p { - style = "color: red;" - +"Stopping current VisionForge server" + kernel.displayHtml { + p { + style = "color: red;" + +"Stopping current VisionForge server" + } } + } //val connector: EngineConnectorConfig = EngineConnectorConfig(host, port) @@ -83,66 +92,91 @@ public class VisionForge( install(WebSockets) }.start(false) - p { - style = "color: blue;" - +"Starting VisionForge server on http://$host:$port" + kernel.displayHtml { + p { + style = "color: blue;" + +"Starting VisionForge server on port $port" + } } } - public fun stopServer() { + internal fun stopServer(kernel: KotlinKernelHost) { engine?.apply { logger.info { "Stopping VisionForge server" } stop(1000, 2000) engine = null } - } - private fun produceHtmlString( - fragment: HtmlVisionFragment, - ): String = createHTML().apply { - val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]" - div { - this.id = id - val engine = engine - if (engine != null) { - //if server exist, serve dynamically - //server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment) - val cellRoute = "content-${counter++}" - - val collector: MutableMap = mutableMapOf() - - val url = engine.environment.connectors.first().let { - url { - protocol = URLProtocol.WS - host = it.host - port = it.port - pathSegments = listOf(cellRoute, "ws") - } - } - - engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector) - - visionFragment( - visionManager, - embedData = true, - updatesUrl = url, - onVisionRendered = { name, vision -> collector[name] = vision }, - fragment = fragment - ) - } else { - //if not, use static rendering - visionFragment(visionManager, fragment = fragment) + kernel.displayHtml { + p { + style = "color: red;" + +"VisionForge server stopped" } } - renderScriptForId(id) - }.finalize() + } - public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = - HTML(produceHtmlString(fragment), isolated ?: isolateFragments) + internal fun TagConsumer<*>.renderScriptForId(id: String, iframeIsolation: Boolean = false) { + script { + type = "text/javascript" + if (iframeIsolation) { + //language=JavaScript + unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" } + } else { + //language=JavaScript + unsafe { +"VisionForge.renderAllVisionsById(document, \"$id\");" } + } + } + } - public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body) - public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body) + + public fun produceHtml( + isolated: Boolean? = null, + fragment: HtmlVisionFragment, + ): MimeTypedResult { + val iframeIsolation = isolated + ?: (notebookMode == VisionForgeCompatibility.JUPYTER || notebookMode == VisionForgeCompatibility.DATALORE) + return HTML( + iframeIsolation + ) { + val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]" + div { + this.id = id + val engine = engine + if (engine != null) { + //if server exist, serve dynamically + //server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment) + val cellRoute = "content-${counter++}" + + val collector: MutableMap = mutableMapOf() + + val url = engine.environment.connectors.first().let { + url { + protocol = URLProtocol.WS + host = it.host + port = it.port + pathSegments = listOf(cellRoute, "ws") + } + } + + engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector) + + visionFragment( + visionManager, + embedData = true, + updatesUrl = url, + onVisionRendered = { name, vision -> collector[name] = vision }, + fragment = fragment + ) + } else { + //if not, use static rendering + visionFragment(visionManager, fragment = fragment) + } + } + renderScriptForId(id, iframeIsolation = iframeIsolation) + } + } public fun form(builder: FORM.() -> Unit): HtmlFormFragment = HtmlFormFragment("form[${counter++}]", builder = builder) } + diff --git a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt index 3029ef32..e7175dd0 100644 --- a/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt +++ b/visionforge-jupyter/src/jvmMain/kotlin/VisionForgeIntegration.kt @@ -1,8 +1,7 @@ package space.kscience.visionforge.jupyter import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML +import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult import org.jetbrains.kotlinx.jupyter.api.declare import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration import space.kscience.dataforge.context.Context @@ -31,11 +30,12 @@ public abstract class VisionForgeIntegration( onLoaded { declare("VisionForge" to handler, "vf" to handler) + handler.startServer(this) } onShutdown { - handler.stopServer() + handler.stopServer(this) } import( @@ -43,14 +43,14 @@ public abstract class VisionForgeIntegration( "space.kscience.visionforge.html.*", "space.kscience.visionforge.jupyter.*" ) - - render { fragment -> - handler.produceHtml(fragment = fragment) - } - - render { fragment -> - handler.produceHtml(fragment = fragment) - } +// +// render { fragment -> +// HTML(fragment.renderToString()) +// } +// +// render { fragment -> +// handler.produceHtml(fragment = fragment) +// } render { vision -> handler.produceHtml { @@ -59,13 +59,13 @@ public abstract class VisionForgeIntegration( } render { page -> - HTML(createHTML().apply { + HTML(true) { head { meta { charset = "utf-8" } page.pageHeaders.values.forEach { - fragment(it) + appendFragment(it) } } body { @@ -74,9 +74,11 @@ public abstract class VisionForgeIntegration( this.id = id visionFragment(visionManager, fragment = page.content) } - renderScriptForId(id) + with(handler) { + renderScriptForId(id, true) + } } - }.finalize(), true) + } } render { fragment -> @@ -87,7 +89,7 @@ public abstract class VisionForgeIntegration( +"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()." } } - fragment(fragment.formBody) + appendFragment(fragment.formBody) vision(fragment.vision) } } @@ -96,10 +98,25 @@ public abstract class VisionForgeIntegration( } } + +/** + * Create a fragment without a head to be embedded in the page + */ +@Suppress("UnusedReceiverParameter") +public fun VisionForge.html(body: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(false, body) + + +/** + * Create a fragment without a head to be embedded in the page + */ +public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeTypedResult = produceHtml(false, body) + + /** * Create a standalone page in the notebook */ public fun VisionForge.page( pageHeaders: Map = emptyMap(), - content: HtmlVisionFragment -): VisionPage = VisionPage(visionManager, pageHeaders, content) \ No newline at end of file + body: VisionTagConsumer<*>.() -> Unit, +): VisionPage = VisionPage(visionManager, pageHeaders, body) + diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt index 54f2e266..c326a816 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jvmMain/kotlin/JupyterCommonIntegration.kt @@ -6,8 +6,12 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.misc.DFExperimental import space.kscience.gdml.Gdml import space.kscience.plotly.Plot +import space.kscience.plotly.PlotlyPage +import space.kscience.plotly.StaticPlotlyRenderer import space.kscience.tables.* import space.kscience.visionforge.gdml.toVision +import space.kscience.visionforge.html.HtmlFragment +import space.kscience.visionforge.html.VisionPage import space.kscience.visionforge.markup.MarkupPlugin import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.plotly.asVision @@ -23,7 +27,7 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan override fun Builder.afterLoaded() { resources { - js("three") { + js("visionforge-common") { classPath("js/visionforge-jupyter-common.js") } } @@ -55,6 +59,24 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan vision { plot.asVision() } } } + + + render { plotlyPage -> + val headers = plotlyPage.headers.associate { plotlyFragment -> + plotlyFragment.hashCode().toString(16) to HtmlFragment { + plotlyFragment.visit(this) + } + + } + VisionPage(visionManager, headers) { + div{ + p { +"Plotly page renderer is not recommended in VisionForge, use `vf.page{}`" } + } + div { + plotlyPage.fragment.render.invoke(this, StaticPlotlyRenderer) + } + } + } } public companion object { diff --git a/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt b/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt index ef4eb39b..95d9e6f7 100644 --- a/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt +++ b/visionforge-threejs/visionforge-threejs-server/src/jvmTest/kotlin/space/kscience/visionforge/three/TestServerExtensions.kt @@ -3,6 +3,7 @@ package space.kscience.visionforge.three import kotlinx.html.stream.createHTML import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.VisionPage +import space.kscience.visionforge.html.appendTo import space.kscience.visionforge.html.importScriptHeader import kotlin.test.Test @@ -15,7 +16,7 @@ class TestServerExtensions { VisionPage.importScriptHeader( "js/visionforge-three.js", ResourceLocation.SYSTEM - ).invoke(this) + ).appendTo(this) }.finalize()