Update notebooks hierarchy

This commit is contained in:
Alexander Nozik 2022-01-02 14:28:24 +03:00
parent 6b8e166978
commit 43285de33c
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
22 changed files with 343 additions and 305 deletions

View File

@ -29,7 +29,10 @@ kotlin {
jvm { jvm {
withJava() withJava()
compilations.all { compilations.all {
kotlinOptions.jvmTarget = "11" kotlinOptions{
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy"
}
} }
testRuns["test"].executionTask.configure { testRuns["test"].executionTask.configure {
useJUnitPlatform() useJUnitPlatform()
@ -51,33 +54,34 @@ kotlin {
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
api(project(":visionforge-solid")) implementation(projects.visionforgeSolid)
api(project(":visionforge-gdml")) implementation(projects.visionforgeGdml)
api(project(":visionforge-plotly")) implementation(projects.visionforgePlotly)
api(projects.visionforge.visionforgeMarkdown) implementation(projects.visionforgeMarkdown)
api(projects.visionforge.cernRootLoader) implementation(projects.cernRootLoader)
implementation(projects.jupyter.jupyterBase)
} }
} }
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
api(project(":ui:ring")) implementation(projects.ui.ring)
api(project(":visionforge-threejs")) implementation(projects.visionforgeThreejs)
} }
} }
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
api(project(":visionforge-server")) implementation(projects.visionforgeServer)
api("ch.qos.logback:logback-classic:1.2.3") implementation("ch.qos.logback:logback-classic:1.2.3")
api("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
} }
} }
} }
} }
tasks.withType<org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask> { val processJupyterApiResources by tasks.getting(org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask::class){
libraryProducers = listOf("space.kscience.visionforge.examples.VisionForgePlayGroundForJupyter") libraryProducers = listOf("space.kscience.visionforge.examples.VisionForgePlayGroundForJupyter")
} }
tasks.findByName("shadowJar")?.dependsOn("processJupyterApiResources") tasks.findByName("shadowJar")?.dependsOn(processJupyterApiResources)

View File

@ -1,39 +1,25 @@
package space.kscience.visionforge.examples 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 org.jetbrains.kotlinx.jupyter.api.libraries.resources
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import space.kscience.plotly.Plot import space.kscience.plotly.Plot
import space.kscience.visionforge.Vision
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.jupyter.JupyterPluginBase
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.embedAndRenderVisionFragment
import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.asVision import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.visionManager
@DFExperimental @DFExperimental
public class VisionForgePlayGroundForJupyter : JupyterIntegration() { internal class VisionForgePlayGroundForJupyter : JupyterPluginBase(
Context("VisionForge") {
private val context = Context("VisionForge") {
plugin(Solids) plugin(Solids)
plugin(PlotlyPlugin) plugin(PlotlyPlugin)
} }
) {
private var counter = 0 override fun Builder.afterLoaded() {
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
}.finalize()
override fun Builder.onLoaded() {
resources { resources {
js("VisionForge") { js("VisionForge") {
classPath("js/visionforge-playground.js") classPath("js/visionforge-playground.js")
@ -44,41 +30,20 @@ public class VisionForgePlayGroundForJupyter : JupyterIntegration() {
"space.kscience.gdml.*", "space.kscience.gdml.*",
"space.kscience.plotly.*", "space.kscience.plotly.*",
"space.kscience.plotly.models.*", "space.kscience.plotly.models.*",
"kotlinx.html.*",
"space.kscience.visionforge.solid.*", "space.kscience.visionforge.solid.*",
"space.kscience.visionforge.html.Page",
"space.kscience.visionforge.html.page"
) )
render<Gdml> { gdmlModel -> render<Gdml> { gdmlModel ->
val fragment = HtmlVisionFragment { handler.produceHtml {
vision(gdmlModel.toVision()) vision(gdmlModel.toVision())
} }
HTML(produceHtmlVisionString(fragment))
}
render<Vision> { vision ->
val fragment = HtmlVisionFragment {
vision(vision)
}
HTML(produceHtmlVisionString(fragment))
} }
render<Plot> { plot -> render<Plot> { plot ->
val fragment = HtmlVisionFragment { handler.produceHtml {
vision(plot.asVision()) vision(plot.asVision())
} }
HTML(produceHtmlVisionString(fragment))
}
render<space.kscience.plotly.PlotlyHtmlFragment> { fragment ->
HTML(createHTML().apply(fragment.visit).finalize())
}
render<Page> { page ->
HTML(page.render(createHTML()), true)
} }
} }

View File

@ -5,7 +5,7 @@ import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.visionforge.VisionManager 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.onPropertyChange
import space.kscience.visionforge.three.server.close import space.kscience.visionforge.three.server.close
import space.kscience.visionforge.three.server.openInBrowser import space.kscience.visionforge.three.server.openInBrowser
@ -18,7 +18,7 @@ fun main() {
val server = visionManager.serve { val server = visionManager.serve {
useScript("js/visionforge-playground.js") useScript("js/visionforge-playground.js")
page { page {
val form = visionOfForm("form") { val form = formFragment("form") {
label { label {
htmlFor = "fname" htmlFor = "fname"
+"First name:" +"First name:"

View File

@ -9,6 +9,7 @@ import space.kscience.visionforge.html.scriptHeader
import space.kscience.visionforge.makeFile import space.kscience.visionforge.makeFile
import space.kscience.visionforge.three.server.VisionServer import space.kscience.visionforge.three.server.VisionServer
import space.kscience.visionforge.three.server.useScript import space.kscience.visionforge.three.server.useScript
import space.kscience.visionforge.visionManager
import java.awt.Desktop import java.awt.Desktop
import java.nio.file.Path import java.nio.file.Path
@ -25,7 +26,7 @@ public fun Context.makeVisionFile(
show: Boolean = true, show: Boolean = true,
content: VisionTagConsumer<*>.() -> Unit content: VisionTagConsumer<*>.() -> Unit
): 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)) mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
} }
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI()) if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())

View File

@ -9,12 +9,12 @@ kotlin {
sourceSets { sourceSets {
commonMain{ commonMain{
dependencies{ dependencies{
api(projects.visionforge.visionforgeCore) api(projects.visionforgeCore)
} }
} }
jvmMain { jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-server")) api(projects.visionforgeServer)
} }
} }
} }

View File

@ -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<HtmlVisionFragment> { fragment ->
handler.produceHtml(fragment = fragment)
}
render<Vision> { vision ->
handler.produceHtml {
vision(vision)
}
}
render<Page> { page ->
HTML(page.render(createHTML()), true)
}
render<HtmlFormFragment> { fragment ->
handler.produceHtml {
fragment(fragment.formBody)
vision(fragment.vision)
}
}
afterLoaded()
}
}

View File

@ -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)
}

View File

@ -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> { 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> { page ->
//HTML(page.render(createHTML()), true)
}
}
}

