[WIP] Moving renderers to a common API
This commit is contained in:
parent
e7f0e1e4fc
commit
3caa22f8bf
25
jupyter/visionforge-jupyter-base/build.gradle.kts
Normal file
25
jupyter/visionforge-jupyter-base/build.gradle.kts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
plugins {
|
||||||
|
id("ru.mipt.npm.gradle.mpp")
|
||||||
|
id("org.jetbrains.kotlin.jupyter.api")
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Common visionforge jupyter module"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain{
|
||||||
|
dependencies{
|
||||||
|
api(projects.visionforge.visionforgeCore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":visionforge-server"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readme {
|
||||||
|
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package space.kscience.visionforge.jupyter
|
||||||
|
|
||||||
|
import io.ktor.server.cio.CIO
|
||||||
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
|
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||||
|
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
||||||
|
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.ContextAware
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.int
|
||||||
|
import space.kscience.dataforge.meta.string
|
||||||
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
import space.kscience.visionforge.Vision
|
||||||
|
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||||
|
import space.kscience.visionforge.html.Page
|
||||||
|
import space.kscience.visionforge.html.embedAndRenderVisionFragment
|
||||||
|
import space.kscience.visionforge.three.server.VisionServer
|
||||||
|
import space.kscience.visionforge.three.server.visionServer
|
||||||
|
import space.kscience.visionforge.visionManager
|
||||||
|
|
||||||
|
private const val DEFAULT_VISIONFORGE_PORT = 88898
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public abstract class JupyterPluginBase(
|
||||||
|
override val context: Context,
|
||||||
|
) : JupyterIntegration(), ContextAware {
|
||||||
|
|
||||||
|
private var counter = 0
|
||||||
|
|
||||||
|
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||||
|
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||||
|
}.finalize()
|
||||||
|
|
||||||
|
private var engine: ApplicationEngine? = null
|
||||||
|
private var server: VisionServer? = null
|
||||||
|
|
||||||
|
override fun Builder.onLoaded() {
|
||||||
|
|
||||||
|
onLoaded {
|
||||||
|
val host = context.properties["visionforge.host"].string ?: "localhost"
|
||||||
|
val port = context.properties["visionforge.port"].int ?: DEFAULT_VISIONFORGE_PORT
|
||||||
|
engine = context.embeddedServer(CIO, port, host) {
|
||||||
|
server = visionServer(context)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
onShutdown {
|
||||||
|
engine?.stop(1000, 1000)
|
||||||
|
engine = null
|
||||||
|
server = null
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
js("three") {
|
||||||
|
classPath("js/gdml-jupyter.js")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import(
|
||||||
|
"kotlinx.html.*",
|
||||||
|
"space.kscience.visionforge.html.Page",
|
||||||
|
"space.kscience.visionforge.html.page",
|
||||||
|
)
|
||||||
|
|
||||||
|
render<Vision> { vision ->
|
||||||
|
val server = this@JupyterPluginBase.server
|
||||||
|
if (server == null) {
|
||||||
|
HTML(produceHtmlVisionString { vision(vision) })
|
||||||
|
} else {
|
||||||
|
val route = "route.${counter++}"
|
||||||
|
HTML(server.createHtmlAndServe(route,route, emptyList()){
|
||||||
|
vision(vision)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render<Page> { page ->
|
||||||
|
//HTML(page.render(createHTML()), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ internal class GdmlForJupyter : JupyterIntegration() {
|
|||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|
||||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment)
|
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||||
}.finalize()
|
}.finalize()
|
||||||
|
|
||||||
override fun Builder.onLoaded() {
|
override fun Builder.onLoaded() {
|
@ -22,6 +22,19 @@ rootProject.name = "visionforge"
|
|||||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
enableFeaturePreview("VERSION_CATALOGS")
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
maven("https://repo.kotlin.link")
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
versionCatalogs {
|
||||||
|
create("npmlibs") {
|
||||||
|
from("ru.mipt.npm:version-catalog:0.10.7")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
include(
|
include(
|
||||||
// ":ui",
|
// ":ui",
|
||||||
":ui:react",
|
":ui:react",
|
||||||
@ -46,5 +59,6 @@ include(
|
|||||||
":demo:jupyter-playground",
|
":demo:jupyter-playground",
|
||||||
":demo:plotly-fx",
|
":demo:plotly-fx",
|
||||||
":demo:js-playground",
|
":demo:js-playground",
|
||||||
":jupyter:visionforge-gdml-jupyter"
|
":jupyter:visionforge-jupyter-base",
|
||||||
|
":jupyter:visionforge-jupyter-gdml"
|
||||||
)
|
)
|
||||||
|
@ -7,9 +7,11 @@ import space.kscience.dataforge.names.Name
|
|||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.VisionManager
|
import space.kscience.visionforge.VisionManager
|
||||||
|
|
||||||
|
|
||||||
public fun TagConsumer<*>.embedVisionFragment(
|
public fun TagConsumer<*>.embedVisionFragment(
|
||||||
manager: VisionManager,
|
manager: VisionManager,
|
||||||
|
embedData: Boolean = true,
|
||||||
|
fetchData: String? = null,
|
||||||
|
fetchUpdates: String? = null,
|
||||||
idPrefix: String? = null,
|
idPrefix: String? = null,
|
||||||
fragment: HtmlVisionFragment,
|
fragment: HtmlVisionFragment,
|
||||||
): Map<Name, Vision> {
|
): Map<Name, Vision> {
|
||||||
@ -17,6 +19,17 @@ public fun TagConsumer<*>.embedVisionFragment(
|
|||||||
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) {
|
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) {
|
||||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||||
visionMap[name] = vision
|
visionMap[name] = vision
|
||||||
|
// Toggle update mode
|
||||||
|
|
||||||
|
fetchUpdates?.let {
|
||||||
|
attributes[OUTPUT_CONNECT_ATTRIBUTE] = it
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData?.let {
|
||||||
|
attributes[OUTPUT_FETCH_ATTRIBUTE] = it
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedData) {
|
||||||
script {
|
script {
|
||||||
type = "text/json"
|
type = "text/json"
|
||||||
attributes["class"] = OUTPUT_DATA_CLASS
|
attributes["class"] = OUTPUT_DATA_CLASS
|
||||||
@ -26,25 +39,36 @@ public fun TagConsumer<*>.embedVisionFragment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fragment(consumer)
|
fragment(consumer)
|
||||||
return visionMap
|
return visionMap
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun FlowContent.embedVisionFragment(
|
public fun FlowContent.embedVisionFragment(
|
||||||
manager: VisionManager,
|
manager: VisionManager,
|
||||||
|
embedData: Boolean = true,
|
||||||
|
fetchDataUrl: String? = null,
|
||||||
|
fetchUpdatesUrl: String? = null,
|
||||||
idPrefix: String? = null,
|
idPrefix: String? = null,
|
||||||
fragment: HtmlVisionFragment,
|
fragment: HtmlVisionFragment,
|
||||||
): Map<Name, Vision> = consumer.embedVisionFragment(manager, idPrefix, fragment)
|
): Map<Name, Vision> = consumer.embedVisionFragment(manager, embedData, fetchDataUrl, fetchUpdatesUrl, idPrefix, fragment)
|
||||||
|
|
||||||
|
|
||||||
internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
|
internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
|
||||||
|
|
||||||
@DFExperimental
|
public fun TagConsumer<*>.embedAndRenderVisionFragment(
|
||||||
public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment) {
|
manager: VisionManager,
|
||||||
|
id: Any,
|
||||||
|
embedData: Boolean = true,
|
||||||
|
fetchData: String? = null,
|
||||||
|
fetchUpdates: String? = null,
|
||||||
|
idPrefix: String? = null,
|
||||||
|
fragment: HtmlVisionFragment,
|
||||||
|
) {
|
||||||
div {
|
div {
|
||||||
div {
|
div {
|
||||||
this.id = id.toString()
|
this.id = id.toString()
|
||||||
embedVisionFragment(manager, fragment = fragment)
|
embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment)
|
||||||
}
|
}
|
||||||
script {
|
script {
|
||||||
type = "text/javascript"
|
type = "text/javascript"
|
||||||
|
@ -126,6 +126,8 @@ public abstract class VisionTagConsumer<R>(
|
|||||||
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 = "."
|
||||||
|
|
||||||
|
public const val AUTO_DATA_ATTRIBUTE: String = "@auto"
|
||||||
|
|
||||||
public const val DEFAULT_VISION_NAME: String = "vision"
|
public const val DEFAULT_VISION_NAME: String = "vision"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,6 @@ import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_
|
|||||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
|
|
||||||
public class VisionClient : AbstractPlugin() {
|
public class VisionClient : AbstractPlugin() {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
@ -27,7 +26,7 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
//private val visionMap = HashMap<Element, Vision>()
|
//private val visionMap = HashMap<Element, Vision>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Up-going tree traversal in search for endpoint attribute
|
* Up-going tree traversal in search for endpoint attribute. If element is null, return window URL
|
||||||
*/
|
*/
|
||||||
private fun resolveEndpoint(element: Element?): String {
|
private fun resolveEndpoint(element: Element?): String {
|
||||||
if (element == null) return window.location.href
|
if (element == null) return window.location.href
|
||||||
@ -57,14 +56,13 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
|
|
||||||
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
|
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
|
private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
|
||||||
if (vision != null) {
|
if (vision != null) {
|
||||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||||
renderer.render(element, vision, outputMeta)
|
renderer.render(element, vision, outputMeta)
|
||||||
|
|
||||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||||
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
||||||
val endpoint = resolveEndpoint(element)
|
val endpoint = resolveEndpoint(element)
|
||||||
logger.info { "Vision server is resolved to $endpoint" }
|
logger.info { "Vision server is resolved to $endpoint" }
|
||||||
URL(endpoint).apply {
|
URL(endpoint).apply {
|
||||||
@ -154,7 +152,7 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> {
|
element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> {
|
||||||
val attr = element.attributes[OUTPUT_FETCH_ATTRIBUTE]!!
|
val attr = element.attributes[OUTPUT_FETCH_ATTRIBUTE]!!
|
||||||
|
|
||||||
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
val fetchUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
||||||
val endpoint = resolveEndpoint(element)
|
val endpoint = resolveEndpoint(element)
|
||||||
logger.info { "Vision server is resolved to $endpoint" }
|
logger.info { "Vision server is resolved to $endpoint" }
|
||||||
URL(endpoint).apply {
|
URL(endpoint).apply {
|
||||||
|
@ -34,10 +34,7 @@ import space.kscience.visionforge.Vision
|
|||||||
import space.kscience.visionforge.VisionChange
|
import space.kscience.visionforge.VisionChange
|
||||||
import space.kscience.visionforge.VisionManager
|
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.*
|
||||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer
|
|
||||||
import space.kscience.visionforge.html.fragment
|
|
||||||
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
|
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@ -71,36 +68,12 @@ public class VisionServer internal constructor(
|
|||||||
globalHeaders.add(block)
|
globalHeaders.add(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun HTML.buildPage(
|
private fun HTML.visionPage(
|
||||||
visionFragment: HtmlVisionFragment,
|
|
||||||
title: String,
|
title: String,
|
||||||
headers: List<HtmlFragment>,
|
headers: List<HtmlFragment>,
|
||||||
|
visionFragment: HtmlVisionFragment,
|
||||||
): Map<Name, Vision> {
|
): Map<Name, Vision> {
|
||||||
val visionMap = HashMap<Name, Vision>()
|
var visionMap: Map<Name,Vision>? = null
|
||||||
|
|
||||||
val consumer = object : VisionTagConsumer<Any?>(consumer, visionManager) {
|
|
||||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
|
||||||
visionMap[name] = vision
|
|
||||||
// Toggle update mode
|
|
||||||
if (dataConnect) {
|
|
||||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataFetch) {
|
|
||||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataEmbed) {
|
|
||||||
script {
|
|
||||||
type = "text/json"
|
|
||||||
attributes["class"] = OUTPUT_DATA_CLASS
|
|
||||||
unsafe {
|
|
||||||
+"\n${visionManager.encodeToString(vision)}\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
head {
|
head {
|
||||||
meta {
|
meta {
|
||||||
@ -113,16 +86,22 @@ public class VisionServer internal constructor(
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
//Load the fragment and remember all loaded visions
|
//Load the fragment and remember all loaded visions
|
||||||
visionFragment(consumer)
|
visionMap = embedVisionFragment(
|
||||||
|
manager = visionManager,
|
||||||
|
embedData = true,
|
||||||
|
fetchDataUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE,
|
||||||
|
fetchUpdatesUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE,
|
||||||
|
fragment = visionFragment
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return visionMap
|
return visionMap!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server a map of visions without providing explicit html page for them
|
* Server a map of visions without providing explicit html page for them
|
||||||
*/
|
*/
|
||||||
@OptIn(DFExperimental::class, ExperimentalTime::class)
|
@OptIn(DFExperimental::class)
|
||||||
public fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
|
public fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
|
||||||
application.log.info("Serving visions $visions at $route")
|
application.log.info("Serving visions $visions at $route")
|
||||||
|
|
||||||
@ -175,6 +154,22 @@ public class VisionServer internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a static html page and serve visions produced in the process
|
||||||
|
*/
|
||||||
|
@DFExperimental
|
||||||
|
public fun createHtmlAndServe(route: String, title: String, headers: List<HtmlFragment>, visionFragment: HtmlVisionFragment): String{
|
||||||
|
val htmlString = createHTML().apply {
|
||||||
|
html {
|
||||||
|
visionPage(title, headers, visionFragment).also {
|
||||||
|
serveVisions(route, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.finalize()
|
||||||
|
|
||||||
|
return htmlString
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serv visions in a given [route] without providing a page template
|
* Serv visions in a given [route] without providing a page template
|
||||||
*/
|
*/
|
||||||
@ -203,7 +198,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(buildPage(visionFragment, title, headers))
|
visions.putAll(visionPage(title, headers, visionFragment))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@ -219,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(buildPage(visionFragment, title, headers))
|
visions.putAll(visionPage(title, headers, visionFragment))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//Use cached html
|
//Use cached html
|
||||||
|
Loading…
Reference in New Issue
Block a user