Update static/dynamic rendering logic

This commit is contained in:
Alexander Nozik 2022-11-20 19:42:05 +03:00
parent 3c51060e2e
commit eae1316de5
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
17 changed files with 539 additions and 104 deletions

View File

@ -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 {

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View 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
}
}

View File

@ -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,15 +78,29 @@ 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
visionFragment(context, fragment = fragment) val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
}.finalize() 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)
}
}
renderScriptForId(id)
}.finalize()
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =
HTML(produceHtmlString(fragment), isolated ?: isolateFragments) HTML(produceHtmlString(fragment), isolated ?: isolateFragments)

View File

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

View File

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

View File

@ -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()}]" fragment(consumer)
div {
this.id = id
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
) )

View File

@ -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.

View File

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

View File

@ -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,18 +45,16 @@ 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 } else {
} else { 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)
}
} }

View File

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

View File

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