View File

@ -32,18 +32,19 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api(project(":visionforge-solid")) implementation(projects.visionforgeSolid)
implementation(projects.jupyter.jupyterBase)
} }
} }
jvmMain { jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-gdml")) implementation(projects.visionforgeGdml)
} }
} }
jsMain { jsMain {
dependencies { dependencies {
api(project(":visionforge-threejs")) implementation(projects.visionforgeThreejs)
implementation(project(":ui:ring")) implementation(projects.ui.ring)
} }
} }

View File

@ -1,34 +1,21 @@
package space.kscience.visionforge.gdml.jupyter 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 org.jetbrains.kotlinx.jupyter.api.libraries.resources
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import space.kscience.visionforge.Vision
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.jupyter.JupyterPluginBase
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.embedAndRenderVisionFragment
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.visionManager
@DFExperimental @DFExperimental
internal class GdmlForJupyter : JupyterIntegration() { internal class GdmlForJupyter : JupyterPluginBase(
Context("GDML") {
private val context = Context("GDML") {
plugin(Solids) plugin(Solids)
} }
) {
private var counter = 0 override fun Builder.afterLoaded() {
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
}.finalize()
override fun Builder.onLoaded() {
resources { resources {
js("three") { js("three") {
@ -38,23 +25,11 @@ internal class GdmlForJupyter : JupyterIntegration() {
import( import(
"space.kscience.gdml.*", "space.kscience.gdml.*",
"kotlinx.html.*",
"space.kscience.visionforge.solid.*",
"space.kscience.visionforge.html.Page",
"space.kscience.visionforge.html.page",
"space.kscience.visionforge.gdml.jupyter.*" "space.kscience.visionforge.gdml.jupyter.*"
) )
render<Vision> { vision ->
HTML(produceHtmlVisionString { vision(vision) })
}
render<Gdml> { gdmlModel -> render<Gdml> { gdmlModel ->
HTML(produceHtmlVisionString { vision(gdmlModel.toVision()) }) handler.produceHtml { vision(gdmlModel.toVision()) }
}
render<Page> { page ->
HTML(page.render(createHTML()), true)
} }
} }
} }

View File

@ -61,6 +61,6 @@ include(
":demo:playground", ":demo:playground",
":demo:plotly-fx", ":demo:plotly-fx",
":demo:js-playground", ":demo:js-playground",
":jupyter:visionforge-jupyter-base", ":jupyter:jupyter-base",
":jupyter:visionforge-jupyter-gdml" ":jupyter:visionforge-jupyter-gdml"
) )

View File

@ -3,7 +3,6 @@ package space.kscience.visionforge.html
import kotlinx.html.FlowContent import kotlinx.html.FlowContent
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
import space.kscience.dataforge.misc.DFExperimental
public typealias HtmlFragment = TagConsumer<*>.() -> Unit public typealias HtmlFragment = TagConsumer<*>.() -> Unit
@ -16,8 +15,3 @@ public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
public fun FlowContent.fragment(fragment: HtmlFragment) { public fun FlowContent.fragment(fragment: HtmlFragment) {
fragment(consumer) fragment(consumer)
} }
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
@DFExperimental
public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content

View File

@ -6,26 +6,47 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager 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, manager: VisionManager,
embedData: Boolean = true, embedData: Boolean = true,
fetchData: String? = null, fetchDataUrl: String? = null,
fetchUpdates: String? = null, fetchUpdatesUrl: String? = null,
idPrefix: String? = null, idPrefix: String? = null,
renderScript: Boolean = true,
fragment: HtmlVisionFragment, fragment: HtmlVisionFragment,
): Map<Name, Vision> { ): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>() val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) { val consumer = object : VisionTagConsumer<Any?>(this@visionFragment, manager, idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision visionMap[name] = vision
// Toggle update mode // Toggle update mode
fetchUpdates?.let { fetchUpdatesUrl?.let {
attributes[OUTPUT_CONNECT_ATTRIBUTE] = it attributes[OUTPUT_CONNECT_ATTRIBUTE] = it
} }
fetchData?.let { fetchDataUrl?.let {
attributes[OUTPUT_FETCH_ATTRIBUTE] = it attributes[OUTPUT_FETCH_ATTRIBUTE] = it
} }
@ -40,39 +61,36 @@ public fun TagConsumer<*>.embedVisionFragment(
} }
} }
} }
if (renderScript) {
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
fragment(consumer) 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<Name, Vision> = 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 {
div {
this.id = id.toString()
embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment)
} }
script { script {
type = "text/javascript" type = "text/javascript"
unsafe { +"window.${RENDER_FUNCTION_NAME}(\"$id\");" } unsafe { +"window.${RENDER_FUNCTION_NAME}(\"$id\");" }
} }
} else {
fragment(consumer)
} }
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<Name, Vision> = consumer.visionFragment(
manager,
embedData,
fetchDataUrl,
fetchUpdatesUrl,
idPrefix,
renderSctipt,
fragment
)

