0.2.0 #71
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 fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment)
|
||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||
}.finalize()
|
||||
|
||||
override fun Builder.onLoaded() {
|
@ -22,6 +22,19 @@ rootProject.name = "visionforge"
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
versionCatalogs {
|
||||
create("npmlibs") {
|
||||
from("ru.mipt.npm:version-catalog:0.10.7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include(
|
||||
// ":ui",
|
||||
":ui:react",
|
||||
@ -46,5 +59,6 @@ include(
|
||||
":demo:jupyter-playground",
|
||||
":demo:plotly-fx",
|
||||
":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.VisionManager
|
||||
|
||||
|
||||
public fun TagConsumer<*>.embedVisionFragment(
|
||||
manager: VisionManager,
|
||||
embedData: Boolean = true,
|
||||
fetchData: String? = null,
|
||||
fetchUpdates: String? = null,
|
||||
idPrefix: String? = null,
|
||||
fragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
@ -17,6 +19,17 @@ public fun TagConsumer<*>.embedVisionFragment(
|
||||
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) {
|
||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||
visionMap[name] = vision
|
||||
// Toggle update mode
|
||||
|
||||
fetchUpdates?.let {
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = it
|
||||
}
|
||||
|
||||
fetchData?.let {
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = it
|
||||
}
|
||||
|
||||
if (embedData) {
|
||||
script {
|
||||
type = "text/json"
|
||||
attributes["class"] = OUTPUT_DATA_CLASS
|
||||
@ -26,25 +39,36 @@ public fun TagConsumer<*>.embedVisionFragment(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment(consumer)
|
||||
return visionMap
|
||||
}
|
||||
|
||||
public fun FlowContent.embedVisionFragment(
|
||||
manager: VisionManager,
|
||||
embedData: Boolean = true,
|
||||
fetchDataUrl: String? = null,
|
||||
fetchUpdatesUrl: String? = null,
|
||||
idPrefix: String? = null,
|
||||
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"
|
||||
|
||||
@DFExperimental
|
||||
public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment) {
|
||||
public fun TagConsumer<*>.embedAndRenderVisionFragment(
|
||||
manager: VisionManager,
|
||||
id: Any,
|
||||
embedData: Boolean = true,
|
||||
fetchData: String? = null,
|
||||
fetchUpdates: String? = null,
|
||||
idPrefix: String? = null,
|
||||
fragment: HtmlVisionFragment,
|
||||
) {
|
||||
div {
|
||||
div {
|
||||
this.id = id.toString()
|
||||
embedVisionFragment(manager, fragment = fragment)
|
||||
embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment)
|
||||
}
|
||||
script {
|
||||
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 DEFAULT_ENDPOINT: String = "."
|
||||
|
||||
public const val AUTO_DATA_ATTRIBUTE: String = "@auto"
|
||||
|
||||
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 kotlin.reflect.KClass
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
public class VisionClient : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
@ -27,7 +26,7 @@ public class VisionClient : AbstractPlugin() {
|
||||
//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 {
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
|
||||
if (vision != null) {
|
||||
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
|
||||
renderer.render(element, vision, outputMeta)
|
||||
|
||||
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)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
@ -154,7 +152,7 @@ public class VisionClient : AbstractPlugin() {
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> {
|
||||
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)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
|
@ -34,10 +34,7 @@ import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionChange
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.flowChanges
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.VisionTagConsumer
|
||||
import space.kscience.visionforge.html.fragment
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
@ -71,36 +68,12 @@ public class VisionServer internal constructor(
|
||||
globalHeaders.add(block)
|
||||
}
|
||||
|
||||
private fun HTML.buildPage(
|
||||
visionFragment: HtmlVisionFragment,
|
||||
private fun HTML.visionPage(
|
||||
title: String,
|
||||
headers: List<HtmlFragment>,
|
||||
visionFragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
val visionMap = HashMap<Name, Vision>()
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var visionMap: Map<Name,Vision>? = null
|
||||
|
||||
head {
|
||||
meta {
|
||||
@ -113,16 +86,22 @@ public class VisionServer internal constructor(
|
||||
}
|
||||
body {
|
||||
//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
|
||||
*/
|
||||
@OptIn(DFExperimental::class, ExperimentalTime::class)
|
||||
@OptIn(DFExperimental::class)
|
||||
public fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = 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
|
||||
*/
|
||||
@ -203,7 +198,7 @@ public class VisionServer internal constructor(
|
||||
val cachedHtml: String? = if (cacheFragments) {
|
||||
//Create and cache page html and map of visions
|
||||
createHTML(true).html {
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
visions.putAll(visionPage(title, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
@ -219,7 +214,7 @@ public class VisionServer internal constructor(
|
||||
//re-create html and vision list on each call
|
||||
call.respondHtml {
|
||||
visions.clear()
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
visions.putAll(visionPage(title, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
//Use cached html
|
||||
|
Loading…
Reference in New Issue
Block a user