Refactor page headers and fragment requirements

This commit is contained in:
Alexander Nozik 2022-01-20 11:13:17 +03:00
parent bfa7f5ea57
commit 9745a58873
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
31 changed files with 374 additions and 435 deletions

View File

@ -36,13 +36,13 @@ internal class VisionForgePlayGroundForJupyter : JupyterPluginBase(
render<Gdml> { gdmlModel ->
handler.produceHtml {
vision(gdmlModel.toVision())
vision { gdmlModel.toVision() }
}
}
render<Plot> { plot ->
handler.produceHtml {
vision(plot.asVision())
vision { plot.asVision() }
}
}
}

View File

@ -1,7 +1,6 @@
package space.kscience.visionforge.examples
import kotlinx.html.h2
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.values.ValueType
import space.kscience.plotly.layout
import space.kscience.plotly.models.ScatterMode
@ -10,25 +9,15 @@ import space.kscience.plotly.scatter
import space.kscience.tables.ColumnHeader
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.markup.markdown
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.box
import space.kscience.visionforge.solid.solid
import space.kscience.visionforge.solid.z
import space.kscience.visionforge.tables.TableVisionPlugin
import space.kscience.visionforge.tables.columnTable
import java.nio.file.Paths
fun main() {
val context = Context {
plugin(Solids)
plugin(PlotlyPlugin)
plugin(TableVisionPlugin)
}
context.makeVisionFile(
fun main() = makeVisionFile(
Paths.get("VisionForgeDemo.html"),
resourceLocation = ResourceLocation.EMBED
) {
@ -183,4 +172,3 @@ fun main() {
""".trimIndent()
}
}
}

View File

@ -3,21 +3,19 @@ package space.kscience.visionforge.examples
import kotlinx.html.*
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.formFragment
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.three.server.close
import space.kscience.visionforge.three.server.openInBrowser
import space.kscience.visionforge.three.server.serve
import space.kscience.visionforge.three.server.useScript
import space.kscience.visionforge.server.close
import space.kscience.visionforge.server.openInBrowser
import space.kscience.visionforge.server.serve
fun main() {
val visionManager = Global.fetch(VisionManager)
val server = visionManager.serve {
useScript("js/visionforge-playground.js")
page {
page(header = Page.scriptHeader("js/visionforge-playground.js")) {
val form = formFragment("form") {
label {
htmlFor = "fname"
@ -50,7 +48,7 @@ fun main() {
}
}
vision("form".asName(), form)
vision("form") { form }
form.onPropertyChange {
println(this)
}

View File

@ -1,19 +1,13 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids
fun main() {
val context = Context {
plugin(Solids)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){
vision("canvas") {
requirePlugin(Solids)
GdmlShowCase.cubes().toVision()
}
}
}

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.gdml.*
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation
@ -10,13 +9,9 @@ import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.visible
import java.nio.file.Path
fun main() {
val context = Context {
plugin(Solids)
}
context.makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceLocation.EMBED) {
fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceLocation.EMBED) {
vision("canvas") {
requirePlugin(Solids)
Gdml {
// geometry variables
val worldSize = 500
@ -241,4 +236,3 @@ fun main() {
}
}
}
}

View File

@ -1,16 +1,12 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.solid.Solids
fun main() {
val context = Context {
plugin(Solids)
}
context.makeVisionFile {
vision("canvas") { GdmlShowCase.babyIaxo().toVision() }
fun main() = makeVisionFile {
vision("canvas") {
requirePlugin(Solids)
GdmlShowCase.babyIaxo().toVision()
}
}

View File

@ -1,16 +1,10 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.plotly.scatter
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
fun main() {
val context = Context {
plugin(PlotlyPlugin)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
vision {
plotly {
scatter {
@ -20,4 +14,3 @@ fun main() {
}
}
}
}

View File

@ -2,20 +2,14 @@ package space.kscience.visionforge.examples
import kotlinx.html.div
import kotlinx.html.h1
import space.kscience.dataforge.context.Context
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.*
import java.nio.file.Paths
import kotlin.random.Random
fun main() {
val context = Context {
plugin(Solids)
}
private val random = Random(112233)
val random = Random(112233)
context.makeVisionFile(
fun main() = makeVisionFile(
Paths.get("randomSpheres.html"),
resourceLocation = ResourceLocation.SYSTEM
) {
@ -38,4 +32,3 @@ fun main() {
}
}
}
}

View File

@ -3,7 +3,6 @@ package space.kscience.visionforge.examples
import ru.mipt.npm.root.DGeoManager
import ru.mipt.npm.root.serialization.TGeoManager
import ru.mipt.npm.root.toSolid
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf
@ -22,11 +21,6 @@ private fun Meta.countTypes(): Sequence<String> = sequence {
}
fun main() {
val context = Context {
plugin(Solids)
}
val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/BM@N_geometry.zip")!!).use {
it.nextEntry
it.readAllBytes().decodeToString()
@ -45,8 +39,9 @@ fun main() {
Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid))
//println(Solids.encodeToString(solid))
context.makeVisionFile {
makeVisionFile {
vision("canvas") {
requirePlugin(Solids)
solid
}
}

View File

@ -1,34 +1,25 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.context.Global
import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.html.VisionTagConsumer
import space.kscience.visionforge.html.page
import space.kscience.visionforge.html.scriptHeader
import space.kscience.visionforge.html.importScriptHeader
import space.kscience.visionforge.makeFile
import space.kscience.visionforge.three.server.VisionServer
import space.kscience.visionforge.three.server.useScript
import space.kscience.visionforge.visionManager
import java.awt.Desktop
import java.nio.file.Path
public fun VisionServer.usePlayground(): Unit {
useScript("js/visionforge-playground.js")
}
@OptIn(DFExperimental::class)
public fun Context.makeVisionFile(
public fun makeVisionFile(
path: Path? = null,
title: String = "VisionForge page",
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
show: Boolean = true,
content: VisionTagConsumer<*>.() -> Unit,
content: HtmlVisionFragment,
): Unit {
val actualPath = visionManager.page(title, content = content).makeFile(path) { actualPath ->
val actualPath = Page(Global, content = content).makeFile(path) { actualPath ->
mapOf(
"playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath),
"title" to Page.title(title),
"playground" to Page.importScriptHeader("js/visionforge-playground.js", resourceLocation, actualPath),
)
}
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())

View File

@ -1,15 +1,12 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.box
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.material
import space.kscience.visionforge.solid.solid
fun main() {
val context = Context {
plugin(Solids)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
vision("canvas") {
solid {
box(100, 100, 100)
@ -19,4 +16,3 @@ fun main() {
}
}
}
}

View File

@ -1,22 +1,17 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.values.ValueType
import space.kscience.tables.ColumnHeader
import space.kscience.tables.valueRow
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.tables.TableVisionPlugin
import space.kscience.visionforge.tables.table
import kotlin.math.pow
fun main() {
val context = Context {
plugin(TableVisionPlugin)
}
val x by ColumnHeader.value(ValueType.NUMBER)
val y by ColumnHeader.value(ValueType.NUMBER)
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
vision {
table(x, y) {
repeat(100) {

View File

@ -1,19 +1,22 @@
package ru.mipt.npm.sat
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.html.div
import kotlinx.html.h1
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.plus
import space.kscience.visionforge.server.close
import space.kscience.visionforge.server.openInBrowser
import space.kscience.visionforge.server.serve
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.three.server.*
import space.kscience.visionforge.three.threeJsHeader
import space.kscience.visionforge.visionManager
import kotlin.random.Random
fun main() {
val satContext = Context("sat") {
plugin(Solids)
@ -23,20 +26,17 @@ fun main() {
val sat = visionOfSatellite(ySegments = 3)
val server = satContext.visionManager.serve {
//use client library
useThreeJs()
//use css
useCss("css/styles.css")
page {
page(header = Page.threeJsHeader + Page.styleSheetHeader("css/styles.css")) {
div("flex-column") {
h1 { +"Satellite detector demo" }
vision(sat)
vision { sat }
}
}
}
server.openInBrowser()
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch {
while (isActive) {
val randomLayer = Random.nextInt(1, 11)

View File

@ -1,7 +1,6 @@
package space.kscience.visionforge.solid.demo
import kotlinx.browser.document
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@ -30,7 +29,8 @@ private class ThreeDemoApp : Application {
}
}
}
GlobalScope.launch {
launch {
while (isActive) {
delay(500)
boxes.forEach { box ->

View File

@ -44,7 +44,7 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
render<Vision> { vision ->
handler.produceHtml {
vision(vision)
vision { vision }
}
}
@ -62,7 +62,7 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
}
}
fragment(fragment.formBody)
vision(fragment.vision)
vision { fragment.vision }
}
}

View File

@ -18,8 +18,8 @@ import space.kscience.dataforge.meta.string
import space.kscience.visionforge.html.HtmlFormFragment
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.server.VisionServer
import space.kscience.visionforge.server.serve
import space.kscience.visionforge.visionManager
/**
@ -75,7 +75,7 @@ public class VisionForgeForNotebook(override val context: Context) : ContextAwar
fragment: HtmlVisionFragment,
): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment)
?: createHTML().apply {
visionFragment(context.visionManager, fragment = fragment)
visionFragment(context, fragment = fragment)
}.finalize()
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =

View File

@ -29,7 +29,9 @@ internal class GdmlForJupyter : JupyterPluginBase(
)
render<Gdml> { gdmlModel ->
handler.produceHtml { vision(gdmlModel.toVision()) }
handler.produceHtml {
vision { gdmlModel.toVision() }
}
}
}
}

View File

@ -6,7 +6,7 @@ import kotlinx.html.stream.createHTML
public typealias HtmlFragment = TagConsumer<*>.() -> Unit
public fun HtmlFragment.render(): String = createHTML().apply(this).finalize()
public fun HtmlFragment.renderToString(): String = createHTML().apply(this).finalize()
public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
fragment()
@ -15,3 +15,8 @@ public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
public fun FlowContent.fragment(fragment: HtmlFragment) {
fragment(consumer)
}
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = {
this@plus()
other()
}

View File

@ -1,6 +1,8 @@
package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
@ -28,7 +30,7 @@ internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
* @param renderScript if true add rendering script after the fragment
*/
public fun TagConsumer<*>.visionFragment(
manager: VisionManager,
context: Context = Global,
embedData: Boolean = true,
fetchDataUrl: String? = null,
fetchUpdatesUrl: String? = null,
@ -37,8 +39,8 @@ public fun TagConsumer<*>.visionFragment(
fragment: HtmlVisionFragment,
): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(this@visionFragment, manager, idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
val consumer = object : VisionTagConsumer<Any?>(this@visionFragment, context, idPrefix) {
override fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
// Toggle update mode
@ -78,19 +80,19 @@ public fun TagConsumer<*>.visionFragment(
}
public fun FlowContent.visionFragment(
manager: VisionManager,
context: Context = Global,
embedData: Boolean = true,
fetchDataUrl: String? = null,
fetchUpdatesUrl: String? = null,
idPrefix: String? = null,
renderSctipt: Boolean = true,
renderScript: Boolean = true,
fragment: HtmlVisionFragment,
): Map<Name, Vision> = consumer.visionFragment(
manager,
context,
embedData,
fetchDataUrl,
fetchUpdatesUrl,
idPrefix,
renderSctipt,
renderScript,
fragment
)

View File

@ -1,37 +1,52 @@
package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.VisionManager
//data class HeaderContainer
import space.kscience.dataforge.context.Context
public data class Page(
public val visionManager: VisionManager,
public val title: String,
public val headers: Map<String, HtmlFragment>,
public val context: Context,
public val headers: Map<String, HtmlFragment> = emptyMap(),
public val content: HtmlVisionFragment,
) {
public fun <R> render(root: TagConsumer<R>): R = root.apply {
head {
meta {
charset = "utf-8"
}
headers.values.forEach {
fragment(it)
}
}
title(this@Page.title)
}
body {
visionFragment(visionManager, fragment = content)
visionFragment(context, fragment = content)
}
}.finalize()
public companion object{
/**
* Use a script with given [src] as a global header for all pages.
*/
public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = {
script {
type = "text/javascript"
this.src = src
block()
}
}
/**
* Use css with given stylesheet link as a global header for all pages.
*/
public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = {
link {
rel = "stylesheet"
this.href = href
block()
}
}
@DFExperimental
public fun VisionManager.page(
title: String = "VisionForge page",
vararg headers: Pair<String, HtmlFragment>,
content: HtmlVisionFragment,
): Page = Page(this, title, mapOf(*headers), content)
public fun title(title:String): HtmlFragment = {
title(title)
}
}
}

View File

@ -1,6 +1,8 @@
package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.MutableMeta
@ -9,9 +11,12 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.VisionTagConsumer.Companion.DEFAULT_VISION_NAME
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
import kotlin.collections.set
@DslMarker
@ -22,10 +27,25 @@ public annotation class VisionDSL
*/
@DFExperimental
@VisionDSL
public class VisionOutput @PublishedApi internal constructor(public val manager: VisionManager) {
public class VisionOutput @PublishedApi internal constructor(public val context: Context, public val name: Name?) {
public var meta: Meta = Meta.EMPTY
//TODO expose a way to define required plugins.
private val requirements: MutableSet<PluginFactory<*>> = HashSet()
public fun requirePlugin(factory: PluginFactory<*>) {
requirements.add(factory)
}
internal fun buildVisionManager(): VisionManager =
if (requirements.all { req -> context.plugins.find(true) { it.tag == req.tag } != null }) {
context.visionManager
} else {
val newContext = context.buildContext(NameToken(DEFAULT_VISION_NAME, name.toString()).asName()) {
plugin(VisionManager)
requirements.forEach { plugin(it) }
}
newContext.visionManager
}
public inline fun meta(block: MutableMeta.() -> Unit) {
this.meta = Meta(block)
@ -36,9 +56,10 @@ public class VisionOutput @PublishedApi internal constructor(public val manager:
* Modified [TagConsumer] that allows rendering output fragments and visions in them
*/
@VisionDSL
@OptIn(DFExperimental::class)
public abstract class VisionTagConsumer<R>(
private val root: TagConsumer<R>,
public val manager: VisionManager,
public val context: Context,
private val idPrefix: String? = null,
) : TagConsumer<R> by root {
@ -46,23 +67,26 @@ public abstract class VisionTagConsumer<R>(
/**
* Render a vision inside the output fragment
* @param manager a [VisionManager] to be used in renderer
* @param name name of the output container
* @param vision an object to be rendered
* @param outputMeta optional configuration for the output container
*/
protected abstract fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta)
protected abstract fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta)
/**
* Create a placeholder for a vision output with optional [Vision] in it
* TODO with multi-receivers could be replaced by [VisionTagConsumer, TagConsumer] extension
*/
public fun <T> TagConsumer<T>.vision(
private fun <T> TagConsumer<T>.vision(
name: Name,
vision: Vision? = null,
manager: VisionManager,
vision: Vision,
outputMeta: Meta = Meta.EMPTY,
): T = div {
id = resolveId(name)
classes = setOf(OUTPUT_CLASS)
vision.setAsRoot(manager)
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) {
//Hard-code output configuration
@ -73,9 +97,7 @@ public abstract class VisionTagConsumer<R>(
}
}
}
vision?.let {
renderVision(name, it, outputMeta)
}
renderVision(manager, name, vision, outputMeta)
}
/**
@ -83,14 +105,14 @@ public abstract class VisionTagConsumer<R>(
* TODO replace by multi-receiver
*/
@OptIn(DFExperimental::class)
public inline fun <T> TagConsumer<T>.vision(
name: Name,
public fun <T> TagConsumer<T>.vision(
name: Name? = null,
@OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
): T {
val output = VisionOutput(manager)
val output = VisionOutput(context, name)
val vision = output.visionProvider()
vision.setAsRoot(manager)
return vision(name, vision, output.meta)
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
return vision(actualName, output.buildVisionManager(), vision, output.meta)
}
/**
@ -98,14 +120,10 @@ public abstract class VisionTagConsumer<R>(
*/
@OptIn(DFExperimental::class)
@VisionDSL
public inline fun <T> TagConsumer<T>.vision(
name: String = DEFAULT_VISION_NAME,
visionProvider: VisionOutput.() -> Vision,
): T = vision(Name.parse(name), visionProvider)
public fun <T> TagConsumer<T>.vision(
vision: Vision,
): T = vision(NameToken("vision", vision.hashCode().toString()).asName(), vision)
name: String?,
@OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
): T = vision(name?.parseAsName(), visionProvider)
/**
* Process the resulting object produced by [TagConsumer]
@ -114,9 +132,7 @@ public abstract class VisionTagConsumer<R>(
//do nothing by default
}
override fun finalize(): R {
return root.finalize().also { processResult(it) }
}
override fun finalize(): R = root.finalize().also { processResult(it) }
public companion object {
public const val OUTPUT_CLASS: String = "visionforge-output"

View File

@ -3,7 +3,6 @@ package space.kscience.visionforge.html
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set
@ -23,8 +22,8 @@ fun FlowContent.renderVisionFragment(
fragment: HtmlVisionFragment,
): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(consumer, Global.fetch(VisionManager), idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
val consumer = object : VisionTagConsumer<Any?>(consumer, Global, idPrefix) {
override fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
renderer(name, vision, outputMeta)
}

View File

@ -4,7 +4,6 @@ import kotlinx.html.link
import kotlinx.html.script
import kotlinx.html.unsafe
import org.slf4j.LoggerFactory
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.VisionManager
import java.nio.file.Files
import java.nio.file.Path
@ -113,10 +112,9 @@ internal fun fileCssHeader(
}
/**
* Make a script header, automatically copying file to appropriate location
* Make a script header from a resource file, automatically copying file to appropriate location
*/
@DFExperimental
public fun scriptHeader(
public fun Page.Companion.importScriptHeader(
scriptResource: String,
resourceLocation: ResourceLocation,
htmlPath: Path? = null,

View File

@ -390,4 +390,7 @@ public fun SolidGroup.gdml(gdml: Gdml, key: String? = null, transformer: GdmlLoa
@VisionBuilder
@DFExperimental
public inline fun VisionOutput.gdml(block: Gdml.() -> Unit): SolidGroup = Gdml(block).toVision()
public inline fun VisionOutput.gdml(block: Gdml.() -> Unit): SolidGroup {
requirePlugin(Solids)
return Gdml(block).toVision()
}

View File

@ -24,4 +24,7 @@ public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
@DFExperimental
public inline fun VisionOutput.plotly(
block: Plot.() -> Unit,
): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block))
): VisionOfPlotly {
requirePlugin(PlotlyPlugin)
return VisionOfPlotly(Plotly.plot(block))
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.three.server
package space.kscience.visionforge.server
import io.ktor.application.*
import io.ktor.features.CORS
@ -14,9 +14,9 @@ import io.ktor.routing.*
import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.util.getOrFail
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@ -32,9 +32,8 @@ import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.flowChanges
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.server.VisionServer.Companion.DEFAULT_PAGE
import java.awt.Desktop
import java.net.URI
import kotlin.time.Duration.Companion.milliseconds
@ -48,7 +47,10 @@ public class VisionServer internal constructor(
private val visionManager: VisionManager,
private val serverUrl: Url,
private val root: Route,
) : Configurable, CoroutineScope by root.application {
) : Configurable {
public val application: Application get() = root.application
override val meta: ObservableMutableMeta = MutableMeta()
/**
@ -76,22 +78,10 @@ public class VisionServer internal constructor(
*/
public var dataUpdate: Boolean by meta.boolean(true, Name.parse("data.update"))
/**
* a list of headers that should be applied to all pages
*/
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
/**
* Add a header to all pages produced by this server
*/
public fun header(block: TagConsumer<*>.() -> Unit) {
globalHeaders.add(block)
}
private fun HTML.visionPage(
title: String,
pagePath: String,
headers: List<HtmlFragment>,
header: HtmlFragment,
visionFragment: HtmlVisionFragment,
): Map<Name, Vision> {
var visionMap: Map<Name, Vision>? = null
@ -99,16 +89,14 @@ public class VisionServer internal constructor(
head {
meta {
charset = "utf-8"
(globalHeaders + headers).forEach {
fragment(it)
}
header()
}
title(title)
}
body {
//Load the fragment and remember all loaded visions
visionMap = visionFragment(
manager = visionManager,
context = visionManager.context,
embedData = true,
fetchUpdatesUrl = "$serverUrl$pagePath/ws",
fragment = visionFragment
@ -127,9 +115,7 @@ public class VisionServer internal constructor(
//Update websocket
webSocket("ws") {
val name: String = call.request.queryParameters["name"]
?: error("Vision name is not defined in parameters")
val name: String = call.request.queryParameters.getOrFail("name")
application.log.debug("Opened server socket for $name")
val vision: Vision = visions[Name.parse(name)] ?: error("Plot with id='$name' not registered")
@ -158,8 +144,7 @@ public class VisionServer internal constructor(
}
//Plots in their json representation
get("data") {
val name: String = call.request.queryParameters["name"]
?: error("Vision name is not defined in parameters")
val name: String = call.request.queryParameters.getOrFail("name")
val vision: Vision? = visions[Name.parse(name)]
if (vision == null) {
@ -178,7 +163,7 @@ public class VisionServer internal constructor(
/**
* 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>) {
root.route(route) {
serveVisions(this, visions)
}
@ -192,7 +177,7 @@ public class VisionServer internal constructor(
fragment: HtmlVisionFragment,
): String = createHTML().apply {
val visions = visionFragment(
visionManager,
visionManager.context,
embedData = true,
fetchUpdatesUrl = "$serverUrl$route/ws",
renderScript = true,
@ -203,12 +188,11 @@ public class VisionServer internal constructor(
/**
* Serve a page, potentially containing any number of visions at a given [pagePath] with given [headers].
*
*/
public fun page(
pagePath: String = DEFAULT_PAGE,
title: String = "VisionForge server page '$pagePath'",
headers: List<HtmlFragment> = emptyList(),
header: HtmlFragment = {},
visionFragment: HtmlVisionFragment,
) {
val visions = HashMap<Name, Vision>()
@ -216,7 +200,7 @@ public class VisionServer internal constructor(
val cachedHtml: String? = if (cacheFragments) {
//Create and cache page html and map of visions
createHTML(true).html {
visions.putAll(visionPage(title, pagePath, headers, visionFragment))
visions.putAll(visionPage(title, pagePath, header, visionFragment))
}
} else {
null
@ -230,7 +214,7 @@ public class VisionServer internal constructor(
//re-create html and vision list on each call
call.respondHtml {
visions.clear()
visions.putAll(visionPage(title, pagePath, headers, visionFragment))
visions.putAll(visionPage(title, pagePath, header, visionFragment))
}
} else {
//Use cached html
@ -249,32 +233,6 @@ public class VisionServer internal constructor(
}
}
/**
* Use a script with given [src] as a global header for all pages.
*/
public inline fun VisionServer.useScript(src: String, crossinline block: SCRIPT.() -> Unit = {}) {
header {
script {
type = "text/javascript"
this.src = src
block()
}
}
}
/**
* Use css with given stylesheet link as a global header for all pages.
*/
public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() -> Unit = {}) {
header {
link {
rel = "stylesheet"
this.href = href
block()
}
}
}
/**
* Attach VisionForge server application to given server
*/

View File

@ -69,5 +69,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
@VisionBuilder
@DFExperimental
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup =
SolidGroup().apply(block)
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup {
requirePlugin(Solids)
return SolidGroup().apply(block)
}

View File

@ -87,7 +87,10 @@ public fun Table<Number>.toVision(): VisionOfTable = toVision { (it ?: Double.Na
public inline fun VisionOutput.table(
vararg headers: ColumnHeader<Value>,
block: MutableRowTable<Value>.() -> Unit,
): VisionOfTable = RowTable(*headers, block = block).toVision()
): VisionOfTable {
requirePlugin(TableVisionPlugin)
return RowTable(*headers, block = block).toVision()
}
@DFExperimental
public inline fun VisionOutput.columnTable(
@ -99,6 +102,7 @@ public inline fun VisionOutput.columnTable(
public fun VisionOutput.columnTable(
vararg dataAndHeaders: Pair<ColumnHeader<Value>, List<Any?>>,
): VisionOfTable {
requirePlugin(TableVisionPlugin)
val columns = dataAndHeaders.map { (header, data) ->
ListColumn(header, data.map { Value.of(it) })
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.three.server
package space.kscience.visionforge.three
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.runVisionClient

View File

@ -1,30 +0,0 @@
package space.kscience.visionforge.three.server
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.html.page
import space.kscience.visionforge.html.scriptHeader
import space.kscience.visionforge.makeFile
import java.awt.Desktop
import java.nio.file.Path
public fun VisionServer.useThreeJs(): Unit {
useScript("js/visionforge-three.js")
}
@DFExperimental
public fun VisionManager.makeThreeJsFile(
content: HtmlVisionFragment,
path: Path? = null,
title: String = "VisionForge page",
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
show: Boolean = true,
): Unit {
val actualPath = page(title, content = content).makeFile(path) { actualPath ->
mapOf("threeJs" to scriptHeader("js/visionforge-three.js", resourceLocation, actualPath))
}
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
}

View File

@ -0,0 +1,29 @@
package space.kscience.visionforge.three
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.html.*
import space.kscience.visionforge.makeFile
import java.awt.Desktop
import java.nio.file.Path
public val Page.Companion.threeJsHeader: HtmlFragment get() = scriptHeader("js/visionforge-three.js")
@DFExperimental
public fun makeThreeJsFile(
path: Path? = null,
title: String = "VisionForge page",
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
show: Boolean = true,
content: HtmlVisionFragment,
): Unit {
val actualPath = Page(Global, content = content).makeFile(path) { actualPath ->
mapOf(
"title" to Page.title(title),
"threeJs" to Page.importScriptHeader("js/visionforge-three.js", resourceLocation, actualPath)
)
}
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
}