0.2.0 #71

Merged
altavir merged 139 commits from dev into master 2022-01-24 09:44:18 +03:00
31 changed files with 374 additions and 435 deletions
Showing only changes of commit 9745a58873 - Show all commits

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,10 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.plotly.scatter import space.kscience.plotly.scatter
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly import space.kscience.visionforge.plotly.plotly
fun main() { fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
val context = Context {
plugin(PlotlyPlugin)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){
vision { vision {
plotly { plotly {
scatter { 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.div
import kotlinx.html.h1 import kotlinx.html.h1
import space.kscience.dataforge.context.Context
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.random.Random import kotlin.random.Random
fun main() { private val random = Random(112233)
val context = Context {
plugin(Solids)
}
val random = Random(112233) fun main() = makeVisionFile(
context.makeVisionFile(
Paths.get("randomSpheres.html"), Paths.get("randomSpheres.html"),
resourceLocation = ResourceLocation.SYSTEM 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.DGeoManager
import ru.mipt.npm.root.serialization.TGeoManager import ru.mipt.npm.root.serialization.TGeoManager
import ru.mipt.npm.root.toSolid import ru.mipt.npm.root.toSolid
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf import space.kscience.dataforge.meta.isLeaf
@ -22,11 +21,6 @@ private fun Meta.countTypes(): Sequence<String> = sequence {
} }
fun main() { fun main() {
val context = Context {
plugin(Solids)
}
val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/BM@N_geometry.zip")!!).use { val string = ZipInputStream(TGeoManager::class.java.getResourceAsStream("/root/BM@N_geometry.zip")!!).use {
it.nextEntry it.nextEntry
it.readAllBytes().decodeToString() it.readAllBytes().decodeToString()
@ -45,8 +39,9 @@ fun main() {
Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid)) Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid))
//println(Solids.encodeToString(solid)) //println(Solids.encodeToString(solid))
context.makeVisionFile { makeVisionFile {
vision("canvas") { vision("canvas") {
requirePlugin(Solids)
solid solid
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
render<Vision> { vision -> render<Vision> { vision ->
handler.produceHtml { handler.produceHtml {
vision(vision) vision { vision }
} }
} }
@ -62,7 +62,7 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
} }
} }
fragment(fragment.formBody) 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.HtmlFormFragment
import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.visionFragment import space.kscience.visionforge.html.visionFragment
import space.kscience.visionforge.three.server.VisionServer import space.kscience.visionforge.server.VisionServer
import space.kscience.visionforge.three.server.serve import space.kscience.visionforge.server.serve
import space.kscience.visionforge.visionManager import space.kscience.visionforge.visionManager
/** /**
@ -75,7 +75,7 @@ public class VisionForgeForNotebook(override val context: Context) : ContextAwar
fragment: HtmlVisionFragment, fragment: HtmlVisionFragment,
): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment) ): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment)
?: createHTML().apply { ?: createHTML().apply {
visionFragment(context.visionManager, fragment = fragment) visionFragment(context, fragment = fragment)
}.finalize() }.finalize()
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult = public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =

View File

@ -29,7 +29,9 @@ internal class GdmlForJupyter : JupyterPluginBase(
) )
render<Gdml> { gdmlModel -> 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 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) { public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
fragment() fragment()
@ -15,3 +15,8 @@ public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
public fun FlowContent.fragment(fragment: HtmlFragment) { public fun FlowContent.fragment(fragment: HtmlFragment) {
fragment(consumer) fragment(consumer)
} }
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = {
this@plus()
other()
}

View File

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

View File

@ -1,37 +1,52 @@
package space.kscience.visionforge.html package space.kscience.visionforge.html
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.context.Context
import space.kscience.visionforge.VisionManager
//data class HeaderContainer
public data class Page( public data class Page(
public val visionManager: VisionManager, public val context: Context,
public val title: String, public val headers: Map<String, HtmlFragment> = emptyMap(),
public val headers: Map<String, HtmlFragment>,
public val content: HtmlVisionFragment, public val content: HtmlVisionFragment,
) { ) {
public fun <R> render(root: TagConsumer<R>): R = root.apply { public fun <R> render(root: TagConsumer<R>): R = root.apply {
head { head {
meta { meta {
charset = "utf-8" charset = "utf-8"
}
headers.values.forEach { headers.values.forEach {
fragment(it) fragment(it)
} }
} }
title(this@Page.title)
}
body { body {
visionFragment(visionManager, fragment = content) visionFragment(context, fragment = content)
} }
}.finalize() }.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 title(title:String): HtmlFragment = {
public fun VisionManager.page( title(title)
title: String = "VisionForge page", }
vararg headers: Pair<String, HtmlFragment>, }
content: HtmlVisionFragment, }
): Page = Page(this, title, mapOf(*headers), content)

View File

@ -1,6 +1,8 @@
package space.kscience.visionforge.html package space.kscience.visionforge.html
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.MutableMeta 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.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.VisionTagConsumer.Companion.DEFAULT_VISION_NAME
import space.kscience.visionforge.setAsRoot import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
import kotlin.collections.set import kotlin.collections.set
@DslMarker @DslMarker
@ -22,10 +27,25 @@ public annotation class VisionDSL
*/ */
@DFExperimental @DFExperimental
@VisionDSL @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 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) { public inline fun meta(block: MutableMeta.() -> Unit) {
this.meta = Meta(block) 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 * Modified [TagConsumer] that allows rendering output fragments and visions in them
*/ */
@VisionDSL @VisionDSL
@OptIn(DFExperimental::class)
public abstract class VisionTagConsumer<R>( public abstract class VisionTagConsumer<R>(
private val root: TagConsumer<R>, private val root: TagConsumer<R>,
public val manager: VisionManager, public val context: Context,
private val idPrefix: String? = null, private val idPrefix: String? = null,
) : TagConsumer<R> by root { ) : TagConsumer<R> by root {
@ -46,23 +67,26 @@ public abstract class VisionTagConsumer<R>(
/** /**
* Render a vision inside the output fragment * Render a vision inside the output fragment
* @param manager a [VisionManager] to be used in renderer
* @param name name of the output container * @param name name of the output container
* @param vision an object to be rendered * @param vision an object to be rendered
* @param outputMeta optional configuration for the output container * @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 * Create a placeholder for a vision output with optional [Vision] in it
* TODO with multi-receivers could be replaced by [VisionTagConsumer, TagConsumer] extension * 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, name: Name,
vision: Vision? = null, manager: VisionManager,
vision: Vision,
outputMeta: Meta = Meta.EMPTY, outputMeta: Meta = Meta.EMPTY,
): T = div { ): T = div {
id = resolveId(name) id = resolveId(name)
classes = setOf(OUTPUT_CLASS) classes = setOf(OUTPUT_CLASS)
vision.setAsRoot(manager)
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString() attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) { if (!outputMeta.isEmpty()) {
//Hard-code output configuration //Hard-code output configuration
@ -73,9 +97,7 @@ public abstract class VisionTagConsumer<R>(
} }
} }
} }
vision?.let { renderVision(manager, name, vision, outputMeta)
renderVision(name, it, outputMeta)
}
} }
/** /**
@ -83,14 +105,14 @@ public abstract class VisionTagConsumer<R>(
* TODO replace by multi-receiver * TODO replace by multi-receiver
*/ */
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
public inline fun <T> TagConsumer<T>.vision( public fun <T> TagConsumer<T>.vision(
name: Name, name: Name? = null,
@OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision, @OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
): T { ): T {
val output = VisionOutput(manager) val output = VisionOutput(context, name)
val vision = output.visionProvider() val vision = output.visionProvider()
vision.setAsRoot(manager) val actualName = name ?: NameToken(DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
return vision(name, vision, output.meta) return vision(actualName, output.buildVisionManager(), vision, output.meta)
} }
/** /**
@ -98,14 +120,10 @@ public abstract class VisionTagConsumer<R>(
*/ */
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
@VisionDSL @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( public fun <T> TagConsumer<T>.vision(
vision: Vision, name: String?,
): T = vision(NameToken("vision", vision.hashCode().toString()).asName(), vision) @OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
): T = vision(name?.parseAsName(), visionProvider)
/** /**
* Process the resulting object produced by [TagConsumer] * Process the resulting object produced by [TagConsumer]
@ -114,9 +132,7 @@ public abstract class VisionTagConsumer<R>(
//do nothing by default //do nothing by default
} }
override fun finalize(): R { override fun finalize(): R = root.finalize().also { processResult(it) }
return root.finalize().also { processResult(it) }
}
public companion object { public companion object {
public const val OUTPUT_CLASS: String = "visionforge-output" 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.*
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set import space.kscience.dataforge.meta.set
@ -23,8 +22,8 @@ fun FlowContent.renderVisionFragment(
fragment: HtmlVisionFragment, fragment: HtmlVisionFragment,
): Map<Name, Vision> { ): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>() val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(consumer, Global.fetch(VisionManager), idPrefix) { val consumer = object : VisionTagConsumer<Any?>(consumer, Global, idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) { override fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision visionMap[name] = vision
renderer(name, vision, outputMeta) renderer(name, vision, outputMeta)
} }

View File

@ -4,7 +4,6 @@ import kotlinx.html.link
import kotlinx.html.script import kotlinx.html.script
import kotlinx.html.unsafe import kotlinx.html.unsafe
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path 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 Page.Companion.importScriptHeader(
public fun scriptHeader(
scriptResource: String, scriptResource: String,
resourceLocation: ResourceLocation, resourceLocation: ResourceLocation,
htmlPath: Path? = null, htmlPath: Path? = null,

View File

@ -390,4 +390,7 @@ public fun SolidGroup.gdml(gdml: Gdml, key: String? = null, transformer: GdmlLoa
@VisionBuilder @VisionBuilder
@DFExperimental @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 @DFExperimental
public inline fun VisionOutput.plotly( public inline fun VisionOutput.plotly(
block: Plot.() -> Unit, 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.application.*
import io.ktor.features.CORS import io.ktor.features.CORS
@ -14,9 +14,9 @@ import io.ktor.routing.*
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.engine.ApplicationEngine import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer
import io.ktor.util.getOrFail
import io.ktor.websocket.WebSockets import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket import io.ktor.websocket.webSocket
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -32,9 +32,8 @@ import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.flowChanges import space.kscience.visionforge.flowChanges
import space.kscience.visionforge.html.HtmlFragment import space.kscience.visionforge.html.HtmlFragment
import space.kscience.visionforge.html.HtmlVisionFragment import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.fragment
import space.kscience.visionforge.html.visionFragment 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.awt.Desktop
import java.net.URI import java.net.URI
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -48,7 +47,10 @@ public class VisionServer internal constructor(
private val visionManager: VisionManager, private val visionManager: VisionManager,
private val serverUrl: Url, private val serverUrl: Url,
private val root: Route, private val root: Route,
) : Configurable, CoroutineScope by root.application { ) : Configurable {
public val application: Application get() = root.application
override val meta: ObservableMutableMeta = MutableMeta() 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")) 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( private fun HTML.visionPage(
title: String, title: String,
pagePath: String, pagePath: String,
headers: List<HtmlFragment>, header: HtmlFragment,
visionFragment: HtmlVisionFragment, visionFragment: HtmlVisionFragment,
): Map<Name, Vision> { ): Map<Name, Vision> {
var visionMap: Map<Name, Vision>? = null var visionMap: Map<Name, Vision>? = null
@ -99,16 +89,14 @@ public class VisionServer internal constructor(
head { head {
meta { meta {
charset = "utf-8" charset = "utf-8"
(globalHeaders + headers).forEach { header()
fragment(it)
}
} }
title(title) title(title)
} }
body { body {
//Load the fragment and remember all loaded visions //Load the fragment and remember all loaded visions
visionMap = visionFragment( visionMap = visionFragment(
manager = visionManager, context = visionManager.context,
embedData = true, embedData = true,
fetchUpdatesUrl = "$serverUrl$pagePath/ws", fetchUpdatesUrl = "$serverUrl$pagePath/ws",
fragment = visionFragment fragment = visionFragment
@ -127,9 +115,7 @@ public class VisionServer internal constructor(
//Update websocket //Update websocket
webSocket("ws") { webSocket("ws") {
val name: String = call.request.queryParameters["name"] val name: String = call.request.queryParameters.getOrFail("name")
?: error("Vision name is not defined in parameters")
application.log.debug("Opened server socket for $name") application.log.debug("Opened server socket for $name")
val vision: Vision = visions[Name.parse(name)] ?: error("Plot with id='$name' not registered") 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 //Plots in their json representation
get("data") { get("data") {
val name: String = call.request.queryParameters["name"] val name: String = call.request.queryParameters.getOrFail("name")
?: error("Vision name is not defined in parameters")
val vision: Vision? = visions[Name.parse(name)] val vision: Vision? = visions[Name.parse(name)]
if (vision == null) { if (vision == null) {
@ -178,7 +163,7 @@ public class VisionServer internal constructor(
/** /**
* Serve visions in a given [route] without providing a page template * Serve visions in a given [route] without providing a page template
*/ */
public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit { public fun serveVisions(route: String, visions: Map<Name, Vision>) {
root.route(route) { root.route(route) {
serveVisions(this, visions) serveVisions(this, visions)
} }
@ -192,7 +177,7 @@ public class VisionServer internal constructor(
fragment: HtmlVisionFragment, fragment: HtmlVisionFragment,
): String = createHTML().apply { ): String = createHTML().apply {
val visions = visionFragment( val visions = visionFragment(
visionManager, visionManager.context,
embedData = true, embedData = true,
fetchUpdatesUrl = "$serverUrl$route/ws", fetchUpdatesUrl = "$serverUrl$route/ws",
renderScript = true, 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]. * Serve a page, potentially containing any number of visions at a given [pagePath] with given [headers].
*
*/ */
public fun page( public fun page(
pagePath: String = DEFAULT_PAGE, pagePath: String = DEFAULT_PAGE,
title: String = "VisionForge server page '$pagePath'", title: String = "VisionForge server page '$pagePath'",
headers: List<HtmlFragment> = emptyList(), header: HtmlFragment = {},
visionFragment: HtmlVisionFragment, visionFragment: HtmlVisionFragment,
) { ) {
val visions = HashMap<Name, Vision>() val visions = HashMap<Name, Vision>()
@ -216,7 +200,7 @@ public class VisionServer internal constructor(
val cachedHtml: String? = if (cacheFragments) { val cachedHtml: String? = if (cacheFragments) {
//Create and cache page html and map of visions //Create and cache page html and map of visions
createHTML(true).html { createHTML(true).html {
visions.putAll(visionPage(title, pagePath, headers, visionFragment)) visions.putAll(visionPage(title, pagePath, header, visionFragment))
} }
} else { } else {
null null
@ -230,7 +214,7 @@ public class VisionServer internal constructor(
//re-create html and vision list on each call //re-create html and vision list on each call
call.respondHtml { call.respondHtml {
visions.clear() visions.clear()
visions.putAll(visionPage(title, pagePath, headers, visionFragment)) visions.putAll(visionPage(title, pagePath, header, visionFragment))
} }
} else { } else {
//Use cached html //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 * Attach VisionForge server application to given server
*/ */

View File

@ -69,5 +69,7 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
@VisionBuilder @VisionBuilder
@DFExperimental @DFExperimental
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup {
SolidGroup().apply(block) 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( public inline fun VisionOutput.table(
vararg headers: ColumnHeader<Value>, vararg headers: ColumnHeader<Value>,
block: MutableRowTable<Value>.() -> Unit, block: MutableRowTable<Value>.() -> Unit,
): VisionOfTable = RowTable(*headers, block = block).toVision() ): VisionOfTable {
requirePlugin(TableVisionPlugin)
return RowTable(*headers, block = block).toVision()
}
@DFExperimental @DFExperimental
public inline fun VisionOutput.columnTable( public inline fun VisionOutput.columnTable(
@ -99,6 +102,7 @@ public inline fun VisionOutput.columnTable(
public fun VisionOutput.columnTable( public fun VisionOutput.columnTable(
vararg dataAndHeaders: Pair<ColumnHeader<Value>, List<Any?>>, vararg dataAndHeaders: Pair<ColumnHeader<Value>, List<Any?>>,
): VisionOfTable { ): VisionOfTable {
requirePlugin(TableVisionPlugin)
val columns = dataAndHeaders.map { (header, data) -> val columns = dataAndHeaders.map { (header, data) ->
ListColumn(header, data.map { Value.of(it) }) 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.dataforge.misc.DFExperimental
import space.kscience.visionforge.runVisionClient 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())
}