Update static/dynamic rendering logic
This commit is contained in:
parent
3c51060e2e
commit
eae1316de5
@ -12,7 +12,7 @@ val fxVersion by extra("11")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.3.0-dev-3"
|
version = "0.3.0-dev-4"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package ru.mipt.npm.muon.monitor
|
package ru.mipt.npm.muon.monitor
|
||||||
|
|
||||||
import kotlinx.browser.document
|
import org.w3c.dom.Document
|
||||||
import react.dom.client.createRoot
|
import react.dom.client.createRoot
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.fetch
|
import space.kscience.dataforge.context.fetch
|
||||||
@ -13,7 +13,7 @@ import space.kscience.visionforge.startApplication
|
|||||||
|
|
||||||
private class MMDemoApp : Application {
|
private class MMDemoApp : Application {
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(document: Document, state: Map<String, Any>) {
|
||||||
|
|
||||||
val context = Context("MM-demo") {
|
val context = Context("MM-demo") {
|
||||||
plugin(ThreePlugin)
|
plugin(ThreePlugin)
|
||||||
|
339
demo/playground/notebooks/demo3D.ipynb
Normal file
339
demo/playground/notebooks/demo3D.ipynb
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,5 @@
|
|||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
import space.kscience.visionforge.jupyter.VFNotebookPlugin
|
||||||
import space.kscience.visionforge.markup.MarkupPlugin
|
import space.kscience.visionforge.markup.MarkupPlugin
|
||||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||||
import space.kscience.visionforge.ring.ThreeWithControlsPlugin
|
import space.kscience.visionforge.ring.ThreeWithControlsPlugin
|
||||||
@ -11,4 +12,5 @@ fun main() = runVisionClient {
|
|||||||
plugin(PlotlyPlugin)
|
plugin(PlotlyPlugin)
|
||||||
plugin(MarkupPlugin)
|
plugin(MarkupPlugin)
|
||||||
plugin(TableVisionJsPlugin)
|
plugin(TableVisionJsPlugin)
|
||||||
|
plugin(VFNotebookPlugin)
|
||||||
}
|
}
|
@ -6,13 +6,13 @@ 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.gdml.toVision
|
import space.kscience.visionforge.gdml.toVision
|
||||||
import space.kscience.visionforge.jupyter.JupyterPluginBase
|
import space.kscience.visionforge.jupyter.VFIntegrationBase
|
||||||
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
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
internal class VisionForgePlayGroundForJupyter : JupyterPluginBase(
|
internal class VisionForgePlayGroundForJupyter : VFIntegrationBase(
|
||||||
Context("VisionForge") {
|
Context("VisionForge") {
|
||||||
plugin(Solids)
|
plugin(Solids)
|
||||||
plugin(PlotlyPlugin)
|
plugin(PlotlyPlugin)
|
||||||
|
@ -11,7 +11,6 @@ import space.kscience.dataforge.misc.DFExperimental
|
|||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.html.VisionPage
|
import space.kscience.visionforge.html.VisionPage
|
||||||
import space.kscience.visionforge.server.DataServeMode
|
|
||||||
import space.kscience.visionforge.server.close
|
import space.kscience.visionforge.server.close
|
||||||
import space.kscience.visionforge.server.openInBrowser
|
import space.kscience.visionforge.server.openInBrowser
|
||||||
import space.kscience.visionforge.server.serve
|
import space.kscience.visionforge.server.serve
|
||||||
@ -37,7 +36,6 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val server = satContext.visionManager.serve {
|
val server = satContext.visionManager.serve {
|
||||||
dataMode = DataServeMode.UPDATE
|
|
||||||
page(VisionPage.threeJsHeader, VisionPage.styleSheetHeader("css/styles.css")) {
|
page(VisionPage.threeJsHeader, VisionPage.styleSheetHeader("css/styles.css")) {
|
||||||
div("flex-column") {
|
div("flex-column") {
|
||||||
h1 { +"Satellite detector demo" }
|
h1 { +"Satellite detector demo" }
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package space.kscience.visionforge.solid.demo
|
package space.kscience.visionforge.solid.demo
|
||||||
|
|
||||||
import kotlinx.browser.document
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.w3c.dom.Document
|
||||||
import space.kscience.visionforge.Application
|
import space.kscience.visionforge.Application
|
||||||
import space.kscience.visionforge.solid.x
|
import space.kscience.visionforge.solid.x
|
||||||
import space.kscience.visionforge.solid.y
|
import space.kscience.visionforge.solid.y
|
||||||
@ -12,7 +12,7 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
private class ThreeDemoApp : Application {
|
private class ThreeDemoApp : Application {
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(document: Document, state: Map<String, Any>) {
|
||||||
|
|
||||||
val element = document.getElementById("demo") ?: error("Element with id 'demo' not found on page")
|
val element = document.getElementById("demo") ?: error("Element with id 'demo' not found on page")
|
||||||
|
|
||||||
|
51
jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt
Normal file
51
jupyter/src/jsMain/kotlin/VFNotebookPlugin.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package space.kscience.visionforge.jupyter
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import space.kscience.dataforge.context.AbstractPlugin
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.PluginFactory
|
||||||
|
import space.kscience.dataforge.context.PluginTag
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.visionforge.VisionClient
|
||||||
|
import space.kscience.visionforge.renderAllVisions
|
||||||
|
import space.kscience.visionforge.renderAllVisionsById
|
||||||
|
import space.kscience.visionforge.renderAllVisionsIn
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
public class VFNotebookPlugin : AbstractPlugin() {
|
||||||
|
private val client by require(VisionClient)
|
||||||
|
|
||||||
|
public fun renderAllVisionsIn(element: Element) {
|
||||||
|
client.renderAllVisionsIn(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun renderAllVisionsById(id: String) {
|
||||||
|
client.renderAllVisionsById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun renderAllVisions() {
|
||||||
|
client.renderAllVisions()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
//register VisionForge in the browser window
|
||||||
|
window.asDynamic().vf = this
|
||||||
|
window.asDynamic().VisionForge = this
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NON_EXPORTABLE_TYPE")
|
||||||
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
|
@Suppress("NON_EXPORTABLE_TYPE")
|
||||||
|
public companion object : PluginFactory<VFNotebookPlugin> {
|
||||||
|
override fun build(context: Context, meta: Meta): VFNotebookPlugin = VFNotebookPlugin()
|
||||||
|
|
||||||
|
override val tag: PluginTag = PluginTag(name = "vision.notebook", group = PluginTag.DATAFORGE_GROUP)
|
||||||
|
|
||||||
|
override val type: KClass<out VFNotebookPlugin> = VFNotebookPlugin::class
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,9 @@
|
|||||||
package space.kscience.visionforge.jupyter
|
package space.kscience.visionforge.jupyter
|
||||||
|
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
import kotlinx.html.FORM
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.html.TagConsumer
|
import kotlinx.html.*
|
||||||
import kotlinx.html.p
|
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import kotlinx.html.style
|
|
||||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||||
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
|
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -21,11 +19,21 @@ import space.kscience.visionforge.html.visionFragment
|
|||||||
import space.kscience.visionforge.server.VisionServer
|
import space.kscience.visionforge.server.VisionServer
|
||||||
import space.kscience.visionforge.server.serve
|
import space.kscience.visionforge.server.serve
|
||||||
import space.kscience.visionforge.visionManager
|
import space.kscience.visionforge.visionManager
|
||||||
|
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\");" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handler class that includes a server and common utilities
|
* A handler class that includes a server and common utilities
|
||||||
*/
|
*/
|
||||||
public class VisionForgeForNotebook(override val context: Context) : ContextAware {
|
public class VFForNotebook(override val context: Context) : ContextAware, CoroutineScope {
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|
||||||
private var engine: ApplicationEngine? = null
|
private var engine: ApplicationEngine? = null
|
||||||
@ -33,6 +41,8 @@ public class VisionForgeForNotebook(override val context: Context) : ContextAwar
|
|||||||
|
|
||||||
public var isolateFragments: Boolean = false
|
public var isolateFragments: Boolean = false
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext get() = context.coroutineContext
|
||||||
|
|
||||||
public fun legacyMode() {
|
public fun legacyMode() {
|
||||||
isolateFragments = true
|
isolateFragments = true
|
||||||
}
|
}
|
||||||
@ -68,14 +78,28 @@ public class VisionForgeForNotebook(override val context: Context) : ContextAwar
|
|||||||
public fun stopServer() {
|
public fun stopServer() {
|
||||||
engine?.apply {
|
engine?.apply {
|
||||||
logger.info { "Stopping VisionForge server" }
|
logger.info { "Stopping VisionForge server" }
|
||||||
}?.stop(1000, 2000)
|
stop(1000, 2000)
|
||||||
|
engine = null
|
||||||
|
server = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun produceHtmlString(
|
private fun produceHtmlString(
|
||||||
fragment: HtmlVisionFragment,
|
fragment: HtmlVisionFragment,
|
||||||
): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment)
|
): String = createHTML().apply {
|
||||||
?: createHTML().apply {
|
val server = server
|
||||||
|
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
|
||||||
|
div {
|
||||||
|
this.id = id
|
||||||
|
if (server != null) {
|
||||||
|
//if server exist, serve dynamically
|
||||||
|
server.serveVisionsFromFragment(consumer, "content-${counter++}", fragment)
|
||||||
|
} else {
|
||||||
|
//if not, use static rendering
|
||||||
visionFragment(context, fragment = fragment)
|
visionFragment(context, fragment = fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderScriptForId(id)
|
||||||
}.finalize()
|
}.finalize()
|
||||||
|
|
||||||
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =
|
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =
|
@ -1,8 +1,7 @@
|
|||||||
package space.kscience.visionforge.jupyter
|
package space.kscience.visionforge.jupyter
|
||||||
|
|
||||||
import kotlinx.html.p
|
import kotlinx.html.*
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import kotlinx.html.style
|
|
||||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||||
import org.jetbrains.kotlinx.jupyter.api.declare
|
import org.jetbrains.kotlinx.jupyter.api.declare
|
||||||
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
||||||
@ -11,11 +10,16 @@ import space.kscience.dataforge.context.ContextAware
|
|||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.html.*
|
import space.kscience.visionforge.html.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextUInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for different Jupyter VF integrations
|
||||||
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public abstract class JupyterPluginBase(final override val context: Context) : JupyterIntegration(), ContextAware {
|
public abstract class VFIntegrationBase(final override val context: Context) : JupyterIntegration(), ContextAware {
|
||||||
|
|
||||||
protected val handler: VisionForgeForNotebook = VisionForgeForNotebook(context)
|
protected val handler: VFForNotebook = VFForNotebook(context)
|
||||||
|
|
||||||
protected abstract fun Builder.afterLoaded()
|
protected abstract fun Builder.afterLoaded()
|
||||||
|
|
||||||
@ -50,7 +54,24 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
|
|||||||
}
|
}
|
||||||
|
|
||||||
render<VisionPage> { page ->
|
render<VisionPage> { page ->
|
||||||
HTML(page.render(createHTML()), true)
|
HTML(createHTML().apply {
|
||||||
|
head {
|
||||||
|
meta {
|
||||||
|
charset = "utf-8"
|
||||||
|
}
|
||||||
|
page.pageHeaders.values.forEach {
|
||||||
|
fragment(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
val id = "fragment[${page.hashCode()}/${Random.nextUInt()}]"
|
||||||
|
div {
|
||||||
|
this.id = id
|
||||||
|
visionFragment(context, fragment = page.content)
|
||||||
|
}
|
||||||
|
renderScriptForId(id)
|
||||||
|
}
|
||||||
|
}.finalize(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
render<HtmlFormFragment> { fragment ->
|
render<HtmlFormFragment> { fragment ->
|
@ -5,11 +5,11 @@ 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.gdml.toVision
|
import space.kscience.visionforge.gdml.toVision
|
||||||
import space.kscience.visionforge.jupyter.JupyterPluginBase
|
import space.kscience.visionforge.jupyter.VFIntegrationBase
|
||||||
import space.kscience.visionforge.solid.Solids
|
import space.kscience.visionforge.solid.Solids
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
internal class GdmlForJupyter : JupyterPluginBase(
|
internal class GdmlForJupyter : VFIntegrationBase(
|
||||||
Context("GDML") {
|
Context("GDML") {
|
||||||
plugin(Solids)
|
plugin(Solids)
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ 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 typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
||||||
|
|
||||||
@ -17,9 +15,6 @@ public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
|||||||
public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content
|
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
|
* Render a fragment in the given consumer and return a map of extracted visions
|
||||||
* @param context a context used to create a vision fragment
|
* @param context a context used to create a vision fragment
|
||||||
@ -35,7 +30,6 @@ public fun TagConsumer<*>.visionFragment(
|
|||||||
fetchDataUrl: String? = null,
|
fetchDataUrl: String? = null,
|
||||||
fetchUpdatesUrl: 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>()
|
||||||
@ -63,19 +57,9 @@ public fun TagConsumer<*>.visionFragment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (renderScript) {
|
|
||||||
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
|
|
||||||
div {
|
|
||||||
this.id = id
|
|
||||||
fragment(consumer)
|
fragment(consumer)
|
||||||
}
|
|
||||||
script {
|
|
||||||
type = "text/javascript"
|
|
||||||
unsafe { +"window.${RENDER_FUNCTION_NAME}(\"$id\");" }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fragment(consumer)
|
|
||||||
}
|
|
||||||
return visionMap
|
return visionMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,16 +67,14 @@ public fun FlowContent.visionFragment(
|
|||||||
context: Context = Global,
|
context: Context = Global,
|
||||||
embedData: Boolean = true,
|
embedData: Boolean = true,
|
||||||
fetchDataUrl: String? = null,
|
fetchDataUrl: String? = null,
|
||||||
fetchUpdatesUrl: String? = null,
|
flowDataUrl: String? = null,
|
||||||
idPrefix: String? = null,
|
idPrefix: String? = null,
|
||||||
renderScript: Boolean = true,
|
|
||||||
fragment: HtmlVisionFragment,
|
fragment: HtmlVisionFragment,
|
||||||
): Map<Name, Vision> = consumer.visionFragment(
|
): Map<Name, Vision> = consumer.visionFragment(
|
||||||
context,
|
context,
|
||||||
embedData,
|
embedData,
|
||||||
fetchDataUrl,
|
fetchDataUrl,
|
||||||
fetchUpdatesUrl,
|
flowDataUrl,
|
||||||
idPrefix,
|
idPrefix,
|
||||||
renderScript,
|
|
||||||
fragment
|
fragment
|
||||||
)
|
)
|
@ -13,20 +13,6 @@ public data class VisionPage(
|
|||||||
public val pageHeaders: Map<String, HtmlFragment> = emptyMap(),
|
public val pageHeaders: Map<String, HtmlFragment> = emptyMap(),
|
||||||
public val content: HtmlVisionFragment,
|
public val content: HtmlVisionFragment,
|
||||||
) {
|
) {
|
||||||
public fun <R> render(root: TagConsumer<R>): R = root.apply {
|
|
||||||
head {
|
|
||||||
meta {
|
|
||||||
charset = "utf-8"
|
|
||||||
}
|
|
||||||
pageHeaders.values.forEach {
|
|
||||||
fragment(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
visionFragment(context, fragment = content)
|
|
||||||
}
|
|
||||||
}.finalize()
|
|
||||||
|
|
||||||
public companion object{
|
public companion object{
|
||||||
/**
|
/**
|
||||||
* Use a script with given [src] as a global header for all pages.
|
* Use a script with given [src] as a global header for all pages.
|
||||||
|
@ -2,7 +2,7 @@ package space.kscience.visionforge
|
|||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.dom.hasClass
|
import org.w3c.dom.Document
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ public interface Application: CoroutineScope {
|
|||||||
* Starting point for an application.
|
* Starting point for an application.
|
||||||
* @param state Initial state between Hot Module Replacement (HMR).
|
* @param state Initial state between Hot Module Replacement (HMR).
|
||||||
*/
|
*/
|
||||||
public fun start(state: Map<String, Any>)
|
public fun start(document: Document, state: Map<String, Any>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ending point for an application.
|
* Ending point for an application.
|
||||||
@ -46,17 +46,13 @@ public interface Application: CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun startApplication(builder: () -> Application) {
|
public fun startApplication(builder: () -> Application) {
|
||||||
fun start(state: dynamic): Application? {
|
fun start(document: Document, state: dynamic): Application{
|
||||||
return if (document.body?.hasClass("application") == true) {
|
|
||||||
val application = builder()
|
val application = builder()
|
||||||
|
|
||||||
@Suppress("UnsafeCastFromDynamic")
|
@Suppress("UnsafeCastFromDynamic")
|
||||||
application.start(state?.appState ?: emptyMap())
|
application.start(document, state?.appState ?: emptyMap())
|
||||||
|
|
||||||
application
|
return application
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var application: Application? = null
|
var application: Application? = null
|
||||||
@ -73,9 +69,9 @@ public fun startApplication(builder: () -> Application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (document.body != null) {
|
if (document.body != null) {
|
||||||
application = start(state)
|
application = start(document, state)
|
||||||
} else {
|
} else {
|
||||||
application = null
|
application = null
|
||||||
document.addEventListener("DOMContentLoaded", { application = start(state) })
|
document.addEventListener("DOMContentLoaded", { application = start(document, state) })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,6 @@ import space.kscience.dataforge.meta.MetaSerializer
|
|||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.int
|
import space.kscience.dataforge.meta.int
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.html.RENDER_FUNCTION_NAME
|
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer
|
import space.kscience.visionforge.html.VisionTagConsumer
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||||
@ -46,10 +45,9 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
return attribute?.value
|
return attribute?.value
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRenderers() = context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values
|
private val renderers by lazy { context.gather<ElementVisionRenderer>(ElementVisionRenderer.TYPE).values }
|
||||||
|
|
||||||
private fun findRendererFor(vision: Vision): ElementVisionRenderer? {
|
private fun findRendererFor(vision: Vision): ElementVisionRenderer? = renderers.mapNotNull {
|
||||||
return getRenderers().mapNotNull {
|
|
||||||
val rating = it.rateVision(vision)
|
val rating = it.rateVision(vision)
|
||||||
if (rating > 0) {
|
if (rating > 0) {
|
||||||
rating to it
|
rating to it
|
||||||
@ -57,7 +55,6 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.maxByOrNull { it.first }?.second
|
}.maxByOrNull { it.first }?.second
|
||||||
}
|
|
||||||
|
|
||||||
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML
|
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML
|
||||||
|
|
||||||
@ -78,7 +75,7 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
if (vision != null) {
|
if (vision != null) {
|
||||||
vision.setAsRoot(visionManager)
|
vision.setAsRoot(visionManager)
|
||||||
val renderer = findRendererFor(vision)
|
val renderer = findRendererFor(vision)
|
||||||
?: error("Could not find renderer for ${visionManager.encodeToString(vision)}")
|
?: error("Could not find renderer for ${vision::class}")
|
||||||
renderer.render(element, vision, outputMeta)
|
renderer.render(element, vision, outputMeta)
|
||||||
|
|
||||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||||
@ -228,7 +225,7 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
|
|
||||||
|
|
||||||
private fun whenDocumentLoaded(block: Document.() -> Unit): Unit {
|
private fun whenDocumentLoaded(block: Document.() -> Unit): Unit {
|
||||||
if (document.readyState == DocumentReadyState.COMPLETE) {
|
if (document.body != null) {
|
||||||
block(document)
|
block(document)
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener("DOMContentLoaded", { block(document) })
|
document.addEventListener("DOMContentLoaded", { block(document) })
|
||||||
@ -267,14 +264,29 @@ public fun VisionClient.renderAllVisions(): Unit = whenDocumentLoaded {
|
|||||||
renderAllVisionsIn(element)
|
renderAllVisionsIn(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class VisionClientApplication(public val context: Context) : Application {
|
||||||
|
private val client = context.fetch(VisionClient)
|
||||||
|
|
||||||
|
override fun start(document: Document, state: Map<String, Any>) {
|
||||||
|
console.info("Starting Vision Client")
|
||||||
|
val element = document.body ?: error("Document does not have a body")
|
||||||
|
client.renderAllVisionsIn(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a vision client context and render all visions on the page.
|
* Create a vision client context and render all visions on the page.
|
||||||
*/
|
*/
|
||||||
public fun runVisionClient(contextBuilder: ContextBuilder.() -> Unit) {
|
public fun runVisionClient(contextBuilder: ContextBuilder.() -> Unit) {
|
||||||
console.info("Starting VisionForge context")
|
console.info("Starting VisionForge context")
|
||||||
val context = Context("VisionForge", contextBuilder)
|
|
||||||
val visionClient = context.fetch(VisionClient)
|
|
||||||
window.asDynamic()[RENDER_FUNCTION_NAME] = visionClient::renderAllVisionsById
|
|
||||||
|
|
||||||
//visionClient.renderAllVisions()
|
val context = Context("VisionForge") {
|
||||||
|
plugin(VisionClient)
|
||||||
|
contextBuilder()
|
||||||
|
}
|
||||||
|
|
||||||
|
startApplication {
|
||||||
|
VisionClientApplication(context)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,14 @@
|
|||||||
package space.kscience.visionforge
|
package space.kscience.visionforge
|
||||||
|
|
||||||
|
import kotlinx.html.body
|
||||||
|
import kotlinx.html.head
|
||||||
|
import kotlinx.html.meta
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.visionforge.html.HtmlFragment
|
import space.kscience.visionforge.html.HtmlFragment
|
||||||
import space.kscience.visionforge.html.VisionPage
|
import space.kscience.visionforge.html.VisionPage
|
||||||
|
import space.kscience.visionforge.html.fragment
|
||||||
|
import space.kscience.visionforge.html.visionFragment
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -56,20 +61,34 @@ import java.nio.file.Path
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Export a [VisionPage] to a file
|
* Export a [VisionPage] to a file
|
||||||
|
*
|
||||||
|
* @param fileHeaders additional file-system specific headers.
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public fun VisionPage.makeFile(
|
public fun VisionPage.makeFile(
|
||||||
path: Path?,
|
path: Path?,
|
||||||
defaultHeaders: ((Path) -> Map<String, HtmlFragment>)? = null,
|
fileHeaders: ((Path) -> Map<String, HtmlFragment>)? = null,
|
||||||
): Path {
|
): Path {
|
||||||
val actualFile = path?.let {
|
val actualFile = path?.let {
|
||||||
Path.of(System.getProperty("user.home")).resolve(path)
|
Path.of(System.getProperty("user.home")).resolve(path)
|
||||||
} ?: Files.createTempFile("tempPlot", ".html")
|
} ?: Files.createTempFile("tempPlot", ".html")
|
||||||
|
|
||||||
val actualDefaultHeaders = defaultHeaders?.invoke(actualFile)
|
val actualDefaultHeaders = fileHeaders?.invoke(actualFile)
|
||||||
val actualPage = if (actualDefaultHeaders == null) this else copy(pageHeaders = actualDefaultHeaders + pageHeaders)
|
val actualHeaders = if (actualDefaultHeaders == null) pageHeaders else actualDefaultHeaders + pageHeaders
|
||||||
|
|
||||||
val htmlString = actualPage.render(createHTML())
|
val htmlString = createHTML().apply {
|
||||||
|
head {
|
||||||
|
meta {
|
||||||
|
charset = "utf-8"
|
||||||
|
}
|
||||||
|
actualHeaders.values.forEach {
|
||||||
|
fragment(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
visionFragment(context, fragment = content)
|
||||||
|
}
|
||||||
|
}.finalize()
|
||||||
|
|
||||||
Files.writeString(actualFile, htmlString)
|
Files.writeString(actualFile, htmlString)
|
||||||
return actualFile
|
return actualFile
|
||||||
|
@ -115,7 +115,7 @@ public class VisionServer internal constructor(
|
|||||||
context = visionManager.context,
|
context = visionManager.context,
|
||||||
embedData = dataMode == DataServeMode.EMBED,
|
embedData = dataMode == DataServeMode.EMBED,
|
||||||
fetchDataUrl = if (dataMode != DataServeMode.EMBED) "$serverUrl$pagePath/data" else null,
|
fetchDataUrl = if (dataMode != DataServeMode.EMBED) "$serverUrl$pagePath/data" else null,
|
||||||
fetchUpdatesUrl = if (dataMode == DataServeMode.UPDATE) "$serverUrl$pagePath/ws" else null,
|
flowDataUrl = if (dataMode == DataServeMode.UPDATE) "$serverUrl$pagePath/ws" else null,
|
||||||
fragment = visionFragment
|
fragment = visionFragment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -192,18 +192,19 @@ public class VisionServer internal constructor(
|
|||||||
* Compile a fragment to string and serve visions from it
|
* Compile a fragment to string and serve visions from it
|
||||||
*/
|
*/
|
||||||
public fun serveVisionsFromFragment(
|
public fun serveVisionsFromFragment(
|
||||||
|
consumer: TagConsumer<*>,
|
||||||
route: String,
|
route: String,
|
||||||
fragment: HtmlVisionFragment,
|
fragment: HtmlVisionFragment,
|
||||||
): String = createHTML().apply {
|
): Unit {
|
||||||
val visions = visionFragment(
|
val visions = consumer.visionFragment(
|
||||||
visionManager.context,
|
visionManager.context,
|
||||||
embedData = true,
|
embedData = true,
|
||||||
fetchUpdatesUrl = "$serverUrl$route/ws",
|
fetchUpdatesUrl = "$serverUrl$route/ws",
|
||||||
renderScript = true,
|
|
||||||
fragment = fragment
|
fragment = fragment
|
||||||
)
|
)
|
||||||
|
|
||||||
serveVisions(route, visions)
|
serveVisions(route, visions)
|
||||||
}.finalize()
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serve a page, potentially containing any number of visions at a given [route] with given [header].
|
* Serve a page, potentially containing any number of visions at a given [route] with given [header].
|
||||||
@ -248,7 +249,11 @@ public class VisionServer internal constructor(
|
|||||||
title: String = "VisionForge server page '$route'",
|
title: String = "VisionForge server page '$route'",
|
||||||
visionFragment: HtmlVisionFragment,
|
visionFragment: HtmlVisionFragment,
|
||||||
) {
|
) {
|
||||||
page(route, mapOf("title" to VisionPage.title(title)) + headers.associateBy { it.hashCode().toString() }, visionFragment)
|
page(
|
||||||
|
route,
|
||||||
|
mapOf("title" to VisionPage.title(title)) + headers.associateBy { it.hashCode().toString() },
|
||||||
|
visionFragment
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user