Jupyter renderers are working for lab and Idea

This commit is contained in:
Alexander Nozik 2023-07-22 15:39:43 +03:00
parent a49a4f1a7f
commit b3f68d879f
17 changed files with 226 additions and 187 deletions

View File

@ -13,7 +13,7 @@ val fxVersion by extra("11")
allprojects {
group = "space.kscience"
version = "0.3.0-dev-11"
version = "0.3.0-dev-12"
}
subprojects {

View File

@ -51,7 +51,7 @@ kotlin {
implementation(projects.visionforgeMarkdown)
implementation(projects.visionforgeTables)
implementation(projects.cernRootLoader)
implementation(projects.visionforgeJupyter.visionforgeJupyterCommon)
api(projects.visionforgeJupyter.visionforgeJupyterCommon)
}
}

View File

@ -2,77 +2,31 @@
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2023-07-20T06:12:13.305060400Z",
"start_time": "2023-07-20T06:12:13.011273800Z"
}
"tags": []
},
"outputs": [],
"source": [
"@file:Repository(\"*mavenLocal\")\n",
"@file:Repository(\"https://repo.kotlin.link\")\n",
"@file:Repository(\"https://maven.pkg.jetbrains.space/spc/p/sci/dev\")\n",
"@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-11\")"
"@file:DependsOn(\"space.kscience:visionforge-jupyter-common-jvm:0.3.0-dev-12\")\n",
"//import space.kscience.visionforge.jupyter.JupyterCommonIntegration\n",
"//\n",
"//val integration = JupyterCommonIntegration()\n",
"//USE(integration.getDefinitions(notebook).first())"
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
"tags": []
},
"ExecuteTime": {
"end_time": "2023-07-20T06:12:19.603077Z",
"start_time": "2023-07-20T06:12:19.419504300Z"
}
},
"outputs": [
{
"data": {
"text/html": "<p style=\"color: blue;\">Starting VisionForge server on http://localhost:7777</p>\n"
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"vf.startServer()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
"ExecuteTime": {
"end_time": "2023-07-20T06:12:21.490069100Z",
"start_time": "2023-07-20T06:12:20.694188600Z"
}
},
"outputs": [
{
"data": {
"text/html": "<div id=\"fragment[1645474043/2315898832]\">\n <h1>AAA</h1>\n <div id=\"output[vision[302766000]]\" class=\"visionforge-output\" data-output-name=\"vision[302766000]\" data-output-connect=\"ws://localhost:7777/content-0/ws\">\n <script type=\"text/json\" class=\"visionforge-output-data\">\n{\n \"type\": \"group.solid\",\n \"children\": {\n \"@ambientLight\": {\n \"type\": \"solid.light.ambient\"\n },\n \"@static[1326263213]\": {\n \"type\": \"solid.box\",\n \"xSize\": 100.0,\n \"ySize\": 100.0,\n \"zSize\": 200.0\n },\n \"@static[1813044036]\": {\n \"type\": \"solid.sphere\",\n \"properties\": {\n \"position\": {\n \"x\": 300\n }\n },\n \"radius\": 100.0\n }\n }\n}\n</script>\n </div>\n <div id=\"output[vision[1326029936]]\" class=\"visionforge-output\" data-output-name=\"vision[1326029936]\" data-output-connect=\"ws://localhost:7777/content-0/ws\">\n <script type=\"text/json\" class=\"visionforge-output-data\">\n{\n \"type\": \"vision.plotly\",\n \"meta\": {\n \"data\": {\n \"type\": \"scatter\",\n \"x\": [\n 1,\n 2,\n 3,\n 1\n ],\n \"y\": [\n 1,\n 2,\n 3,\n 4\n ],\n \"@index\": \"0\"\n }\n }\n}\n</script>\n </div>\n</div>\n<script type=\"text/javascript\">VisionForge.renderAllVisionsById(\"fragment[1645474043/2315898832]\");</script>\n"
},
"execution_count": 4,
"metadata": {
"text/html": {
"isolated": true
}
},
"output_type": "execute_result"
}
],
"source": [
"vf.page {\n",
"vf.fragment {\n",
" h1 { +\"AAA\" }\n",
" vision {\n",
" solid {\n",
@ -100,24 +54,27 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"tags": []
},
"outputs": [],
"source": [
"vf.stopServer()"
"Plotly.plot { \n",
" scatter{\n",
" x(1,2,3)\n",
" y(1,2,3)\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [],
"metadata": {
"collapsed": false
}
"source": []
}
],
"metadata": {

View File

@ -2,7 +2,6 @@ package space.kscience.visionforge.react
import kotlinx.css.*
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import react.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
@ -29,7 +28,7 @@ public val ThreeCanvasComponent: FC<ThreeCanvasProps> = fc("ThreeCanvasComponent
useEffect(props.solid, props.options, elementRef) {
if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
val element = elementRef.current ?: error("Canvas element not found")
canvas = ThreeCanvas(three, element, props.options ?: Canvas3DOptions())
}
}

View File

@ -4,19 +4,28 @@ import kotlinx.html.FlowContent
import kotlinx.html.TagConsumer
import kotlinx.html.stream.createHTML
public typealias HtmlFragment = TagConsumer<*>.() -> Unit
public fun HtmlFragment.renderToString(): String = createHTML().apply(this).finalize()
public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
fragment()
/**
* A standalone HTML fragment
*/
public fun interface HtmlFragment {
public fun TagConsumer<*>.append()
}
public fun FlowContent.fragment(fragment: HtmlFragment) {
fragment(consumer)
}
/**
* Convenience method to append fragment to the given [consumer]
*/
public fun HtmlFragment.appendTo(consumer: TagConsumer<*>): Unit = consumer.append()
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = {
this@plus()
other()
/**
* Create a string from this [HtmlFragment]
*/
public fun HtmlFragment.renderToString(): String = createHTML().apply { append() }.finalize()
public fun TagConsumer<*>.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(this)
public fun FlowContent.appendFragment(fragment: HtmlFragment): Unit = fragment.appendTo(consumer)
public operator fun HtmlFragment.plus(other: HtmlFragment): HtmlFragment = HtmlFragment {
this@plus.appendTo(this)
other.appendTo(this)
}

View File

@ -2,18 +2,17 @@ package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
@DFExperimental
public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content
public fun interface HtmlVisionFragment{
public fun VisionTagConsumer<*>.append()
}
public fun HtmlVisionFragment.appendTo(consumer: VisionTagConsumer<*>): Unit = consumer.append()
/**
* Render a fragment in the given consumer and return a map of extracted visions
@ -84,7 +83,7 @@ public fun TagConsumer<*>.visionFragment(
}
}
fragment(consumer)
fragment.appendTo(consumer)
}
public fun FlowContent.visionFragment(

View File

@ -17,7 +17,7 @@ public data class VisionPage(
/**
* Use a script with given [src] as a global header for all pages.
*/
public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = {
public fun scriptHeader(src: String, block: SCRIPT.() -> Unit = {}): HtmlFragment = HtmlFragment{
script {
type = "text/javascript"
this.src = src
@ -28,7 +28,7 @@ public data class VisionPage(
/**
* Use css with the given stylesheet link as a global header for all pages.
*/
public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = {
public fun styleSheetHeader(href: String, block: LINK.() -> Unit = {}): HtmlFragment = HtmlFragment{
link {
rel = "stylesheet"
this.href = href
@ -36,7 +36,7 @@ public data class VisionPage(
}
}
public fun title(title:String): HtmlFragment = {
public fun title(title:String): HtmlFragment = HtmlFragment{
title(title)
}
}

View File

@ -24,7 +24,7 @@ fun FlowContent.renderVisionFragment(
renderer(name, vision, outputMeta)
}
}
fragment(consumer)
fragment.appendTo(consumer)
return visionMap
}
@ -35,7 +35,7 @@ private fun VisionOutput.base(block: VisionGroup.() -> Unit) = context.visionMan
@DFExperimental
class HtmlTagTest {
val fragment: HtmlVisionFragment = {
val fragment = HtmlVisionFragment{
div {
h1 { +"Head" }
vision("ddd") {

View File

@ -286,8 +286,8 @@ public fun VisionClient.renderAllVisionsIn(element: Element) {
/**
* Render all visions in an element with a given [id]
*/
public fun VisionClient.renderAllVisionsById(id: String): Unit = whenDocumentLoaded {
val element = getElementById(id)
public fun VisionClient.renderAllVisionsById(document: Document, id: String): Unit {
val element = document.getElementById(id)
if (element != null) {
renderAllVisionsIn(element)
} else {

View File

@ -34,10 +34,10 @@ public interface HtmlVisionContext : ContextAware {
public typealias HtmlVisionContextFragment = context(HtmlVisionContext) TagConsumer<*>.() -> Unit
context(HtmlVisionContext)
public fun HtmlVisionFragment(
content: TagConsumer<*>.() -> Unit,
): HtmlVisionFragment = content
//context(HtmlVisionContext)
//public fun HtmlVisionFragment(
// content: TagConsumer<*>.() -> Unit,
//): HtmlVisionFragment = HtmlVisionFragment { }
context(HtmlVisionContext)
private fun <T> TagConsumer<T>.vision(

View File

@ -91,14 +91,14 @@ internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String,
*/
internal fun fileScriptHeader(
path: Path,
): HtmlFragment = {
): HtmlFragment = HtmlFragment{
script {
type = "text/javascript"
src = path.toString()
}
}
internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = {
internal fun embedScriptHeader(resource: String, classLoader: ClassLoader): HtmlFragment = HtmlFragment{
script {
type = "text/javascript"
unsafe {
@ -113,7 +113,7 @@ internal fun fileCssHeader(
cssPath: Path,
resource: String,
classLoader: ClassLoader,
): HtmlFragment = {
): HtmlFragment = HtmlFragment{
val relativePath = checkOrStoreFile(basePath, cssPath, resource, classLoader)
link {
rel = "stylesheet"

View File

@ -78,7 +78,7 @@ public fun VisionPage.makeFile(
charset = "utf-8"
}
actualHeaders.values.forEach {
fragment(it)
appendFragment(it)
}
}
body {

View File

@ -1,6 +1,7 @@
package space.kscience.visionforge.jupyter
import kotlinx.browser.window
import org.w3c.dom.Document
import org.w3c.dom.Element
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context
@ -20,8 +21,8 @@ public class VFNotebookClient : AbstractPlugin() {
client.renderAllVisionsIn(element)
}
public fun renderAllVisionsById(id: String) {
client.renderAllVisionsById(id)
public fun renderAllVisionsById(document: Document, id: String) {
client.renderAllVisionsById(document, id)
}
public fun renderAllVisions() {

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
@ -20,24 +21,33 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.visionFragment
import space.kscience.visionforge.html.*
import space.kscience.visionforge.server.VisionRoute
import space.kscience.visionforge.server.serveVisionData
import kotlin.coroutines.CoroutineContext
import kotlin.random.Random
import kotlin.random.nextUInt
internal fun TagConsumer<*>.renderScriptForId(id: String) {
script {
type = "text/javascript"
unsafe { +"VisionForge.renderAllVisionsById(\"$id\");" }
}
@Suppress("FunctionName")
internal inline fun HTML(isolated: Boolean = false, block: TagConsumer<*>.() -> Unit): MimeTypedResult =
HTML(createHTML().apply(block).finalize(), isolated)
internal fun KotlinKernelHost.displayHtml(block: TagConsumer<*>.() -> Unit) {
display(HTML(false, block), null)
}
public enum class VisionForgeCompatibility {
JUPYTER,
JUPYTER_LAB,
DATALORE,
IDEA
}
/**
* A handler class that includes a server and common utilities
*/
@Suppress("ExtractKtorModule")
public class VisionForge(
public val visionManager: VisionManager,
meta: Meta = Meta.EMPTY,
@ -51,31 +61,30 @@ public class VisionForge(
private var engine: ApplicationEngine? = null
public var isolateFragments: Boolean = false
public var notebookMode: VisionForgeCompatibility = VisionForgeCompatibility.IDEA
override val coroutineContext: CoroutineContext get() = context.coroutineContext
public fun legacyMode() {
isolateFragments = true
}
public fun isServerRunning(): Boolean = engine != null
public fun html(block: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(createHTML().apply(block).finalize())
public fun getProperty(name: String): TypedMeta<*>? = configuration[name] ?: context.properties[name]
public fun startServer(
internal fun startServer(
kernel: KotlinKernelHost,
host: String = getProperty("visionforge.host").string ?: "localhost",
port: Int = getProperty("visionforge.port").int ?: VisionRoute.DEFAULT_PORT,
): MimeTypedResult = html {
) {
if (engine != null) {
kernel.displayHtml {
p {
style = "color: red;"
+"Stopping current VisionForge server"
}
}
}
//val connector: EngineConnectorConfig = EngineConnectorConfig(host, port)
engine?.stop(1000, 2000)
@ -83,23 +92,52 @@ public class VisionForge(
install(WebSockets)
}.start(false)
kernel.displayHtml {
p {
style = "color: blue;"
+"Starting VisionForge server on http://$host:$port"
+"Starting VisionForge server on port $port"
}
}
}
public fun stopServer() {
internal fun stopServer(kernel: KotlinKernelHost) {
engine?.apply {
logger.info { "Stopping VisionForge server" }
stop(1000, 2000)
engine = null
}
kernel.displayHtml {
p {
style = "color: red;"
+"VisionForge server stopped"
}
}
}
private fun produceHtmlString(
internal fun TagConsumer<*>.renderScriptForId(id: String, iframeIsolation: Boolean = false) {
script {
type = "text/javascript"
if (iframeIsolation) {
//language=JavaScript
unsafe { +"parent.VisionForge.renderAllVisionsById(document, \"$id\");" }
} else {
//language=JavaScript
unsafe { +"VisionForge.renderAllVisionsById(document, \"$id\");" }
}
}
}
public fun produceHtml(
isolated: Boolean? = null,
fragment: HtmlVisionFragment,
): String = createHTML().apply {
): MimeTypedResult {
val iframeIsolation = isolated
?: (notebookMode == VisionForgeCompatibility.JUPYTER || notebookMode == VisionForgeCompatibility.DATALORE)
return HTML(
iframeIsolation
) {
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
@ -134,15 +172,11 @@ public class VisionForge(
visionFragment(visionManager, fragment = fragment)
}
}
renderScriptForId(id)
}.finalize()
public fun produceHtml(isolated: Boolean? = null, fragment: HtmlVisionFragment): MimeTypedResult =
HTML(produceHtmlString(fragment), isolated ?: isolateFragments)
public fun fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(fragment = body)
public fun page(body: HtmlVisionFragment): MimeTypedResult = produceHtml(true, body)
renderScriptForId(id, iframeIsolation = iframeIsolation)
}
}
public fun form(builder: FORM.() -> Unit): HtmlFormFragment =
HtmlFormFragment("form[${counter++}]", builder = builder)
}

View File

@ -1,8 +1,7 @@
package space.kscience.visionforge.jupyter
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import org.jetbrains.kotlinx.jupyter.api.declare
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import space.kscience.dataforge.context.Context
@ -31,11 +30,12 @@ public abstract class VisionForgeIntegration(
onLoaded {
declare("VisionForge" to handler, "vf" to handler)
handler.startServer(this)
}
onShutdown {
handler.stopServer()
handler.stopServer(this)
}
import(
@ -43,14 +43,14 @@ public abstract class VisionForgeIntegration(
"space.kscience.visionforge.html.*",
"space.kscience.visionforge.jupyter.*"
)
render<HtmlFragment> { fragment ->
handler.produceHtml(fragment = fragment)
}
render<HtmlVisionFragment> { fragment ->
handler.produceHtml(fragment = fragment)
}
//
// render<HtmlFragment> { fragment ->
// HTML(fragment.renderToString())
// }
//
// render<HtmlVisionFragment> { fragment ->
// handler.produceHtml(fragment = fragment)
// }
render<Vision> { vision ->
handler.produceHtml {
@ -59,13 +59,13 @@ public abstract class VisionForgeIntegration(
}
render<VisionPage> { page ->
HTML(createHTML().apply {
HTML(true) {
head {
meta {
charset = "utf-8"
}
page.pageHeaders.values.forEach {
fragment(it)
appendFragment(it)
}
}
body {
@ -74,9 +74,11 @@ public abstract class VisionForgeIntegration(
this.id = id
visionFragment(visionManager, fragment = page.content)
}
renderScriptForId(id)
with(handler) {
renderScriptForId(id, true)
}
}
}
}.finalize(), true)
}
render<HtmlFormFragment> { fragment ->
@ -87,7 +89,7 @@ public abstract class VisionForgeIntegration(
+"The server is not running. Forms are not interactive. Start server with `VisionForge.startServer()."
}
}
fragment(fragment.formBody)
appendFragment(fragment.formBody)
vision(fragment.vision)
}
}
@ -96,10 +98,25 @@ public abstract class VisionForgeIntegration(
}
}
/**
* Create a fragment without a head to be embedded in the page
*/
@Suppress("UnusedReceiverParameter")
public fun VisionForge.html(body: TagConsumer<*>.() -> Unit): MimeTypedResult = HTML(false, body)
/**
* Create a fragment without a head to be embedded in the page
*/
public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeTypedResult = produceHtml(false, body)
/**
* Create a standalone page in the notebook
*/
public fun VisionForge.page(
pageHeaders: Map<String, HtmlFragment> = emptyMap(),
content: HtmlVisionFragment
): VisionPage = VisionPage(visionManager, pageHeaders, content)
body: VisionTagConsumer<*>.() -> Unit,
): VisionPage = VisionPage(visionManager, pageHeaders, body)

View File

@ -6,8 +6,12 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.gdml.Gdml
import space.kscience.plotly.Plot
import space.kscience.plotly.PlotlyPage
import space.kscience.plotly.StaticPlotlyRenderer
import space.kscience.tables.*
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.HtmlFragment
import space.kscience.visionforge.html.VisionPage
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.asVision
@ -23,7 +27,7 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan
override fun Builder.afterLoaded() {
resources {
js("three") {
js("visionforge-common") {
classPath("js/visionforge-jupyter-common.js")
}
}
@ -55,6 +59,24 @@ public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionMan
vision { plot.asVision() }
}
}
render<PlotlyPage> { plotlyPage ->
val headers = plotlyPage.headers.associate { plotlyFragment ->
plotlyFragment.hashCode().toString(16) to HtmlFragment {
plotlyFragment.visit(this)
}
}
VisionPage(visionManager, headers) {
div{
p { +"Plotly page renderer is not recommended in VisionForge, use `vf.page{}`" }
}
div {
plotlyPage.fragment.render.invoke(this, StaticPlotlyRenderer)
}
}
}
}
public companion object {

View File

@ -3,6 +3,7 @@ package space.kscience.visionforge.three
import kotlinx.html.stream.createHTML
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.html.VisionPage
import space.kscience.visionforge.html.appendTo
import space.kscience.visionforge.html.importScriptHeader
import kotlin.test.Test
@ -15,7 +16,7 @@ class TestServerExtensions {
VisionPage.importScriptHeader(
"js/visionforge-three.js",
ResourceLocation.SYSTEM
).invoke(this)
).appendTo(this)
}.finalize()