v0.2.0-dev-22 #47
@ -2,7 +2,7 @@ package ru.mipt.npm.sat
|
|||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.vision.client.VisionClient
|
import hep.dataforge.vision.client.VisionClient
|
||||||
import hep.dataforge.vision.client.fetchAndRenderAllVisions
|
import hep.dataforge.vision.client.renderAllVisions
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
|
|
||||||
@ -11,6 +11,6 @@ fun main() {
|
|||||||
Global.plugins.load(ThreePlugin)
|
Global.plugins.load(ThreePlugin)
|
||||||
//Fetch from server and render visions for all outputs
|
//Fetch from server and render visions for all outputs
|
||||||
window.onload = {
|
window.onload = {
|
||||||
Global.plugins.fetch(VisionClient).fetchAndRenderAllVisions()
|
Global.plugins.fetch(VisionClient).renderAllVisions()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,11 +3,11 @@ package ru.mipt.npm.sat
|
|||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vision.VisionManager
|
|
||||||
import hep.dataforge.vision.server.*
|
import hep.dataforge.vision.server.*
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
import hep.dataforge.vision.solid.SolidManager
|
||||||
import hep.dataforge.vision.solid.color
|
import hep.dataforge.vision.solid.color
|
||||||
|
import hep.dataforge.vision.visionManager
|
||||||
import io.ktor.util.KtorExperimentalAPI
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
@ -18,16 +18,21 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
@OptIn(KtorExperimentalAPI::class)
|
@OptIn(KtorExperimentalAPI::class)
|
||||||
fun main() {
|
fun main() {
|
||||||
val sat = visionOfSatellite(
|
//Create a geometry
|
||||||
ySegments = 3,
|
val sat = visionOfSatellite(ySegments = 3)
|
||||||
)
|
|
||||||
|
|
||||||
val context = Global.context("SAT") {
|
val context = Global.context("SAT") {
|
||||||
|
//need to install solids extension, vision manager is installed automatically
|
||||||
plugin(SolidManager)
|
plugin(SolidManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
val server = context.plugins.fetch(VisionManager).serve {
|
// fetch vision manager
|
||||||
|
val visionManager = context.visionManager
|
||||||
|
|
||||||
|
val server = visionManager.serve {
|
||||||
|
//use client library
|
||||||
useScript("visionforge-solid.js")
|
useScript("visionforge-solid.js")
|
||||||
|
//use css
|
||||||
useCss("css/styles.css")
|
useCss("css/styles.css")
|
||||||
page {
|
page {
|
||||||
div("flex-column") {
|
div("flex-column") {
|
||||||
@ -36,11 +41,12 @@ fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.show()
|
server.show()
|
||||||
|
|
||||||
context.launch {
|
context.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val target = "layer[${Random.nextInt(1,11)}].segment[${Random.nextInt(3)},${Random.nextInt(3)}]".toName()
|
val target = "layer[${Random.nextInt(1, 11)}].segment[${Random.nextInt(3)},${Random.nextInt(3)}]".toName()
|
||||||
(sat[target] as? Solid)?.color("red")
|
(sat[target] as? Solid)?.color("red")
|
||||||
delay(300)
|
delay(300)
|
||||||
(sat[target] as? Solid)?.color = "green"
|
(sat[target] as? Solid)?.color = "green"
|
||||||
@ -49,7 +55,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("Press Enter to close server")
|
println("Press Enter to close server")
|
||||||
while (readLine()!="exit"){
|
while (readLine() != "exit") {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,3 +79,8 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
internal val visionSerializer: PolymorphicSerializer<Vision> = PolymorphicSerializer(Vision::class)
|
internal val visionSerializer: PolymorphicSerializer<Vision> = PolymorphicSerializer(Vision::class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a [VisionManager] from this plugin
|
||||||
|
*/
|
||||||
|
public val Context.visionManager: VisionManager get() = plugins.fetch(VisionManager)
|
@ -1,28 +0,0 @@
|
|||||||
package hep.dataforge.vision.html
|
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import kotlinx.html.FlowContent
|
|
||||||
import kotlinx.html.TagConsumer
|
|
||||||
|
|
||||||
public class BindingOutputTagConsumer<T, V : Vision>(
|
|
||||||
root: TagConsumer<T>,
|
|
||||||
prefix: String? = null,
|
|
||||||
) : OutputTagConsumer<T, V>(root, prefix) {
|
|
||||||
|
|
||||||
private val _bindings = HashMap<Name, V>()
|
|
||||||
public val bindings: Map<Name, V> get() = _bindings
|
|
||||||
|
|
||||||
override fun FlowContent.renderVision(name: Name, vision: V, outputMeta: Meta) {
|
|
||||||
_bindings[name] = vision
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T : Any> TagConsumer<T>.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
|
|
||||||
return BindingOutputTagConsumer<T, Vision>(this).apply(fragment.content).bindings
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun FlowContent.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
|
|
||||||
return BindingOutputTagConsumer<Any?, Vision>(consumer).apply(fragment.content).bindings
|
|
||||||
}
|
|
@ -1,20 +1,16 @@
|
|||||||
package hep.dataforge.vision.html
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import kotlinx.html.FlowContent
|
import kotlinx.html.FlowContent
|
||||||
import kotlinx.html.TagConsumer
|
import kotlinx.html.TagConsumer
|
||||||
|
|
||||||
public class HtmlFragment(public val content: TagConsumer<*>.() -> Unit)
|
public typealias HtmlFragment = TagConsumer<*>.()->Unit
|
||||||
|
|
||||||
public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
|
public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
|
||||||
fragment.content(this)
|
fragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun FlowContent.fragment(fragment: HtmlFragment) {
|
public fun FlowContent.fragment(fragment: HtmlFragment) {
|
||||||
fragment.content(consumer)
|
fragment(consumer)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HtmlVisionFragment<V : Vision>(public val content: OutputTagConsumer<*, V>.() -> Unit)
|
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
||||||
|
|
||||||
public fun buildVisionFragment(block: OutputTagConsumer<*, Vision>.() -> Unit): HtmlVisionFragment<Vision> =
|
|
||||||
HtmlVisionFragment(block)
|
|
@ -1,60 +0,0 @@
|
|||||||
package hep.dataforge.vision.html
|
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import hep.dataforge.vision.VisionManager
|
|
||||||
import kotlinx.html.FlowContent
|
|
||||||
import kotlinx.html.TagConsumer
|
|
||||||
import kotlinx.html.script
|
|
||||||
import kotlinx.html.stream.createHTML
|
|
||||||
import kotlinx.html.unsafe
|
|
||||||
|
|
||||||
public typealias HtmlVisionRenderer<V> = FlowContent.(V, Meta) -> Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An [OutputTagConsumer] that directly renders given [Vision] using provided [renderer]
|
|
||||||
*/
|
|
||||||
public class StaticOutputTagConsumer<R, V : Vision>(
|
|
||||||
root: TagConsumer<R>,
|
|
||||||
prefix: String? = null,
|
|
||||||
private val renderer: HtmlVisionRenderer<V>,
|
|
||||||
) : OutputTagConsumer<R, V>(root, prefix) {
|
|
||||||
override fun FlowContent.renderVision(name: Name, vision: V, outputMeta: Meta): Unit = renderer(vision, outputMeta)
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public fun embed(manager: VisionManager): HtmlVisionRenderer<Vision> = { vision: Vision, _: Meta ->
|
|
||||||
script {
|
|
||||||
attributes["class"] = OUTPUT_DATA_CLASS
|
|
||||||
unsafe {
|
|
||||||
+manager.encodeToString(vision)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T : Any> HtmlVisionFragment<Vision>.renderToObject(
|
|
||||||
root: TagConsumer<T>,
|
|
||||||
prefix: String? = null,
|
|
||||||
renderer: HtmlVisionRenderer<Vision>,
|
|
||||||
): T = StaticOutputTagConsumer(root, prefix, renderer).apply(content).finalize()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render an object to HTML embedding the data as script bodies
|
|
||||||
*/
|
|
||||||
public fun <T : Any> HtmlVisionFragment<Vision>.embedToObject(
|
|
||||||
manager: VisionManager,
|
|
||||||
root: TagConsumer<T>,
|
|
||||||
prefix: String? = null,
|
|
||||||
): T = renderToObject(root, prefix, StaticOutputTagConsumer.embed(manager))
|
|
||||||
|
|
||||||
public fun HtmlVisionFragment<Vision>.renderToString(renderer: HtmlVisionRenderer<Vision>): String =
|
|
||||||
renderToObject(createHTML(), null, renderer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a fragment to a string, embedding all visions data
|
|
||||||
*/
|
|
||||||
public fun HtmlVisionFragment<Vision>.embedToString(manager: VisionManager): String =
|
|
||||||
embedToObject(manager, createHTML())
|
|
||||||
|
|
@ -12,7 +12,7 @@ import kotlinx.html.*
|
|||||||
* A placeholder object to attach inline vision builders.
|
* A placeholder object to attach inline vision builders.
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public class VisionOutput {
|
public class VisionOutput @PublishedApi internal constructor(){
|
||||||
public var meta: Meta = Meta.EMPTY
|
public var meta: Meta = Meta.EMPTY
|
||||||
|
|
||||||
public inline fun meta(block: MetaBuilder.() -> Unit) {
|
public inline fun meta(block: MetaBuilder.() -> Unit) {
|
||||||
@ -23,7 +23,7 @@ public class VisionOutput {
|
|||||||
/**
|
/**
|
||||||
* Modified [TagConsumer] that allows rendering output fragments and visions in them
|
* Modified [TagConsumer] that allows rendering output fragments and visions in them
|
||||||
*/
|
*/
|
||||||
public abstract class OutputTagConsumer<R, V : Vision>(
|
public abstract class VisionTagConsumer<R>(
|
||||||
private val root: TagConsumer<R>,
|
private val root: TagConsumer<R>,
|
||||||
private val idPrefix: String? = null,
|
private val idPrefix: String? = null,
|
||||||
) : TagConsumer<R> by root {
|
) : TagConsumer<R> by root {
|
||||||
@ -36,14 +36,14 @@ public abstract class OutputTagConsumer<R, V : Vision>(
|
|||||||
* @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 FlowContent.renderVision(name: Name, vision: V, outputMeta: Meta)
|
protected abstract fun DIV.renderVision(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
|
||||||
*/
|
*/
|
||||||
public fun <T> TagConsumer<T>.vision(
|
public fun <T> TagConsumer<T>.vision(
|
||||||
name: Name,
|
name: Name,
|
||||||
vision: V? = null,
|
vision: Vision? = null,
|
||||||
outputMeta: Meta = Meta.EMPTY,
|
outputMeta: Meta = Meta.EMPTY,
|
||||||
): T = div {
|
): T = div {
|
||||||
id = resolveId(name)
|
id = resolveId(name)
|
||||||
@ -64,7 +64,7 @@ public abstract class OutputTagConsumer<R, V : Vision>(
|
|||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
public inline fun <T> TagConsumer<T>.vision(
|
public inline fun <T> TagConsumer<T>.vision(
|
||||||
name: Name,
|
name: Name,
|
||||||
visionProvider: VisionOutput.() -> V,
|
visionProvider: VisionOutput.() -> Vision,
|
||||||
): T {
|
): T {
|
||||||
val output = VisionOutput()
|
val output = VisionOutput()
|
||||||
val vision = output.visionProvider()
|
val vision = output.visionProvider()
|
||||||
@ -74,11 +74,11 @@ public abstract class OutputTagConsumer<R, V : Vision>(
|
|||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
public inline fun <T> TagConsumer<T>.vision(
|
public inline fun <T> TagConsumer<T>.vision(
|
||||||
name: String,
|
name: String,
|
||||||
visionProvider: VisionOutput.() -> V,
|
visionProvider: VisionOutput.() -> Vision,
|
||||||
): T = vision(name.toName(), visionProvider)
|
): T = vision(name.toName(), visionProvider)
|
||||||
|
|
||||||
public inline fun <T> TagConsumer<T>.vision(
|
public inline fun <T> TagConsumer<T>.vision(
|
||||||
vision: V,
|
vision: Vision,
|
||||||
): T = vision("vision[${vision.hashCode()}]".toName(), vision)
|
): T = vision("vision[${vision.hashCode()}]".toName(), vision)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,6 +97,9 @@ public abstract class OutputTagConsumer<R, V : Vision>(
|
|||||||
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
|
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
|
||||||
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
|
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
|
||||||
|
|
||||||
|
public const val OUTPUT_FETCH_VISION_ATTRIBUTE: String = "data-output-fetch-vision"
|
||||||
|
public const val OUTPUT_FETCH_UPDATE_ATTRIBUTE: String = "data-output-fetch-update"
|
||||||
|
|
||||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||||
public const val DEFAULT_ENDPOINT: String = "."
|
public const val DEFAULT_ENDPOINT: String = "."
|
@ -0,0 +1,42 @@
|
|||||||
|
package hep.dataforge.vision.html
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.vision.Vision
|
||||||
|
import hep.dataforge.vision.VisionManager
|
||||||
|
import kotlinx.html.DIV
|
||||||
|
import kotlinx.html.FlowContent
|
||||||
|
import kotlinx.html.script
|
||||||
|
import kotlinx.html.unsafe
|
||||||
|
|
||||||
|
|
||||||
|
public fun FlowContent.embedVisionFragment(
|
||||||
|
manager: VisionManager,
|
||||||
|
idPrefix: String? = null,
|
||||||
|
fragment: HtmlVisionFragment,
|
||||||
|
) {
|
||||||
|
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
|
||||||
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||||
|
script {
|
||||||
|
attributes["class"] = OUTPUT_DATA_CLASS
|
||||||
|
unsafe {
|
||||||
|
+manager.encodeToString(vision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment(consumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit
|
||||||
|
|
||||||
|
public fun <R> FlowContent.renderVisionFragment(
|
||||||
|
renderer: DIV.(name: Name, vision: Vision, meta: Meta) -> Unit,
|
||||||
|
idPrefix: String? = null,
|
||||||
|
fragment: HtmlVisionFragment,
|
||||||
|
) {
|
||||||
|
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
|
||||||
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) = renderer(name, vision, outputMeta)
|
||||||
|
}
|
||||||
|
fragment(consumer)
|
||||||
|
}
|
@ -4,7 +4,7 @@ import hep.dataforge.meta.Meta
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
|
|
||||||
public interface Output<in V : Vision> {
|
public fun interface Output<in V : Vision> {
|
||||||
public fun render(vision: V)
|
public fun render(vision: V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,9 @@ package hep.dataforge.vision.html
|
|||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.configure
|
import hep.dataforge.meta.configure
|
||||||
import hep.dataforge.meta.set
|
import hep.dataforge.meta.set
|
||||||
import hep.dataforge.vision.Vision
|
|
||||||
import hep.dataforge.vision.VisionBase
|
import hep.dataforge.vision.VisionBase
|
||||||
import hep.dataforge.vision.VisionGroup
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
class HtmlTagTest {
|
class HtmlTagTest {
|
||||||
@ -15,11 +14,11 @@ class HtmlTagTest {
|
|||||||
fun VisionOutput.base(block: VisionBase.() -> Unit) =
|
fun VisionOutput.base(block: VisionBase.() -> Unit) =
|
||||||
VisionBase().apply(block)
|
VisionBase().apply(block)
|
||||||
|
|
||||||
val fragment = buildVisionFragment {
|
val fragment: HtmlVisionFragment = {
|
||||||
div {
|
div {
|
||||||
h1 { +"Head" }
|
h1 { +"Head" }
|
||||||
vision("ddd") {
|
vision("ddd") {
|
||||||
meta{
|
meta {
|
||||||
"metaProperty" put 87
|
"metaProperty" put 87
|
||||||
}
|
}
|
||||||
base {
|
base {
|
||||||
@ -32,7 +31,7 @@ class HtmlTagTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val simpleVisionRenderer: HtmlVisionRenderer<Vision> = { vision, _ ->
|
val simpleVisionRenderer: HtmlVisionRenderer = { _, vision, _ ->
|
||||||
div {
|
div {
|
||||||
h2 { +"Properties" }
|
h2 { +"Properties" }
|
||||||
ul {
|
ul {
|
||||||
@ -46,13 +45,17 @@ class HtmlTagTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupRenderer: HtmlVisionRenderer<VisionGroup> = { group, _ ->
|
val groupRenderer: HtmlVisionRenderer = { _, group, _ ->
|
||||||
p { +"This is group" }
|
p { +"This is group" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStringRender() {
|
fun testStringRender() {
|
||||||
println(fragment.renderToString(simpleVisionRenderer))
|
println(
|
||||||
|
createHTML().div {
|
||||||
|
renderVisionFragment<String>(simpleVisionRenderer, fragment = fragment)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,9 +6,10 @@ import hep.dataforge.meta.MetaSerializer
|
|||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.VisionChange
|
import hep.dataforge.vision.VisionChange
|
||||||
import hep.dataforge.vision.VisionManager
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.html.OutputTagConsumer
|
import hep.dataforge.vision.html.VisionTagConsumer
|
||||||
import hep.dataforge.vision.html.OutputTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||||
import hep.dataforge.vision.html.OutputTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_FETCH_UPDATE_ATTRIBUTE
|
||||||
|
import hep.dataforge.vision.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
@ -43,22 +44,23 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
|
|
||||||
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML
|
private fun Element.getEmbeddedData(className: String): String? = getElementsByClassName(className)[0]?.innerHTML
|
||||||
|
|
||||||
|
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value == "true"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch from server and render a vision, described in a given with [OutputTagConsumer.OUTPUT_CLASS] class.
|
* Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class.
|
||||||
*/
|
*/
|
||||||
public fun renderVisionAt(element: Element, requestUpdates: Boolean = true) {
|
public fun renderVisionAt(element: Element) {
|
||||||
val name = resolveName(element) ?: error("The element is not a vision output")
|
val name = resolveName(element) ?: error("The element is not a vision output")
|
||||||
console.info("Found DF output with name $name")
|
console.info("Found DF output with name $name")
|
||||||
if (!element.classList.contains(OutputTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
|
if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||||
val endpoint = resolveEndpoint(element)
|
|
||||||
console.info("Vision server is resolved to $endpoint")
|
|
||||||
|
|
||||||
val outputMeta = element.getEmbeddedData(OutputTagConsumer.OUTPUT_META_CLASS)?.let {
|
|
||||||
|
val outputMeta = element.getEmbeddedData(VisionTagConsumer.OUTPUT_META_CLASS)?.let {
|
||||||
VisionManager.defaultJson.decodeFromString(MetaSerializer, it)
|
VisionManager.defaultJson.decodeFromString(MetaSerializer, it)
|
||||||
} ?: Meta.EMPTY
|
} ?: Meta.EMPTY
|
||||||
|
|
||||||
//Trying to render embedded vision
|
//Trying to render embedded vision
|
||||||
val embeddedVision = element.getEmbeddedData(OutputTagConsumer.OUTPUT_DATA_CLASS)?.let {
|
val embeddedVision = element.getEmbeddedData(VisionTagConsumer.OUTPUT_DATA_CLASS)?.let {
|
||||||
visionManager.decodeFromString(it)
|
visionManager.decodeFromString(it)
|
||||||
}
|
}
|
||||||
if (embeddedVision != null) {
|
if (embeddedVision != null) {
|
||||||
@ -66,54 +68,60 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
renderer.render(element, embeddedVision, outputMeta)
|
renderer.render(element, embeddedVision, outputMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fetchUrl = URL(endpoint).apply {
|
if(element.getFlag(VisionTagConsumer.OUTPUT_FETCH_VISION_ATTRIBUTE)) {
|
||||||
searchParams.append("name", name)
|
|
||||||
pathname += "/vision"
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info("Fetching vision data from $fetchUrl")
|
val endpoint = resolveEndpoint(element)
|
||||||
window.fetch(fetchUrl).then { response ->
|
console.info("Vision server is resolved to $endpoint")
|
||||||
if (response.ok) {
|
|
||||||
response.text().then { text ->
|
|
||||||
val vision = visionManager.decodeFromString(text)
|
|
||||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
|
||||||
renderer.render(element, vision, outputMeta)
|
|
||||||
if (requestUpdates) {
|
|
||||||
val wsUrl = URL(endpoint).apply {
|
|
||||||
pathname += "/ws"
|
|
||||||
protocol = "ws"
|
|
||||||
searchParams.append("name", name)
|
|
||||||
}
|
|
||||||
WebSocket(wsUrl.toString()).apply {
|
|
||||||
onmessage = { messageEvent ->
|
|
||||||
val stringData: String? = messageEvent.data as? String
|
|
||||||
if (stringData != null) {
|
|
||||||
val dif = visionManager.jsonFormat.decodeFromString(
|
|
||||||
VisionChange.serializer(),
|
|
||||||
stringData
|
|
||||||
)
|
|
||||||
vision.update(dif)
|
|
||||||
} else {
|
|
||||||
console.error("WebSocket message data is not a string")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onopen = {
|
|
||||||
console.info("WebSocket update channel established for output '$name'")
|
|
||||||
}
|
|
||||||
onclose = {
|
|
||||||
console.info("WebSocket update channel closed for output '$name'")
|
|
||||||
}
|
|
||||||
onerror = {
|
|
||||||
console.error("WebSocket update channel error for output '$name'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
val fetchUrl = URL(endpoint).apply {
|
||||||
}
|
searchParams.append("name", name)
|
||||||
} else {
|
pathname += "/vision"
|
||||||
console.error("Failed to fetch initial vision state from $endpoint")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.info("Fetching vision data from $fetchUrl")
|
||||||
|
window.fetch(fetchUrl).then { response ->
|
||||||
|
if (response.ok) {
|
||||||
|
response.text().then { text ->
|
||||||
|
val vision = visionManager.decodeFromString(text)
|
||||||
|
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||||
|
renderer.render(element, vision, outputMeta)
|
||||||
|
if (element.getFlag(OUTPUT_FETCH_UPDATE_ATTRIBUTE)) {
|
||||||
|
val wsUrl = URL(endpoint).apply {
|
||||||
|
pathname += "/ws"
|
||||||
|
protocol = "ws"
|
||||||
|
searchParams.append("name", name)
|
||||||
|
}
|
||||||
|
WebSocket(wsUrl.toString()).apply {
|
||||||
|
onmessage = { messageEvent ->
|
||||||
|
val stringData: String? = messageEvent.data as? String
|
||||||
|
if (stringData != null) {
|
||||||
|
val dif = visionManager.jsonFormat.decodeFromString(
|
||||||
|
VisionChange.serializer(),
|
||||||
|
stringData
|
||||||
|
)
|
||||||
|
vision.update(dif)
|
||||||
|
} else {
|
||||||
|
console.error("WebSocket message data is not a string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onopen = {
|
||||||
|
console.info("WebSocket update channel established for output '$name'")
|
||||||
|
}
|
||||||
|
onclose = {
|
||||||
|
console.info("WebSocket update channel closed for output '$name'")
|
||||||
|
}
|
||||||
|
onerror = {
|
||||||
|
console.error("WebSocket update channel error for output '$name'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch initial vision state from $endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,20 +136,20 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch and render visions for all elements with [OutputTagConsumer.OUTPUT_CLASS] class inside given [element].
|
* Fetch and render visions for all elements with [VisionTagConsumer.OUTPUT_CLASS] class inside given [element].
|
||||||
*/
|
*/
|
||||||
public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) {
|
public fun VisionClient.renderAllVisionsAt(element: Element) {
|
||||||
val elements = element.getElementsByClassName(OutputTagConsumer.OUTPUT_CLASS)
|
val elements = element.getElementsByClassName(VisionTagConsumer.OUTPUT_CLASS)
|
||||||
console.info("Finished search for outputs. Found ${elements.length} items")
|
console.info("Finished search for outputs. Found ${elements.length} items")
|
||||||
elements.asList().forEach { child ->
|
elements.asList().forEach { child ->
|
||||||
renderVisionAt(child, requestUpdates)
|
renderVisionAt(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch visions from the server for all elements with [OutputTagConsumer.OUTPUT_CLASS] class in the document body
|
* Fetch visions from the server for all elements with [VisionTagConsumer.OUTPUT_CLASS] class in the document body
|
||||||
*/
|
*/
|
||||||
public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true) {
|
public fun VisionClient.renderAllVisions() {
|
||||||
val element = document.body ?: error("Document does not have a body")
|
val element = document.body ?: error("Document does not have a body")
|
||||||
fetchVisionsInChildren(element, requestUpdates)
|
renderAllVisionsAt(element)
|
||||||
}
|
}
|
@ -1,17 +1,9 @@
|
|||||||
package hep.dataforge.vision.client
|
package hep.dataforge.vision.client
|
||||||
|
|
||||||
import hep.dataforge.meta.DFExperimental
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.html.BindingOutputTagConsumer
|
import org.w3c.dom.Element
|
||||||
import hep.dataforge.vision.html.HtmlVisionFragment
|
|
||||||
import hep.dataforge.vision.html.OutputTagConsumer
|
|
||||||
import kotlinx.browser.document
|
|
||||||
import kotlinx.html.TagConsumer
|
|
||||||
import org.w3c.dom.*
|
|
||||||
|
|
||||||
@Type(ElementVisionRenderer.TYPE)
|
@Type(ElementVisionRenderer.TYPE)
|
||||||
public interface ElementVisionRenderer {
|
public interface ElementVisionRenderer {
|
||||||
@ -34,44 +26,36 @@ public interface ElementVisionRenderer {
|
|||||||
public const val DEFAULT_RATING: Int = 10
|
public const val DEFAULT_RATING: Int = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
@DFExperimental
|
//@DFExperimental
|
||||||
public fun Map<String, Vision>.bind(rendererFactory: (Vision) -> ElementVisionRenderer) {
|
//public fun Map<String, Vision>.bind(rendererFactory: (Vision) -> ElementVisionRenderer) {
|
||||||
forEach { (id, vision) ->
|
// forEach { (id, vision) ->
|
||||||
val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
// val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||||
rendererFactory(vision).render(element, vision)
|
// rendererFactory(vision).render(element, vision)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
@DFExperimental
|
//@DFExperimental
|
||||||
public fun Element.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer) {
|
//public fun Element.renderAllVisions(
|
||||||
val elements = getElementsByClassName(OutputTagConsumer.OUTPUT_CLASS)
|
// visionProvider: (Name) -> Vision,
|
||||||
elements.asList().forEach { element ->
|
// rendererFactory: (Vision) -> ElementVisionRenderer,
|
||||||
val name = element.attributes[OutputTagConsumer.OUTPUT_NAME_ATTRIBUTE]?.value
|
//) {
|
||||||
if (name == null) {
|
// val elements = getElementsByClassName(VisionTagConsumer.OUTPUT_CLASS)
|
||||||
console.error("Attribute ${OutputTagConsumer.OUTPUT_NAME_ATTRIBUTE} not defined in the output element")
|
// elements.asList().forEach { element ->
|
||||||
return@forEach
|
// val name = element.attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE]?.value
|
||||||
}
|
// if (name == null) {
|
||||||
val vision = visionProvider(name.toName())
|
// console.error("Attribute ${VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE} not defined in the output element")
|
||||||
rendererFactory(vision).render(element, vision)
|
// return@forEach
|
||||||
}
|
// }
|
||||||
}
|
// val vision = visionProvider(name.toName())
|
||||||
|
// rendererFactory(vision).render(element, vision)
|
||||||
@DFExperimental
|
// }
|
||||||
public fun Document.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer): Unit {
|
//}
|
||||||
documentElement?.renderAllVisions(visionProvider,rendererFactory)
|
//
|
||||||
}
|
//@DFExperimental
|
||||||
|
//public fun Document.renderAllVisions(
|
||||||
@DFExperimental
|
// visionProvider: (Name) -> Vision,
|
||||||
public fun HtmlVisionFragment<Vision>.renderInDocument(
|
// rendererFactory: (Vision) -> ElementVisionRenderer,
|
||||||
root: TagConsumer<HTMLElement>,
|
//): Unit {
|
||||||
renderer: ElementVisionRenderer,
|
// documentElement?.renderAllVisions(visionProvider, rendererFactory)
|
||||||
): HTMLElement = BindingOutputTagConsumer<HTMLElement, Vision>(root).apply(content).let { scope ->
|
//}
|
||||||
scope.finalize().apply {
|
|
||||||
scope.bindings.forEach { (name, vision) ->
|
|
||||||
val id = scope.resolveId(name)
|
|
||||||
val element = document.getElementById(id) ?: error("Could not find element with name $name and id $id")
|
|
||||||
renderer.render(element, vision)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
//package hep.dataforge.vision.export
|
|
||||||
//
|
|
||||||
//package kscience.plotly
|
|
||||||
//
|
|
||||||
//import kotlinx.html.*
|
|
||||||
//import kotlinx.html.stream.createHTML
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * A custom HTML fragment including plotly container reference
|
|
||||||
// */
|
|
||||||
//public class PlotlyFragment(public val render: FlowContent.(renderer: PlotlyRenderer) -> Unit)
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * A complete page including headers and title
|
|
||||||
// */
|
|
||||||
//public data class PlotlyPage(
|
|
||||||
// val headers: Collection<HtmlFragment>,
|
|
||||||
// val fragment: PlotlyFragment,
|
|
||||||
// val title: String = "Plotly.kt",
|
|
||||||
// val renderer: PlotlyRenderer = StaticPlotlyRenderer
|
|
||||||
//) {
|
|
||||||
// public fun render(): String = createHTML().html {
|
|
||||||
// head {
|
|
||||||
// meta {
|
|
||||||
// charset = "utf-8"
|
|
||||||
// }
|
|
||||||
// title(this@PlotlyPage.title)
|
|
||||||
// headers.distinct().forEach { it.visit(consumer) }
|
|
||||||
// }
|
|
||||||
// body {
|
|
||||||
// fragment.render(this, renderer)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public fun Plotly.fragment(content: FlowContent.(renderer: PlotlyRenderer) -> Unit): PlotlyFragment = PlotlyFragment(content)
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Create a complete page including plots
|
|
||||||
// */
|
|
||||||
//public fun Plotly.page(
|
|
||||||
// vararg headers: HtmlFragment = arrayOf(cdnPlotlyHeader),
|
|
||||||
// title: String = "Plotly.kt",
|
|
||||||
// renderer: PlotlyRenderer = StaticPlotlyRenderer,
|
|
||||||
// content: FlowContent.(renderer: PlotlyRenderer) -> Unit
|
|
||||||
//): PlotlyPage = PlotlyPage(headers.toList(), fragment(content), title, renderer)
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Convert an html plot fragment to page
|
|
||||||
// */
|
|
||||||
//public fun PlotlyFragment.toPage(
|
|
||||||
// vararg headers: HtmlFragment = arrayOf(cdnPlotlyHeader),
|
|
||||||
// title: String = "Plotly.kt",
|
|
||||||
// renderer: PlotlyRenderer = StaticPlotlyRenderer
|
|
||||||
//): PlotlyPage = PlotlyPage(headers.toList(), this, title, renderer)
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Convert a plot to the sigle-plot page
|
|
||||||
// */
|
|
||||||
//public fun Plot.toPage(
|
|
||||||
// vararg headers: HtmlFragment = arrayOf(cdnPlotlyHeader),
|
|
||||||
// config: PlotlyConfig = PlotlyConfig.empty(),
|
|
||||||
// title: String = "Plotly.kt",
|
|
||||||
// renderer: PlotlyRenderer = StaticPlotlyRenderer
|
|
||||||
//): PlotlyPage = PlotlyFragment {
|
|
||||||
// plot(this@toPage, config = config, renderer = renderer)
|
|
||||||
//}.toPage(*headers, title = title)
|
|
@ -0,0 +1,140 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.vision.html.HtmlFragment
|
||||||
|
import kotlinx.html.link
|
||||||
|
import kotlinx.html.script
|
||||||
|
import kotlinx.html.unsafe
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of resources for plot.
|
||||||
|
*/
|
||||||
|
public enum class ResourceLocation {
|
||||||
|
// /**
|
||||||
|
// * Use cdn or other remote source for assets
|
||||||
|
// */
|
||||||
|
// REMOTE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store assets in a sibling folder `.dataforge/assets` or in a system-wide folder if this is a default temporary file
|
||||||
|
*/
|
||||||
|
LOCAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store assets in a system-window `~/.dataforge/assets` folder
|
||||||
|
*/
|
||||||
|
SYSTEM,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Embed the asset into the html. Could produce very large files.
|
||||||
|
*/
|
||||||
|
EMBED
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the asset exists in given local location and put it there if it does not
|
||||||
|
* @param
|
||||||
|
*/
|
||||||
|
internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path {
|
||||||
|
val fullPath = basePath.resolveSibling(filePath).toAbsolutePath()
|
||||||
|
|
||||||
|
if (Files.exists(fullPath)) {
|
||||||
|
//TODO checksum
|
||||||
|
} else {
|
||||||
|
//TODO add logging
|
||||||
|
|
||||||
|
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
||||||
|
Files.createDirectories(fullPath.parent)
|
||||||
|
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (basePath.isAbsolute && fullPath.startsWith(basePath)) {
|
||||||
|
basePath.relativize(fullPath)
|
||||||
|
} else {
|
||||||
|
filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A header that automatically copies relevant scripts to given path
|
||||||
|
*/
|
||||||
|
internal fun fileScriptHeader(
|
||||||
|
basePath: Path,
|
||||||
|
scriptPath: Path,
|
||||||
|
resource: String
|
||||||
|
): HtmlFragment = {
|
||||||
|
val relativePath = checkOrStoreFile(basePath, scriptPath, resource)
|
||||||
|
script {
|
||||||
|
type = "text/javascript"
|
||||||
|
src = relativePath.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun embedScriptHeader(resource: String): HtmlFragment = {
|
||||||
|
script {
|
||||||
|
type = "text/javascript"
|
||||||
|
unsafe {
|
||||||
|
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
||||||
|
+bytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun fileCssHeader(
|
||||||
|
basePath: Path,
|
||||||
|
cssPath: Path,
|
||||||
|
resource: String
|
||||||
|
): HtmlFragment = {
|
||||||
|
val relativePath = checkOrStoreFile(basePath, cssPath, resource)
|
||||||
|
link {
|
||||||
|
rel = "stylesheet"
|
||||||
|
href = relativePath.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * A system-wide plotly store location
|
||||||
|
// */
|
||||||
|
//val systemHeader = HtmlFragment {
|
||||||
|
// val relativePath = checkOrStoreFile(
|
||||||
|
// Path.of("."),
|
||||||
|
// Path.of(System.getProperty("user.home")).resolve(".plotly/$assetsDirectory$PLOTLY_SCRIPT_PATH"),
|
||||||
|
// PLOTLY_SCRIPT_PATH
|
||||||
|
// )
|
||||||
|
// script {
|
||||||
|
// type = "text/javascript"
|
||||||
|
// src = relativePath.toString()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * embedded plotly script
|
||||||
|
// */
|
||||||
|
//val embededHeader = HtmlFragment {
|
||||||
|
// script {
|
||||||
|
// unsafe {
|
||||||
|
// val bytes = HtmlFragment::class.java.getResourceAsStream(PLOTLY_SCRIPT_PATH).readAllBytes()
|
||||||
|
// +bytes.toString(Charsets.UTF_8)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//internal fun inferPlotlyHeader(
|
||||||
|
// target: Path?,
|
||||||
|
// resourceLocation: ResourceLocation
|
||||||
|
//): HtmlFragment = when (resourceLocation) {
|
||||||
|
// ResourceLocation.REMOTE -> cdnPlotlyHeader
|
||||||
|
// ResourceLocation.LOCAL -> if (target != null) {
|
||||||
|
// localHeader(target)
|
||||||
|
// } else {
|
||||||
|
// systemPlotlyHeader
|
||||||
|
// }
|
||||||
|
// ResourceLocation.SYSTEM -> systemPlotlyHeader
|
||||||
|
// ResourceLocation.EMBED -> embededPlotlyHeader
|
||||||
|
//}
|
@ -0,0 +1,37 @@
|
|||||||
|
package hep.dataforge.vision
|
||||||
|
|
||||||
|
import hep.dataforge.vision.html.*
|
||||||
|
import kotlinx.html.*
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a file with the embedded vision data
|
||||||
|
*/
|
||||||
|
public fun HtmlVisionFragment.makeFile(manager: VisionManager, vararg headers: HtmlFragment, path: Path? = null, show: Boolean = true) {
|
||||||
|
val actualFile = path ?: Files.createTempFile("tempPlot", ".html")
|
||||||
|
Files.createDirectories(actualFile.parent)
|
||||||
|
val htmlString = createHTML().apply {
|
||||||
|
head {
|
||||||
|
meta {
|
||||||
|
charset = "utf-8"
|
||||||
|
headers.forEach {
|
||||||
|
fragment(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title(title)
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
embedVisionFragment(manager, fragment = this@makeFile)
|
||||||
|
}
|
||||||
|
}.finalize()
|
||||||
|
|
||||||
|
Files.writeString(actualFile, htmlString)
|
||||||
|
if (show) {
|
||||||
|
Desktop.getDesktop().browse(actualFile.toFile().toURI())
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,10 @@ import hep.dataforge.vision.Vision
|
|||||||
import hep.dataforge.vision.VisionChange
|
import hep.dataforge.vision.VisionChange
|
||||||
import hep.dataforge.vision.VisionManager
|
import hep.dataforge.vision.VisionManager
|
||||||
import hep.dataforge.vision.flowChanges
|
import hep.dataforge.vision.flowChanges
|
||||||
import hep.dataforge.vision.html.*
|
import hep.dataforge.vision.html.HtmlFragment
|
||||||
|
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||||
|
import hep.dataforge.vision.html.VisionTagConsumer
|
||||||
|
import hep.dataforge.vision.html.fragment
|
||||||
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.features.CORS
|
import io.ktor.features.CORS
|
||||||
@ -56,15 +59,25 @@ public class VisionServer internal constructor(
|
|||||||
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
|
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
|
||||||
|
|
||||||
public fun header(block: TagConsumer<*>.() -> Unit) {
|
public fun header(block: TagConsumer<*>.() -> Unit) {
|
||||||
globalHeaders.add(HtmlFragment(block))
|
globalHeaders.add(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun HTML.buildPage(
|
private fun HTML.buildPage(
|
||||||
visionFragment: HtmlVisionFragment<Vision>,
|
visionFragment: HtmlVisionFragment,
|
||||||
title: String,
|
title: String,
|
||||||
headers: List<HtmlFragment>,
|
headers: List<HtmlFragment>,
|
||||||
): Map<Name, Vision> {
|
): Map<Name, Vision> {
|
||||||
lateinit var visionMap: Map<Name, Vision>
|
val visionMap = HashMap<Name, Vision>()
|
||||||
|
|
||||||
|
val consumer = object : VisionTagConsumer<Any?>(consumer) {
|
||||||
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||||
|
visionMap[name] = vision
|
||||||
|
|
||||||
|
// Toggle updates
|
||||||
|
attributes[OUTPUT_FETCH_VISION_ATTRIBUTE] = "true"
|
||||||
|
attributes[OUTPUT_FETCH_UPDATE_ATTRIBUTE] = "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
head {
|
head {
|
||||||
meta {
|
meta {
|
||||||
@ -77,7 +90,7 @@ public class VisionServer internal constructor(
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
//Load the fragment and remember all loaded visions
|
//Load the fragment and remember all loaded visions
|
||||||
visionMap = visionFragment(visionFragment)
|
visionFragment(consumer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return visionMap
|
return visionMap
|
||||||
@ -146,11 +159,11 @@ public class VisionServer internal constructor(
|
|||||||
* Serve a page, potentially containing any number of visions at a given [route] with given [headers].
|
* Serve a page, potentially containing any number of visions at a given [route] with given [headers].
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public fun servePage(
|
public fun page(
|
||||||
visionFragment: HtmlVisionFragment<Vision>,
|
|
||||||
route: String = DEFAULT_PAGE,
|
route: String = DEFAULT_PAGE,
|
||||||
title: String = "VisionForge server page '$route'",
|
title: String = "VisionForge server page '$route'",
|
||||||
headers: List<HtmlFragment> = emptyList(),
|
headers: List<HtmlFragment> = emptyList(),
|
||||||
|
visionFragment: HtmlVisionFragment,
|
||||||
) {
|
) {
|
||||||
val visions = HashMap<Name, Vision>()
|
val visions = HashMap<Name, Vision>()
|
||||||
|
|
||||||
@ -185,19 +198,6 @@ public class VisionServer internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A shortcut method to easily create Complete pages filled with visions
|
|
||||||
*/
|
|
||||||
public fun page(
|
|
||||||
route: String = DEFAULT_PAGE,
|
|
||||||
title: String = "VisionForge server page '$route'",
|
|
||||||
headers: List<HtmlFragment> = emptyList(),
|
|
||||||
content: OutputTagConsumer<*, Vision>.() -> Unit,
|
|
||||||
) {
|
|
||||||
servePage(buildVisionFragment(content), route, title, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val DEFAULT_PAGE: String = "/"
|
public const val DEFAULT_PAGE: String = "/"
|
||||||
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
|
||||||
|
Loading…
Reference in New Issue
Block a user