diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index aa9e7c07..9a845c50 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -29,7 +29,10 @@ kotlin { jvm { withJava() compilations.all { - kotlinOptions.jvmTarget = "11" + kotlinOptions{ + jvmTarget = "11" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" + } } testRuns["test"].executionTask.configure { useJUnitPlatform() @@ -51,33 +54,34 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - api(project(":visionforge-solid")) - api(project(":visionforge-gdml")) - api(project(":visionforge-plotly")) - api(projects.visionforge.visionforgeMarkdown) - api(projects.visionforge.cernRootLoader) + implementation(projects.visionforgeSolid) + implementation(projects.visionforgeGdml) + implementation(projects.visionforgePlotly) + implementation(projects.visionforgeMarkdown) + implementation(projects.cernRootLoader) + implementation(projects.jupyter.jupyterBase) } } val jsMain by getting { dependencies { - api(project(":ui:ring")) - api(project(":visionforge-threejs")) + implementation(projects.ui.ring) + implementation(projects.visionforgeThreejs) } } val jvmMain by getting { dependencies { - api(project(":visionforge-server")) - api("ch.qos.logback:logback-classic:1.2.3") - api("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") + implementation(projects.visionforgeServer) + implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") } } } } -tasks.withType { +val processJupyterApiResources by tasks.getting(org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask::class){ libraryProducers = listOf("space.kscience.visionforge.examples.VisionForgePlayGroundForJupyter") } -tasks.findByName("shadowJar")?.dependsOn("processJupyterApiResources") \ No newline at end of file +tasks.findByName("shadowJar")?.dependsOn(processJupyterApiResources) \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt b/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt index b820cf71..6cbe311d 100644 --- a/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt +++ b/demo/playground/src/jvmMain/kotlin/VisionForgePlayGroundForJupyter.kt @@ -1,41 +1,27 @@ package space.kscience.visionforge.examples -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML -import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration import org.jetbrains.kotlinx.jupyter.api.libraries.resources 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.visionforge.Vision import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.html.HtmlVisionFragment -import space.kscience.visionforge.html.Page -import space.kscience.visionforge.html.embedAndRenderVisionFragment +import space.kscience.visionforge.jupyter.JupyterPluginBase import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.plotly.asVision import space.kscience.visionforge.solid.Solids -import space.kscience.visionforge.visionManager @DFExperimental -public class VisionForgePlayGroundForJupyter : JupyterIntegration() { - - private val context = Context("VisionForge") { +internal class VisionForgePlayGroundForJupyter : JupyterPluginBase( + Context("VisionForge") { plugin(Solids) plugin(PlotlyPlugin) } +) { - private var counter = 0 - - private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply { - embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment) - }.finalize() - - override fun Builder.onLoaded() { - + override fun Builder.afterLoaded() { resources { - js("VisionForge"){ + js("VisionForge") { classPath("js/visionforge-playground.js") } } @@ -44,41 +30,20 @@ public class VisionForgePlayGroundForJupyter : JupyterIntegration() { "space.kscience.gdml.*", "space.kscience.plotly.*", "space.kscience.plotly.models.*", - "kotlinx.html.*", "space.kscience.visionforge.solid.*", - "space.kscience.visionforge.html.Page", - "space.kscience.visionforge.html.page" ) + render { gdmlModel -> - val fragment = HtmlVisionFragment { + handler.produceHtml { vision(gdmlModel.toVision()) } - HTML(produceHtmlVisionString(fragment)) - } - - render { vision -> - val fragment = HtmlVisionFragment { - vision(vision) - } - - HTML(produceHtmlVisionString(fragment)) } render { plot -> - val fragment = HtmlVisionFragment { + handler.produceHtml { vision(plot.asVision()) } - - HTML(produceHtmlVisionString(fragment)) - } - - render { fragment -> - HTML(createHTML().apply(fragment.visit).finalize()) - } - - render { page -> - HTML(page.render(createHTML()), true) } } diff --git a/demo/playground/src/jvmMain/kotlin/formServer.kt b/demo/playground/src/jvmMain/kotlin/formServer.kt index bdc95017..c5276554 100644 --- a/demo/playground/src/jvmMain/kotlin/formServer.kt +++ b/demo/playground/src/jvmMain/kotlin/formServer.kt @@ -5,7 +5,7 @@ import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.fetch import space.kscience.dataforge.names.asName import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.html.visionOfForm +import space.kscience.visionforge.html.formFragment import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.three.server.close import space.kscience.visionforge.three.server.openInBrowser @@ -18,7 +18,7 @@ fun main() { val server = visionManager.serve { useScript("js/visionforge-playground.js") page { - val form = visionOfForm("form") { + val form = formFragment("form") { label { htmlFor = "fname" +"First name:" diff --git a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt index e4111afc..6d9ff46c 100644 --- a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt +++ b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt @@ -9,6 +9,7 @@ import space.kscience.visionforge.html.scriptHeader import space.kscience.visionforge.makeFile import space.kscience.visionforge.three.server.VisionServer import space.kscience.visionforge.three.server.useScript +import space.kscience.visionforge.visionManager import java.awt.Desktop import java.nio.file.Path @@ -25,7 +26,7 @@ public fun Context.makeVisionFile( show: Boolean = true, content: VisionTagConsumer<*>.() -> Unit ): Unit { - val actualPath = page(title, content = content).makeFile(path) { actualPath -> + val actualPath = visionManager.page(title, content = content).makeFile(path) { actualPath -> mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath)) } if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI()) diff --git a/jupyter/visionforge-jupyter-base/build.gradle.kts b/jupyter/jupyter-base/build.gradle.kts similarity index 75% rename from jupyter/visionforge-jupyter-base/build.gradle.kts rename to jupyter/jupyter-base/build.gradle.kts index 3e5f671d..0b406250 100644 --- a/jupyter/visionforge-jupyter-base/build.gradle.kts +++ b/jupyter/jupyter-base/build.gradle.kts @@ -9,12 +9,12 @@ kotlin { sourceSets { commonMain{ dependencies{ - api(projects.visionforge.visionforgeCore) + api(projects.visionforgeCore) } } jvmMain { dependencies { - implementation(project(":visionforge-server")) + api(projects.visionforgeServer) } } } diff --git a/jupyter/jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt b/jupyter/jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt new file mode 100644 index 00000000..b0422516 --- /dev/null +++ b/jupyter/jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt @@ -0,0 +1,62 @@ +package space.kscience.visionforge.jupyter + +import kotlinx.html.stream.createHTML +import org.jetbrains.kotlinx.jupyter.api.HTML +import org.jetbrains.kotlinx.jupyter.api.declare +import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.visionforge.Vision +import space.kscience.visionforge.html.HtmlFormFragment +import space.kscience.visionforge.html.HtmlVisionFragment +import space.kscience.visionforge.html.Page +import space.kscience.visionforge.html.fragment + +@DFExperimental +public abstract class JupyterPluginBase(final override val context: Context) : JupyterIntegration(), ContextAware { + + protected val handler: VisionForgeServerHandler = VisionForgeServerHandler(context) + + protected abstract fun Builder.afterLoaded() + + final override fun Builder.onLoaded() { + + onLoaded { + declare("visionForge" to handler) + } + + onShutdown { + handler.stopServer() + } + + import( + "kotlinx.html.*", + "space.kscience.visionforge.html.*" + ) + + + render { fragment -> + handler.produceHtml(fragment = fragment) + } + + render { vision -> + handler.produceHtml { + vision(vision) + } + + } + + render { page -> + HTML(page.render(createHTML()), true) + } + + render { fragment -> + handler.produceHtml { + fragment(fragment.formBody) + vision(fragment.vision) + } + } + afterLoaded() + } +} diff --git a/jupyter/jupyter-base/src/jvmMain/kotlin/VisionForgeServerHandler.kt b/jupyter/jupyter-base/src/jvmMain/kotlin/VisionForgeServerHandler.kt new file mode 100644 index 00000000..a01bdc43 --- /dev/null +++ b/jupyter/jupyter-base/src/jvmMain/kotlin/VisionForgeServerHandler.kt @@ -0,0 +1,81 @@ +package space.kscience.visionforge.jupyter + +import io.ktor.server.engine.ApplicationEngine +import kotlinx.html.FORM +import kotlinx.html.p +import kotlinx.html.stream.createHTML +import kotlinx.html.style +import org.jetbrains.kotlinx.jupyter.api.HTML +import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.context.info +import space.kscience.dataforge.context.logger +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.int +import space.kscience.dataforge.meta.string +import space.kscience.visionforge.html.HtmlFormFragment +import space.kscience.visionforge.html.HtmlFragment +import space.kscience.visionforge.html.HtmlVisionFragment +import space.kscience.visionforge.html.visionFragment +import space.kscience.visionforge.three.server.VisionServer +import space.kscience.visionforge.three.server.serve +import space.kscience.visionforge.visionManager + +public class VisionForgeServerHandler(override val context: Context) : ContextAware { + private var counter = 0 + + private var engine: ApplicationEngine? = null + private var server: VisionServer? = null + + public var isolateFragments: Boolean = false + + public fun legacyMode() { + isolateFragments = true + } + + public fun startServer( + host: String = context.properties["visionforge.host"].string ?: "localhost", + port: Int = context.properties["visionforge.port"].int ?: VisionServer.DEFAULT_PORT, + configuration: VisionServer.() -> Unit = {}, + ): HtmlFragment { + engine?.stop(1000, 2000) + engine = context.visionManager.serve(host, port) { + configuration() + server = this + }.start() + return { + if(server!= null){ + p { + style = "color: red;" + +"Stopping current VisionForge server" + } + } + p { + style = "color: blue;" + +"Starting VisionForge server on http://$host:$port" + } + } + } + + public fun stopServer() { + engine?.apply { + logger.info { "Stopping VisionForge server" } + }?.stop(1000, 2000) + } + + private fun produceHtmlString( + fragment: HtmlVisionFragment, + ): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment) + ?: createHTML().apply { + visionFragment(context.visionManager, fragment = fragment) + }.finalize() + + public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = + HTML(produceHtmlString(fragment), isolated ?: isolateFragments) + + public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body) + public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body) + + public fun form(builder: FORM.() -> Unit): HtmlFormFragment = HtmlFormFragment("form[${counter++}]", builder = builder) +} \ No newline at end of file diff --git a/jupyter/visionforge-jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt b/jupyter/visionforge-jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt deleted file mode 100644 index e071ac45..00000000 --- a/jupyter/visionforge-jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt +++ /dev/null @@ -1,77 +0,0 @@ -package space.kscience.visionforge.jupyter - -import io.ktor.server.cio.CIO -import io.ktor.server.engine.ApplicationEngine -import io.ktor.server.engine.embeddedServer -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML -import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.ContextAware -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.int -import space.kscience.dataforge.meta.string -import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.Vision -import space.kscience.visionforge.html.HtmlVisionFragment -import space.kscience.visionforge.html.Page -import space.kscience.visionforge.html.embedAndRenderVisionFragment -import space.kscience.visionforge.three.server.VisionServer -import space.kscience.visionforge.three.server.visionServer -import space.kscience.visionforge.visionManager - -private const val DEFAULT_VISIONFORGE_PORT = 88898 - -@DFExperimental -public abstract class JupyterPluginBase( - override val context: Context, -) : JupyterIntegration(), ContextAware { - - private var counter = 0 - - private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply { - embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment) - }.finalize() - - private var engine: ApplicationEngine? = null - private var server: VisionServer? = null - - override fun Builder.onLoaded() { - - onLoaded { - val host = context.properties["visionforge.host"].string ?: "localhost" - val port = context.properties["visionforge.port"].int ?: DEFAULT_VISIONFORGE_PORT - engine = context.embeddedServer(CIO, port, host) { - server = visionServer(context) - }.start() - } - - onShutdown { - engine?.stop(1000, 1000) - engine = null - server = null - } - - import( - "kotlinx.html.*", - "space.kscience.visionforge.html.Page", - "space.kscience.visionforge.html.page", - ) - - render { vision -> - val server = this@JupyterPluginBase.server - if (server == null) { - HTML(produceHtmlVisionString { vision(vision) }) - } else { - val route = "route.${counter++}" - HTML(server.createHtmlAndServe(route,route, emptyList()){ - vision(vision) - }) - } - } - - render { page -> - //HTML(page.render(createHTML()), true) - } - } -} diff --git a/jupyter/visionforge-jupyter-gdml/build.gradle.kts b/jupyter/visionforge-jupyter-gdml/build.gradle.kts index c2c77516..afb08473 100644 --- a/jupyter/visionforge-jupyter-gdml/build.gradle.kts +++ b/jupyter/visionforge-jupyter-gdml/build.gradle.kts @@ -32,18 +32,19 @@ kotlin { sourceSets { commonMain { dependencies { - api(project(":visionforge-solid")) + implementation(projects.visionforgeSolid) + implementation(projects.jupyter.jupyterBase) } } jvmMain { dependencies { - implementation(project(":visionforge-gdml")) + implementation(projects.visionforgeGdml) } } jsMain { dependencies { - api(project(":visionforge-threejs")) - implementation(project(":ui:ring")) + implementation(projects.visionforgeThreejs) + implementation(projects.ui.ring) } } diff --git a/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt b/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt index 2aa1873b..9dc95d6d 100644 --- a/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt +++ b/jupyter/visionforge-jupyter-gdml/src/jvmMain/kotlin/GdmlForJupyter.kt @@ -1,34 +1,21 @@ package space.kscience.visionforge.gdml.jupyter -import kotlinx.html.stream.createHTML -import org.jetbrains.kotlinx.jupyter.api.HTML -import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration import org.jetbrains.kotlinx.jupyter.api.libraries.resources import space.kscience.dataforge.context.Context import space.kscience.dataforge.misc.DFExperimental import space.kscience.gdml.Gdml -import space.kscience.visionforge.Vision import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.html.HtmlVisionFragment -import space.kscience.visionforge.html.Page -import space.kscience.visionforge.html.embedAndRenderVisionFragment +import space.kscience.visionforge.jupyter.JupyterPluginBase import space.kscience.visionforge.solid.Solids -import space.kscience.visionforge.visionManager @DFExperimental -internal class GdmlForJupyter : JupyterIntegration() { - - private val context = Context("GDML") { +internal class GdmlForJupyter : JupyterPluginBase( + Context("GDML") { plugin(Solids) } +) { - private var counter = 0 - - private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply { - embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment) - }.finalize() - - override fun Builder.onLoaded() { + override fun Builder.afterLoaded() { resources { js("three") { @@ -38,23 +25,11 @@ internal class GdmlForJupyter : JupyterIntegration() { import( "space.kscience.gdml.*", - "kotlinx.html.*", - "space.kscience.visionforge.solid.*", - "space.kscience.visionforge.html.Page", - "space.kscience.visionforge.html.page", "space.kscience.visionforge.gdml.jupyter.*" ) - render { vision -> - HTML(produceHtmlVisionString { vision(vision) }) - } - render { gdmlModel -> - HTML(produceHtmlVisionString { vision(gdmlModel.toVision()) }) - } - - render { page -> - HTML(page.render(createHTML()), true) + handler.produceHtml { vision(gdmlModel.toVision()) } } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index dc65b446..6d3e5961 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,6 @@ include( ":demo:playground", ":demo:plotly-fx", ":demo:js-playground", - ":jupyter:visionforge-jupyter-base", + ":jupyter:jupyter-base", ":jupyter:visionforge-jupyter-gdml" ) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionFragment.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt similarity index 64% rename from visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionFragment.kt rename to visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt index 9be18263..343a29ce 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlVisionFragment.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/HtmlFragment.kt @@ -3,7 +3,6 @@ package space.kscience.visionforge.html import kotlinx.html.FlowContent import kotlinx.html.TagConsumer import kotlinx.html.stream.createHTML -import space.kscience.dataforge.misc.DFExperimental public typealias HtmlFragment = TagConsumer<*>.() -> Unit @@ -15,9 +14,4 @@ public fun TagConsumer<*>.fragment(fragment: HtmlFragment) { public fun FlowContent.fragment(fragment: HtmlFragment) { fragment(consumer) -} - -public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit - -@DFExperimental -public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content \ No newline at end of file +} \ 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 7bfe5012..6ec98684 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 @@ -6,26 +6,47 @@ import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager +import kotlin.random.Random +import kotlin.random.nextUInt -public fun TagConsumer<*>.embedVisionFragment( +public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit + +@DFExperimental +public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content + + +internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById" + + +/** + * Render a fragment in the given consumer and return a map of extracted visions + * @param manager a VisionManager used for serialization + * @param embedData embed Vision initial state in the HTML + * @param fetchDataUrl fetch data after first render from given url + * @param fetchUpdatesUrl receive push updates from the server at given url + * @param idPrefix a prefix to be used before vision ids + * @param renderScript if true add rendering script after the fragment + */ +public fun TagConsumer<*>.visionFragment( manager: VisionManager, embedData: Boolean = true, - fetchData: String? = null, - fetchUpdates: String? = null, + fetchDataUrl: String? = null, + fetchUpdatesUrl: String? = null, idPrefix: String? = null, + renderScript: Boolean = true, fragment: HtmlVisionFragment, ): Map { val visionMap = HashMap() - val consumer = object : VisionTagConsumer(this@embedVisionFragment, manager, idPrefix) { + val consumer = object : VisionTagConsumer(this@visionFragment, manager, idPrefix) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { visionMap[name] = vision // Toggle update mode - fetchUpdates?.let { + fetchUpdatesUrl?.let { attributes[OUTPUT_CONNECT_ATTRIBUTE] = it } - fetchData?.let { + fetchDataUrl?.let { attributes[OUTPUT_FETCH_ATTRIBUTE] = it } @@ -40,39 +61,36 @@ public fun TagConsumer<*>.embedVisionFragment( } } } - fragment(consumer) - return visionMap -} - -public fun FlowContent.embedVisionFragment( - manager: VisionManager, - embedData: Boolean = true, - fetchDataUrl: String? = null, - fetchUpdatesUrl: String? = null, - idPrefix: String? = null, - fragment: HtmlVisionFragment, -): Map = consumer.embedVisionFragment(manager, embedData, fetchDataUrl, fetchUpdatesUrl, idPrefix, fragment) - - -internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById" - -public fun TagConsumer<*>.embedAndRenderVisionFragment( - manager: VisionManager, - id: Any, - embedData: Boolean = true, - fetchData: String? = null, - fetchUpdates: String? = null, - idPrefix: String? = null, - fragment: HtmlVisionFragment, -) { - div { + if (renderScript) { + val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]" div { - this.id = id.toString() - embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment) + this.id = id + fragment(consumer) } script { type = "text/javascript" unsafe { +"window.${RENDER_FUNCTION_NAME}(\"$id\");" } } + } else { + fragment(consumer) } -} \ No newline at end of file + return visionMap +} + +public fun FlowContent.visionFragment( + manager: VisionManager, + embedData: Boolean = true, + fetchDataUrl: String? = null, + fetchUpdatesUrl: String? = null, + idPrefix: String? = null, + renderSctipt: Boolean = true, + fragment: HtmlVisionFragment, +): Map = consumer.visionFragment( + manager, + embedData, + fetchDataUrl, + fetchUpdatesUrl, + idPrefix, + renderSctipt, + fragment +) \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/Page.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/Page.kt index 6e807e92..96cae1ce 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/Page.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/Page.kt @@ -1,12 +1,11 @@ package space.kscience.visionforge.html import kotlinx.html.* -import space.kscience.dataforge.context.Context import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.visionManager +import space.kscience.visionforge.VisionManager public data class Page( - public val context: Context, + public val visionManager: VisionManager, public val title: String, public val headers: Map, public val content: HtmlVisionFragment, @@ -22,14 +21,14 @@ public data class Page( title(this@Page.title) } body { - embedVisionFragment(context.visionManager, fragment = content) + visionFragment(visionManager, fragment = content) } }.finalize() } @DFExperimental -public fun Context.page( +public fun VisionManager.page( title: String = "VisionForge page", vararg headers: Pair, content: HtmlVisionFragment, diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtmlForm.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtmlForm.kt index a3070e58..0ef6f54e 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtmlForm.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtmlForm.kt @@ -7,6 +7,7 @@ import kotlinx.html.id import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.node @Serializable @@ -17,10 +18,29 @@ public class VisionOfHtmlForm( public var values: Meta? by meta.node() } -public inline fun TagConsumer.visionOfForm(id: String, crossinline builder: FORM.() -> Unit): VisionOfHtmlForm { - form { - this.id = id - builder() +public class HtmlFormFragment internal constructor( + public val vision: VisionOfHtmlForm, + public val formBody: HtmlFragment, +){ + public val values: Meta? get() = vision.values + public operator fun get(valueName: String): Meta? = values?.get(valueName) +} + +public fun HtmlFormFragment(id: String? = null, builder: FORM.() -> Unit): HtmlFormFragment { + val realId = id ?: "form[${builder.hashCode().toUInt()}]" + return HtmlFormFragment(VisionOfHtmlForm(realId)) { + form { + this.id = realId + builder() + } } - return VisionOfHtmlForm(id) +} + +public fun TagConsumer.formFragment( + id: String? = null, + builder: FORM.() -> Unit, +): VisionOfHtmlForm { + val formFragment = HtmlFormFragment(id, builder) + fragment(formFragment.formBody) + return formFragment.vision } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt index c047222b..c9bd6857 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt @@ -42,7 +42,7 @@ public abstract class VisionTagConsumer( private val idPrefix: String? = null, ) : TagConsumer by root { - public open fun resolveId(name: Name): String = (idPrefix ?: "output:") + name.toString() + public open fun resolveId(name: Name): String = (idPrefix ?: "output") + "[$name]" /** * Render a vision inside the output fragment 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 c236a19d..2bd6dc84 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -164,7 +164,7 @@ public class VisionClient : AbstractPlugin() { val endpoint = resolveEndpoint(element) logger.info { "Vision server is resolved to $endpoint" } URL(endpoint).apply { - pathname += "/vision" + pathname += "/data" } } else { URL(attr.value) diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt index 0d67a821..c6d87b19 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt @@ -94,7 +94,7 @@ public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer event.preventDefault() val formData = FormData(form).toMeta() - console.log(formData.toString()) + //console.log(formData.toString()) vision.values = formData false } diff --git a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt index 5065c765..b5b78ba0 100644 --- a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt @@ -4,12 +4,10 @@ import io.ktor.application.* import io.ktor.features.CORS import io.ktor.features.CallLogging import io.ktor.html.respondHtml -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode +import io.ktor.http.* import io.ktor.http.cio.websocket.Frame import io.ktor.http.content.resources import io.ktor.http.content.static -import io.ktor.http.withCharset import io.ktor.response.respond import io.ktor.response.respondText import io.ktor.routing.* @@ -25,8 +23,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.html.* import kotlinx.html.stream.createHTML -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.fetch import space.kscience.dataforge.meta.* import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name @@ -34,7 +30,10 @@ import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionChange import space.kscience.visionforge.VisionManager import space.kscience.visionforge.flowChanges -import space.kscience.visionforge.html.* +import space.kscience.visionforge.html.HtmlFragment +import space.kscience.visionforge.html.HtmlVisionFragment +import space.kscience.visionforge.html.fragment +import space.kscience.visionforge.html.visionFragment import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE import java.awt.Desktop import java.net.URI @@ -43,12 +42,13 @@ import kotlin.time.Duration.Companion.milliseconds /** * A ktor plugin container with given [routing] + * @param serverUrl a server url including root route */ public class VisionServer internal constructor( private val visionManager: VisionManager, - private val application: Application, - private val rootRoute: String, -) : Configurable, CoroutineScope by application { + private val serverUrl: Url, + private val root: Route, +) : Configurable, CoroutineScope by root.application { override val meta: ObservableMutableMeta = MutableMeta() /** @@ -74,7 +74,7 @@ public class VisionServer internal constructor( /** * Connect to server to get pushes. The address of the server is embedded in the tag. Default: `true` */ - public var dataConnect: Boolean by meta.boolean(true, Name.parse("data.connect")) + public var dataUpdate: Boolean by meta.boolean(true, Name.parse("data.update")) /** * a list of headers that should be applied to all pages @@ -90,6 +90,7 @@ public class VisionServer internal constructor( private fun HTML.visionPage( title: String, + pagePath: String, headers: List, visionFragment: HtmlVisionFragment, ): Map { @@ -106,11 +107,10 @@ public class VisionServer internal constructor( } body { //Load the fragment and remember all loaded visions - visionMap = embedVisionFragment( + visionMap = visionFragment( manager = visionManager, embedData = true, - fetchDataUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE, - fetchUpdatesUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE, + fetchUpdatesUrl = "$serverUrl$pagePath/ws", fragment = visionFragment ) } @@ -122,7 +122,7 @@ public class VisionServer internal constructor( * Server a map of visions without providing explicit html page for them */ @OptIn(DFExperimental::class) - internal fun serveVisions(route: Route, visions: Map): Unit = route { + private fun serveVisions(route: Route, visions: Map): Unit = route { application.log.info("Serving visions $visions at $route") //Update websocket @@ -157,7 +157,7 @@ public class VisionServer internal constructor( } } //Plots in their json representation - get("vision") { + get("data") { val name: String = call.request.queryParameters["name"] ?: error("Vision name is not defined in parameters") @@ -174,47 +174,40 @@ public class VisionServer internal constructor( } } - /** - * Create a static html page and serve visions produced in the process - */ - @DFExperimental - public fun createHtmlAndServe( - route: String, - title: String, - headers: List, - visionFragment: HtmlVisionFragment, - ): String { - val htmlString = createHTML().apply { - html { - visionPage(title, headers, visionFragment).also { - serveVisions(route, it) - } - } - }.finalize() - - return htmlString - } /** * Serve visions in a given [route] without providing a page template */ public fun serveVisions(route: String, visions: Map): Unit { - application.routing { - route(rootRoute) { - route(route) { - serveVisions(this, visions) - } - } + root.route(route) { + serveVisions(this, visions) } } /** - * Serve a page, potentially containing any number of visions at a given [route] with given [headers]. + * Compile a fragment to string and serve visions from it + */ + public fun serveVisionsFromFragment( + route: String, + fragment: HtmlVisionFragment, + ): String = createHTML().apply { + val visions = visionFragment( + visionManager, + embedData = true, + fetchUpdatesUrl = "$serverUrl$route/ws", + renderScript = true, + fragment = fragment + ) + serveVisions(route, visions) + }.finalize() + + /** + * Serve a page, potentially containing any number of visions at a given [pagePath] with given [headers]. * */ public fun page( - route: String = DEFAULT_PAGE, - title: String = "VisionForge server page '$route'", + pagePath: String = DEFAULT_PAGE, + title: String = "VisionForge server page '$pagePath'", headers: List = emptyList(), visionFragment: HtmlVisionFragment, ) { @@ -223,35 +216,34 @@ public class VisionServer internal constructor( val cachedHtml: String? = if (cacheFragments) { //Create and cache page html and map of visions createHTML(true).html { - visions.putAll(visionPage(title, headers, visionFragment)) + visions.putAll(visionPage(title, pagePath, headers, visionFragment)) } } else { null } - application.routing { - route(rootRoute) { - route(route) { - serveVisions(this, visions) - //filled pages - get { - if (cachedHtml == null) { - //re-create html and vision list on each call - call.respondHtml { - visions.clear() - visions.putAll(visionPage(title, headers, visionFragment)) - } - } else { - //Use cached html - call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8)) - } + root.route(pagePath) { + serveVisions(this, visions) + //filled pages + get { + if (cachedHtml == null) { + //re-create html and vision list on each call + call.respondHtml { + visions.clear() + visions.putAll(visionPage(title, pagePath, headers, visionFragment)) } + } else { + //Use cached html + call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8)) } } } + + } public companion object { + public const val DEFAULT_PORT: Int = 7777 public const val DEFAULT_PAGE: String = "/" public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval") } @@ -286,7 +278,11 @@ public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() - /** * Attach VisionForge server application to given server */ -public fun Application.visionServer(context: Context, route: String = DEFAULT_PAGE): VisionServer { +public fun Application.visionServer( + visionManager: VisionManager, + webServerUrl: Url, + path: String = DEFAULT_PAGE, +): VisionServer { if (featureOrNull(WebSockets) == null) { install(WebSockets) } @@ -301,17 +297,15 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA install(CallLogging) } - val visionManager = context.fetch(VisionManager) + val serverRoute = (featureOrNull(Routing) ?: install(Routing)).createRouteFromPath(path) - routing { - route(route) { - static { - resources() - } + serverRoute { + static { + resources() } } - return VisionServer(visionManager, this, route) + return VisionServer(visionManager, webServerUrl.copy(encodedPath = path), serverRoute) } /** @@ -319,10 +313,11 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA */ public fun VisionManager.serve( host: String = "localhost", - port: Int = 7777, + port: Int = VisionServer.DEFAULT_PORT, block: VisionServer.() -> Unit, ): ApplicationEngine = context.embeddedServer(CIO, port, host) { - visionServer(context).apply(block) + val url = URLBuilder(host = host, port = port).build() + visionServer(this@serve, url).apply(block) }.start() /** diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt index 8b1428ec..1d5fd3d9 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt @@ -47,7 +47,7 @@ public abstract class MeshThreeFactory( obj.onPropertyChange { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { - val oldGeometry = mesh.geometry as BufferGeometry + val oldGeometry = mesh.geometry val newGeometry = buildGeometry(obj) oldGeometry.attributes = newGeometry.attributes //mesh.applyWireFrame(obj) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index d715a20f..ef9944e0 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -53,7 +53,7 @@ public class ThreeCanvas( private val mousePosition: Vector2 = Vector2() private val scene: Scene = Scene().apply { - options.useProperty(Canvas3DOptions::axes, this) { axesConfig -> + options.useProperty(Canvas3DOptions::axes, this) { getObjectByName(AXES_NAME)?.let { remove(it) } val axesObject = AxesHelper(axes.size.toInt()).apply { visible = axes.visible } axesObject.name = AXES_NAME diff --git a/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/space/kscience/visionforge/three/server/serverExtensions.kt b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/space/kscience/visionforge/three/server/serverExtensions.kt index 94034547..d6656c46 100644 --- a/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/space/kscience/visionforge/three/server/serverExtensions.kt +++ b/visionforge-threejs/visionforge-threejs-server/src/jvmMain/kotlin/space/kscience/visionforge/three/server/serverExtensions.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.three.server -import space.kscience.dataforge.context.Context import space.kscience.dataforge.misc.DFExperimental +import space.kscience.visionforge.VisionManager import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.page @@ -16,7 +16,7 @@ public fun VisionServer.useThreeJs(): Unit { } @DFExperimental -public fun Context.makeThreeJsFile( +public fun VisionManager.makeThreeJsFile( content: HtmlVisionFragment, path: Path? = null, title: String = "VisionForge page",