View File

@ -1,12 +1,11 @@
package space.kscience.visionforge.html package space.kscience.visionforge.html
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.visionManager import space.kscience.visionforge.VisionManager
public data class Page( public data class Page(
public val context: Context, public val visionManager: VisionManager,
public val title: String, public val title: String,
public val headers: Map<String, HtmlFragment>, public val headers: Map<String, HtmlFragment>,
public val content: HtmlVisionFragment, public val content: HtmlVisionFragment,
@ -22,14 +21,14 @@ public data class Page(
title(this@Page.title) title(this@Page.title)
} }
body { body {
embedVisionFragment(context.visionManager, fragment = content) visionFragment(visionManager, fragment = content)
} }
}.finalize() }.finalize()
} }
@DFExperimental @DFExperimental
public fun Context.page( public fun VisionManager.page(
title: String = "VisionForge page", title: String = "VisionForge page",
vararg headers: Pair<String, HtmlFragment>, vararg headers: Pair<String, HtmlFragment>,
content: HtmlVisionFragment, content: HtmlVisionFragment,

View File

@ -7,6 +7,7 @@ import kotlinx.html.id
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.node import space.kscience.dataforge.meta.node
@Serializable @Serializable
@ -17,10 +18,29 @@ public class VisionOfHtmlForm(
public var values: Meta? by meta.node() public var values: Meta? by meta.node()
} }
public inline fun <R> TagConsumer<R>.visionOfForm(id: String, crossinline builder: FORM.() -> Unit): VisionOfHtmlForm { 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 { form {
this.id = id this.id = realId
builder() builder()
} }
return VisionOfHtmlForm(id) }
}
public fun <R> TagConsumer<R>.formFragment(
id: String? = null,
builder: FORM.() -> Unit,
): VisionOfHtmlForm {
val formFragment = HtmlFormFragment(id, builder)
fragment(formFragment.formBody)
return formFragment.vision
} }

View File

@ -42,7 +42,7 @@ public abstract class VisionTagConsumer<R>(
private val idPrefix: String? = null, private val idPrefix: String? = null,
) : TagConsumer<R> by root { ) : TagConsumer<R> 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 * Render a vision inside the output fragment

View File

@ -164,7 +164,7 @@ public class VisionClient : AbstractPlugin() {
val endpoint = resolveEndpoint(element) val endpoint = resolveEndpoint(element)
logger.info { "Vision server is resolved to $endpoint" } logger.info { "Vision server is resolved to $endpoint" }
URL(endpoint).apply { URL(endpoint).apply {
pathname += "/vision" pathname += "/data"
} }
} else { } else {
URL(attr.value) URL(attr.value)

View File

@ -94,7 +94,7 @@ public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<Vis
form.onsubmit = { event -> form.onsubmit = { event ->
event.preventDefault() event.preventDefault()
val formData = FormData(form).toMeta() val formData = FormData(form).toMeta()
console.log(formData.toString()) //console.log(formData.toString())
vision.values = formData vision.values = formData
false false
} }

View File

@ -4,12 +4,10 @@ import io.ktor.application.*
import io.ktor.features.CORS import io.ktor.features.CORS
import io.ktor.features.CallLogging import io.ktor.features.CallLogging
import io.ktor.html.respondHtml import io.ktor.html.respondHtml
import io.ktor.http.ContentType import io.ktor.http.*
import io.ktor.http.HttpStatusCode
import io.ktor.http.cio.websocket.Frame import io.ktor.http.cio.websocket.Frame
import io.ktor.http.content.resources import io.ktor.http.content.resources
import io.ktor.http.content.static import io.ktor.http.content.static
import io.ktor.http.withCharset
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.response.respondText import io.ktor.response.respondText
import io.ktor.routing.* import io.ktor.routing.*
@ -25,8 +23,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.html.* import kotlinx.html.*
import kotlinx.html.stream.createHTML 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.meta.*
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
@ -34,7 +30,10 @@ import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionChange import space.kscience.visionforge.VisionChange
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.flowChanges 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 space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
import java.awt.Desktop import java.awt.Desktop
import java.net.URI import java.net.URI
@ -43,12 +42,13 @@ import kotlin.time.Duration.Companion.milliseconds
/** /**
* A ktor plugin container with given [routing] * A ktor plugin container with given [routing]
* @param serverUrl a server url including root route
*/ */
public class VisionServer internal constructor( public class VisionServer internal constructor(
private val visionManager: VisionManager, private val visionManager: VisionManager,
private val application: Application, private val serverUrl: Url,
private val rootRoute: String, private val root: Route,
) : Configurable, CoroutineScope by application { ) : Configurable, CoroutineScope by root.application {
override val meta: ObservableMutableMeta = MutableMeta() 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` * 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 * a list of headers that should be applied to all pages
@ -90,6 +90,7 @@ public class VisionServer internal constructor(
private fun HTML.visionPage( private fun HTML.visionPage(
title: String, title: String,
pagePath: String,
headers: List<HtmlFragment>, headers: List<HtmlFragment>,
visionFragment: HtmlVisionFragment, visionFragment: HtmlVisionFragment,
): Map<Name, Vision> { ): Map<Name, Vision> {
@ -106,11 +107,10 @@ public class VisionServer internal constructor(
} }
body { body {
//Load the fragment and remember all loaded visions //Load the fragment and remember all loaded visions
visionMap = embedVisionFragment( visionMap = visionFragment(
manager = visionManager, manager = visionManager,
embedData = true, embedData = true,
fetchDataUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE, fetchUpdatesUrl = "$serverUrl$pagePath/ws",
fetchUpdatesUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE,
fragment = visionFragment fragment = visionFragment
) )
} }
@ -122,7 +122,7 @@ public class VisionServer internal constructor(
* Server a map of visions without providing explicit html page for them * Server a map of visions without providing explicit html page for them
*/ */
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
internal fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route { private fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
application.log.info("Serving visions $visions at $route") application.log.info("Serving visions $visions at $route")
//Update websocket //Update websocket
@ -157,7 +157,7 @@ public class VisionServer internal constructor(
} }
} }
//Plots in their json representation //Plots in their json representation
get("vision") { get("data") {
val name: String = call.request.queryParameters["name"] val name: String = call.request.queryParameters["name"]
?: error("Vision name is not defined in parameters") ?: 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<HtmlFragment>,
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 * Serve visions in a given [route] without providing a page template
*/ */
public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit { public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit {
application.routing { root.route(route) {
route(rootRoute) {
route(route) {
serveVisions(this, visions) 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( public fun page(
route: String = DEFAULT_PAGE, pagePath: String = DEFAULT_PAGE,
title: String = "VisionForge server page '$route'", title: String = "VisionForge server page '$pagePath'",
headers: List<HtmlFragment> = emptyList(), headers: List<HtmlFragment> = emptyList(),
visionFragment: HtmlVisionFragment, visionFragment: HtmlVisionFragment,
) { ) {
@ -223,15 +216,13 @@ public class VisionServer internal constructor(
val cachedHtml: String? = if (cacheFragments) { val cachedHtml: String? = if (cacheFragments) {
//Create and cache page html and map of visions //Create and cache page html and map of visions
createHTML(true).html { createHTML(true).html {
visions.putAll(visionPage(title, headers, visionFragment)) visions.putAll(visionPage(title, pagePath, headers, visionFragment))
} }
} else { } else {
null null
} }
application.routing { root.route(pagePath) {
route(rootRoute) {
route(route) {
serveVisions(this, visions) serveVisions(this, visions)
//filled pages //filled pages
get { get {
@ -239,7 +230,7 @@ public class VisionServer internal constructor(
//re-create html and vision list on each call //re-create html and vision list on each call
call.respondHtml { call.respondHtml {
visions.clear() visions.clear()
visions.putAll(visionPage(title, headers, visionFragment)) visions.putAll(visionPage(title, pagePath, headers, visionFragment))
} }
} else { } else {
//Use cached html //Use cached html
@ -247,11 +238,12 @@ public class VisionServer internal constructor(
} }
} }
} }
}
}
} }
public companion object { public companion object {
public const val DEFAULT_PORT: Int = 7777
public const val DEFAULT_PAGE: String = "/" public const val DEFAULT_PAGE: String = "/"
public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval") 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 * 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) { if (featureOrNull(WebSockets) == null) {
install(WebSockets) install(WebSockets)
} }
@ -301,17 +297,15 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA
install(CallLogging) install(CallLogging)
} }
val visionManager = context.fetch(VisionManager) val serverRoute = (featureOrNull(Routing) ?: install(Routing)).createRouteFromPath(path)
routing { serverRoute {
route(route) {
static { static {
resources() 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( public fun VisionManager.serve(
host: String = "localhost", host: String = "localhost",
port: Int = 7777, port: Int = VisionServer.DEFAULT_PORT,
block: VisionServer.() -> Unit, block: VisionServer.() -> Unit,
): ApplicationEngine = context.embeddedServer(CIO, port, host) { ): 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() }.start()
/** /**

View File

@ -47,7 +47,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
obj.onPropertyChange { name -> obj.onPropertyChange { name ->
when { when {
name.startsWith(Solid.GEOMETRY_KEY) -> { name.startsWith(Solid.GEOMETRY_KEY) -> {
val oldGeometry = mesh.geometry as BufferGeometry val oldGeometry = mesh.geometry
val newGeometry = buildGeometry(obj) val newGeometry = buildGeometry(obj)
oldGeometry.attributes = newGeometry.attributes oldGeometry.attributes = newGeometry.attributes
//mesh.applyWireFrame(obj) //mesh.applyWireFrame(obj)

View File

@ -53,7 +53,7 @@ public class ThreeCanvas(
private val mousePosition: Vector2 = Vector2() private val mousePosition: Vector2 = Vector2()
private val scene: Scene = Scene().apply { private val scene: Scene = Scene().apply {
options.useProperty(Canvas3DOptions::axes, this) { axesConfig -> options.useProperty(Canvas3DOptions::axes, this) {
getObjectByName(AXES_NAME)?.let { remove(it) } getObjectByName(AXES_NAME)?.let { remove(it) }
val axesObject = AxesHelper(axes.size.toInt()).apply { visible = axes.visible } val axesObject = AxesHelper(axes.size.toInt()).apply { visible = axes.visible }
axesObject.name = AXES_NAME axesObject.name = AXES_NAME

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.three.server package space.kscience.visionforge.three.server
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.html.page import space.kscience.visionforge.html.page
@ -16,7 +16,7 @@ public fun VisionServer.useThreeJs(): Unit {
} }
@DFExperimental @DFExperimental
public fun Context.makeThreeJsFile( public fun VisionManager.makeThreeJsFile(
content: HtmlVisionFragment, content: HtmlVisionFragment,
path: Path? = null, path: Path? = null,
title: String = "VisionForge page", title: String = "VisionForge page",