0.2.0 #71
@ -29,7 +29,10 @@ kotlin {
|
||||
jvm {
|
||||
withJava()
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
kotlinOptions{
|
||||
jvmTarget = "11"
|
||||
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy"
|
||||
}
|
||||
}
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
@ -51,33 +54,34 @@ kotlin {
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":visionforge-solid"))
|
||||
api(project(":visionforge-gdml"))
|
||||
api(project(":visionforge-plotly"))
|
||||
api(projects.visionforge.visionforgeMarkdown)
|
||||
api(projects.visionforge.cernRootLoader)
|
||||
implementation(projects.visionforgeSolid)
|
||||
implementation(projects.visionforgeGdml)
|
||||
implementation(projects.visionforgePlotly)
|
||||
implementation(projects.visionforgeMarkdown)
|
||||
implementation(projects.cernRootLoader)
|
||||
implementation(projects.jupyter.jupyterBase)
|
||||
}
|
||||
}
|
||||
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(project(":ui:ring"))
|
||||
api(project(":visionforge-threejs"))
|
||||
implementation(projects.ui.ring)
|
||||
implementation(projects.visionforgeThreejs)
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
api(project(":visionforge-server"))
|
||||
api("ch.qos.logback:logback-classic:1.2.3")
|
||||
api("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||
implementation(projects.visionforgeServer)
|
||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask> {
|
||||
val processJupyterApiResources by tasks.getting(org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask::class){
|
||||
libraryProducers = listOf("space.kscience.visionforge.examples.VisionForgePlayGroundForJupyter")
|
||||
}
|
||||
|
||||
tasks.findByName("shadowJar")?.dependsOn("processJupyterApiResources")
|
||||
tasks.findByName("shadowJar")?.dependsOn(processJupyterApiResources)
|
@ -1,41 +1,27 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
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.misc.DFExperimental
|
||||
import space.kscience.gdml.Gdml
|
||||
import space.kscience.plotly.Plot
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.embedAndRenderVisionFragment
|
||||
import space.kscience.visionforge.jupyter.JupyterPluginBase
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.plotly.asVision
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.visionManager
|
||||
|
||||
@DFExperimental
|
||||
public class VisionForgePlayGroundForJupyter : JupyterIntegration() {
|
||||
|
||||
private val context = Context("VisionForge") {
|
||||
internal class VisionForgePlayGroundForJupyter : JupyterPluginBase(
|
||||
Context("VisionForge") {
|
||||
plugin(Solids)
|
||||
plugin(PlotlyPlugin)
|
||||
}
|
||||
) {
|
||||
|
||||
private var counter = 0
|
||||
|
||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||
}.finalize()
|
||||
|
||||
override fun Builder.onLoaded() {
|
||||
|
||||
override fun Builder.afterLoaded() {
|
||||
resources {
|
||||
js("VisionForge"){
|
||||
js("VisionForge") {
|
||||
classPath("js/visionforge-playground.js")
|
||||
}
|
||||
}
|
||||
@ -44,41 +30,20 @@ public class VisionForgePlayGroundForJupyter : JupyterIntegration() {
|
||||
"space.kscience.gdml.*",
|
||||
"space.kscience.plotly.*",
|
||||
"space.kscience.plotly.models.*",
|
||||
"kotlinx.html.*",
|
||||
"space.kscience.visionforge.solid.*",
|
||||
"space.kscience.visionforge.html.Page",
|
||||
"space.kscience.visionforge.html.page"
|
||||
)
|
||||
|
||||
|
||||
render<Gdml> { gdmlModel ->
|
||||
val fragment = HtmlVisionFragment {
|
||||
handler.produceHtml {
|
||||
vision(gdmlModel.toVision())
|
||||
}
|
||||
HTML(produceHtmlVisionString(fragment))
|
||||
}
|
||||
|
||||
render<Vision> { vision ->
|
||||
val fragment = HtmlVisionFragment {
|
||||
vision(vision)
|
||||
}
|
||||
|
||||
HTML(produceHtmlVisionString(fragment))
|
||||
}
|
||||
|
||||
render<Plot> { plot ->
|
||||
val fragment = HtmlVisionFragment {
|
||||
handler.produceHtml {
|
||||
vision(plot.asVision())
|
||||
}
|
||||
|
||||
HTML(produceHtmlVisionString(fragment))
|
||||
}
|
||||
|
||||
render<space.kscience.plotly.PlotlyHtmlFragment> { fragment ->
|
||||
HTML(createHTML().apply(fragment.visit).finalize())
|
||||
}
|
||||
|
||||
render<Page> { page ->
|
||||
HTML(page.render(createHTML()), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.visionOfForm
|
||||
import space.kscience.visionforge.html.formFragment
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
import space.kscience.visionforge.three.server.close
|
||||
import space.kscience.visionforge.three.server.openInBrowser
|
||||
@ -18,7 +18,7 @@ fun main() {
|
||||
val server = visionManager.serve {
|
||||
useScript("js/visionforge-playground.js")
|
||||
page {
|
||||
val form = visionOfForm("form") {
|
||||
val form = formFragment("form") {
|
||||
label {
|
||||
htmlFor = "fname"
|
||||
+"First name:"
|
||||
|
@ -9,6 +9,7 @@ import space.kscience.visionforge.html.scriptHeader
|
||||
import space.kscience.visionforge.makeFile
|
||||
import space.kscience.visionforge.three.server.VisionServer
|
||||
import space.kscience.visionforge.three.server.useScript
|
||||
import space.kscience.visionforge.visionManager
|
||||
import java.awt.Desktop
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -25,7 +26,7 @@ public fun Context.makeVisionFile(
|
||||
show: Boolean = true,
|
||||
content: VisionTagConsumer<*>.() -> Unit
|
||||
): Unit {
|
||||
val actualPath = page(title, content = content).makeFile(path) { actualPath ->
|
||||
val actualPath = visionManager.page(title, content = content).makeFile(path) { actualPath ->
|
||||
mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
|
||||
}
|
||||
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
|
@ -9,12 +9,12 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain{
|
||||
dependencies{
|
||||
api(projects.visionforge.visionforgeCore)
|
||||
api(projects.visionforgeCore)
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-server"))
|
||||
api(projects.visionforgeServer)
|
||||
}
|
||||
}
|
||||
}
|
62
jupyter/jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt
Normal file
62
jupyter/jupyter-base/src/jvmMain/kotlin/JupyterPluginBase.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package space.kscience.visionforge.jupyter
|
||||
|
||||
import kotlinx.html.stream.createHTML
|
||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||
import org.jetbrains.kotlinx.jupyter.api.declare
|
||||
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.html.HtmlFormFragment
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.fragment
|
||||
|
||||
@DFExperimental
|
||||
public abstract class JupyterPluginBase(final override val context: Context) : JupyterIntegration(), ContextAware {
|
||||
|
||||
protected val handler: VisionForgeServerHandler = VisionForgeServerHandler(context)
|
||||
|
||||
protected abstract fun Builder.afterLoaded()
|
||||
|
||||
final override fun Builder.onLoaded() {
|
||||
|
||||
onLoaded {
|
||||
declare("visionForge" to handler)
|
||||
}
|
||||
|
||||
onShutdown {
|
||||
handler.stopServer()
|
||||
}
|
||||
|
||||
import(
|
||||
"kotlinx.html.*",
|
||||
"space.kscience.visionforge.html.*"
|
||||
)
|
||||
|
||||
|
||||
render<HtmlVisionFragment> { fragment ->
|
||||
handler.produceHtml(fragment = fragment)
|
||||
}
|
||||
|
||||
render<Vision> { vision ->
|
||||
handler.produceHtml {
|
||||
vision(vision)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render<Page> { page ->
|
||||
HTML(page.render(createHTML()), true)
|
||||
}
|
||||
|
||||
render<HtmlFormFragment> { fragment ->
|
||||
handler.produceHtml {
|
||||
fragment(fragment.formBody)
|
||||
vision(fragment.vision)
|
||||
}
|
||||
}
|
||||
afterLoaded()
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package space.kscience.visionforge.jupyter
|
||||
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import kotlinx.html.FORM
|
||||
import kotlinx.html.p
|
||||
import kotlinx.html.stream.createHTML
|
||||
import kotlinx.html.style
|
||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.visionforge.html.HtmlFormFragment
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.visionFragment
|
||||
import space.kscience.visionforge.three.server.VisionServer
|
||||
import space.kscience.visionforge.three.server.serve
|
||||
import space.kscience.visionforge.visionManager
|
||||
|
||||
public class VisionForgeServerHandler(override val context: Context) : ContextAware {
|
||||
private var counter = 0
|
||||
|
||||
private var engine: ApplicationEngine? = null
|
||||
private var server: VisionServer? = null
|
||||
|
||||
public var isolateFragments: Boolean = false
|
||||
|
||||
public fun legacyMode() {
|
||||
isolateFragments = true
|
||||
}
|
||||
|
||||
public fun startServer(
|
||||
host: String = context.properties["visionforge.host"].string ?: "localhost",
|
||||
port: Int = context.properties["visionforge.port"].int ?: VisionServer.DEFAULT_PORT,
|
||||
configuration: VisionServer.() -> Unit = {},
|
||||
): HtmlFragment {
|
||||
engine?.stop(1000, 2000)
|
||||
engine = context.visionManager.serve(host, port) {
|
||||
configuration()
|
||||
server = this
|
||||
}.start()
|
||||
return {
|
||||
if(server!= null){
|
||||
p {
|
||||
style = "color: red;"
|
||||
+"Stopping current VisionForge server"
|
||||
}
|
||||
}
|
||||
p {
|
||||
style = "color: blue;"
|
||||
+"Starting VisionForge server on http://$host:$port"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun stopServer() {
|
||||
engine?.apply {
|
||||
logger.info { "Stopping VisionForge server" }
|
||||
}?.stop(1000, 2000)
|
||||
}
|
||||
|
||||
private fun produceHtmlString(
|
||||
fragment: HtmlVisionFragment,
|
||||
): String = server?.serveVisionsFromFragment("content[${counter++}]", fragment)
|
||||
?: createHTML().apply {
|
||||
visionFragment(context.visionManager, fragment = fragment)
|
||||
}.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)
|
||||
|
||||
public fun form(builder: FORM.() -> Unit): HtmlFormFragment = HtmlFormFragment("form[${counter++}]", builder = builder)
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
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 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -32,18 +32,19 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-solid"))
|
||||
implementation(projects.visionforgeSolid)
|
||||
implementation(projects.jupyter.jupyterBase)
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-gdml"))
|
||||
implementation(projects.visionforgeGdml)
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-threejs"))
|
||||
implementation(project(":ui:ring"))
|
||||
implementation(projects.visionforgeThreejs)
|
||||
implementation(projects.ui.ring)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,34 +1,21 @@
|
||||
package space.kscience.visionforge.gdml.jupyter
|
||||
|
||||
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.misc.DFExperimental
|
||||
import space.kscience.gdml.Gdml
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.embedAndRenderVisionFragment
|
||||
import space.kscience.visionforge.jupyter.JupyterPluginBase
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.visionManager
|
||||
|
||||
@DFExperimental
|
||||
internal class GdmlForJupyter : JupyterIntegration() {
|
||||
|
||||
private val context = Context("GDML") {
|
||||
internal class GdmlForJupyter : JupyterPluginBase(
|
||||
Context("GDML") {
|
||||
plugin(Solids)
|
||||
}
|
||||
) {
|
||||
|
||||
private var counter = 0
|
||||
|
||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||
}.finalize()
|
||||
|
||||
override fun Builder.onLoaded() {
|
||||
override fun Builder.afterLoaded() {
|
||||
|
||||
resources {
|
||||
js("three") {
|
||||
@ -38,23 +25,11 @@ internal class GdmlForJupyter : JupyterIntegration() {
|
||||
|
||||
import(
|
||||
"space.kscience.gdml.*",
|
||||
"kotlinx.html.*",
|
||||
"space.kscience.visionforge.solid.*",
|
||||
"space.kscience.visionforge.html.Page",
|
||||
"space.kscience.visionforge.html.page",
|
||||
"space.kscience.visionforge.gdml.jupyter.*"
|
||||
)
|
||||
|
||||
render<Vision> { vision ->
|
||||
HTML(produceHtmlVisionString { vision(vision) })
|
||||
}
|
||||
|
||||
render<Gdml> { gdmlModel ->
|
||||
HTML(produceHtmlVisionString { vision(gdmlModel.toVision()) })
|
||||
}
|
||||
|
||||
render<Page> { page ->
|
||||
HTML(page.render(createHTML()), true)
|
||||
handler.produceHtml { vision(gdmlModel.toVision()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,6 @@ include(
|
||||
":demo:playground",
|
||||
":demo:plotly-fx",
|
||||
":demo:js-playground",
|
||||
":jupyter:visionforge-jupyter-base",
|
||||
":jupyter:jupyter-base",
|
||||
":jupyter:visionforge-jupyter-gdml"
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ package space.kscience.visionforge.html
|
||||
import kotlinx.html.FlowContent
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.stream.createHTML
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
|
||||
public typealias HtmlFragment = TagConsumer<*>.() -> Unit
|
||||
|
||||
@ -15,9 +14,4 @@ public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
|
||||
|
||||
public fun FlowContent.fragment(fragment: HtmlFragment) {
|
||||
fragment(consumer)
|
||||
}
|
||||
|
||||
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
||||
|
||||
@DFExperimental
|
||||
public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content
|
||||
}
|
@ -6,26 +6,47 @@ import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
|
||||
public fun TagConsumer<*>.embedVisionFragment(
|
||||
public typealias HtmlVisionFragment = VisionTagConsumer<*>.() -> Unit
|
||||
|
||||
@DFExperimental
|
||||
public fun HtmlVisionFragment(content: VisionTagConsumer<*>.() -> Unit): HtmlVisionFragment = content
|
||||
|
||||
|
||||
internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
|
||||
|
||||
|
||||
/**
|
||||
* Render a fragment in the given consumer and return a map of extracted visions
|
||||
* @param manager a VisionManager used for serialization
|
||||
* @param embedData embed Vision initial state in the HTML
|
||||
* @param fetchDataUrl fetch data after first render from given url
|
||||
* @param fetchUpdatesUrl receive push updates from the server at given url
|
||||
* @param idPrefix a prefix to be used before vision ids
|
||||
* @param renderScript if true add rendering script after the fragment
|
||||
*/
|
||||
public fun TagConsumer<*>.visionFragment(
|
||||
manager: VisionManager,
|
||||
embedData: Boolean = true,
|
||||
fetchData: String? = null,
|
||||
fetchUpdates: String? = null,
|
||||
fetchDataUrl: String? = null,
|
||||
fetchUpdatesUrl: String? = null,
|
||||
idPrefix: String? = null,
|
||||
renderScript: Boolean = true,
|
||||
fragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
val visionMap = HashMap<Name, Vision>()
|
||||
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) {
|
||||
val consumer = object : VisionTagConsumer<Any?>(this@visionFragment, manager, idPrefix) {
|
||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||
visionMap[name] = vision
|
||||
// Toggle update mode
|
||||
|
||||
fetchUpdates?.let {
|
||||
fetchUpdatesUrl?.let {
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = it
|
||||
}
|
||||
|
||||
fetchData?.let {
|
||||
fetchDataUrl?.let {
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = it
|
||||
}
|
||||
|
||||
@ -40,39 +61,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, embedData, fetchDataUrl, fetchUpdatesUrl, idPrefix, fragment)
|
||||
|
||||
|
||||
internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
|
||||
|
||||
public fun TagConsumer<*>.embedAndRenderVisionFragment(
|
||||
manager: VisionManager,
|
||||
id: Any,
|
||||
embedData: Boolean = true,
|
||||
fetchData: String? = null,
|
||||
fetchUpdates: String? = null,
|
||||
idPrefix: String? = null,
|
||||
fragment: HtmlVisionFragment,
|
||||
) {
|
||||
div {
|
||||
if (renderScript) {
|
||||
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
|
||||
div {
|
||||
this.id = id.toString()
|
||||
embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment)
|
||||
this.id = id
|
||||
fragment(consumer)
|
||||
}
|
||||
script {
|
||||
type = "text/javascript"
|
||||
unsafe { +"window.${RENDER_FUNCTION_NAME}(\"$id\");" }
|
||||
}
|
||||
} else {
|
||||
fragment(consumer)
|
||||
}
|
||||
}
|
||||
return visionMap
|
||||
}
|
||||
|
||||
public fun FlowContent.visionFragment(
|
||||
manager: VisionManager,
|
||||
embedData: Boolean = true,
|
||||
fetchDataUrl: String? = null,
|
||||
fetchUpdatesUrl: String? = null,
|
||||
idPrefix: String? = null,
|
||||
renderSctipt: Boolean = true,
|
||||
fragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> = consumer.visionFragment(
|
||||
manager,
|
||||
embedData,
|
||||
fetchDataUrl,
|
||||
fetchUpdatesUrl,
|
||||
idPrefix,
|
||||
renderSctipt,
|
||||
fragment
|
||||
)
|
@ -1,12 +1,11 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.visionManager
|
||||
import space.kscience.visionforge.VisionManager
|
||||
|
||||
public data class Page(
|
||||
public val context: Context,
|
||||
public val visionManager: VisionManager,
|
||||
public val title: String,
|
||||
public val headers: Map<String, HtmlFragment>,
|
||||
public val content: HtmlVisionFragment,
|
||||
@ -22,14 +21,14 @@ public data class Page(
|
||||
title(this@Page.title)
|
||||
}
|
||||
body {
|
||||
embedVisionFragment(context.visionManager, fragment = content)
|
||||
visionFragment(visionManager, fragment = content)
|
||||
}
|
||||
}.finalize()
|
||||
}
|
||||
|
||||
|
||||
@DFExperimental
|
||||
public fun Context.page(
|
||||
public fun VisionManager.page(
|
||||
title: String = "VisionForge page",
|
||||
vararg headers: Pair<String, HtmlFragment>,
|
||||
content: HtmlVisionFragment,
|
||||
|
@ -7,6 +7,7 @@ import kotlinx.html.id
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.node
|
||||
|
||||
@Serializable
|
||||
@ -17,10 +18,29 @@ public class VisionOfHtmlForm(
|
||||
public var values: Meta? by meta.node()
|
||||
}
|
||||
|
||||
public inline fun <R> TagConsumer<R>.visionOfForm(id: String, crossinline builder: FORM.() -> Unit): VisionOfHtmlForm {
|
||||
form {
|
||||
this.id = id
|
||||
builder()
|
||||
public class HtmlFormFragment internal constructor(
|
||||
public val vision: VisionOfHtmlForm,
|
||||
public val formBody: HtmlFragment,
|
||||
){
|
||||
public val values: Meta? get() = vision.values
|
||||
public operator fun get(valueName: String): Meta? = values?.get(valueName)
|
||||
}
|
||||
|
||||
public fun HtmlFormFragment(id: String? = null, builder: FORM.() -> Unit): HtmlFormFragment {
|
||||
val realId = id ?: "form[${builder.hashCode().toUInt()}]"
|
||||
return HtmlFormFragment(VisionOfHtmlForm(realId)) {
|
||||
form {
|
||||
this.id = realId
|
||||
builder()
|
||||
}
|
||||
}
|
||||
return VisionOfHtmlForm(id)
|
||||
}
|
||||
|
||||
public fun <R> TagConsumer<R>.formFragment(
|
||||
id: String? = null,
|
||||
builder: FORM.() -> Unit,
|
||||
): VisionOfHtmlForm {
|
||||
val formFragment = HtmlFormFragment(id, builder)
|
||||
fragment(formFragment.formBody)
|
||||
return formFragment.vision
|
||||
}
|
@ -42,7 +42,7 @@ public abstract class VisionTagConsumer<R>(
|
||||
private val idPrefix: String? = null,
|
||||
) : TagConsumer<R> by root {
|
||||
|
||||
public open fun resolveId(name: Name): String = (idPrefix ?: "output:") + name.toString()
|
||||
public open fun resolveId(name: Name): String = (idPrefix ?: "output") + "[$name]"
|
||||
|
||||
/**
|
||||
* Render a vision inside the output fragment
|
||||
|
@ -164,7 +164,7 @@ public class VisionClient : AbstractPlugin() {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
pathname += "/vision"
|
||||
pathname += "/data"
|
||||
}
|
||||
} else {
|
||||
URL(attr.value)
|
||||
|
@ -94,7 +94,7 @@ public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<Vis
|
||||
form.onsubmit = { event ->
|
||||
event.preventDefault()
|
||||
val formData = FormData(form).toMeta()
|
||||
console.log(formData.toString())
|
||||
//console.log(formData.toString())
|
||||
vision.values = formData
|
||||
false
|
||||
}
|
||||
|
@ -4,12 +4,10 @@ import io.ktor.application.*
|
||||
import io.ktor.features.CORS
|
||||
import io.ktor.features.CallLogging
|
||||
import io.ktor.html.respondHtml
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.content.resources
|
||||
import io.ktor.http.content.static
|
||||
import io.ktor.http.withCharset
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
@ -25,8 +23,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.createHTML
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
@ -34,7 +30,10 @@ 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.*
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.fragment
|
||||
import space.kscience.visionforge.html.visionFragment
|
||||
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
@ -43,12 +42,13 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* A ktor plugin container with given [routing]
|
||||
* @param serverUrl a server url including root route
|
||||
*/
|
||||
public class VisionServer internal constructor(
|
||||
private val visionManager: VisionManager,
|
||||
private val application: Application,
|
||||
private val rootRoute: String,
|
||||
) : Configurable, CoroutineScope by application {
|
||||
private val serverUrl: Url,
|
||||
private val root: Route,
|
||||
) : Configurable, CoroutineScope by root.application {
|
||||
override val meta: ObservableMutableMeta = MutableMeta()
|
||||
|
||||
/**
|
||||
@ -74,7 +74,7 @@ public class VisionServer internal constructor(
|
||||
/**
|
||||
* Connect to server to get pushes. The address of the server is embedded in the tag. Default: `true`
|
||||
*/
|
||||
public var dataConnect: Boolean by meta.boolean(true, Name.parse("data.connect"))
|
||||
public var dataUpdate: Boolean by meta.boolean(true, Name.parse("data.update"))
|
||||
|
||||
/**
|
||||
* a list of headers that should be applied to all pages
|
||||
@ -90,6 +90,7 @@ public class VisionServer internal constructor(
|
||||
|
||||
private fun HTML.visionPage(
|
||||
title: String,
|
||||
pagePath: String,
|
||||
headers: List<HtmlFragment>,
|
||||
visionFragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
@ -106,11 +107,10 @@ public class VisionServer internal constructor(
|
||||
}
|
||||
body {
|
||||
//Load the fragment and remember all loaded visions
|
||||
visionMap = embedVisionFragment(
|
||||
visionMap = visionFragment(
|
||||
manager = visionManager,
|
||||
embedData = true,
|
||||
fetchDataUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE,
|
||||
fetchUpdatesUrl = VisionTagConsumer.AUTO_DATA_ATTRIBUTE,
|
||||
fetchUpdatesUrl = "$serverUrl$pagePath/ws",
|
||||
fragment = visionFragment
|
||||
)
|
||||
}
|
||||
@ -122,7 +122,7 @@ public class VisionServer internal constructor(
|
||||
* Server a map of visions without providing explicit html page for them
|
||||
*/
|
||||
@OptIn(DFExperimental::class)
|
||||
internal fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
|
||||
private fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
|
||||
application.log.info("Serving visions $visions at $route")
|
||||
|
||||
//Update websocket
|
||||
@ -157,7 +157,7 @@ public class VisionServer internal constructor(
|
||||
}
|
||||
}
|
||||
//Plots in their json representation
|
||||
get("vision") {
|
||||
get("data") {
|
||||
val name: String = call.request.queryParameters["name"]
|
||||
?: error("Vision name is not defined in parameters")
|
||||
|
||||
@ -174,47 +174,40 @@ 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve visions in a given [route] without providing a page template
|
||||
*/
|
||||
public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit {
|
||||
application.routing {
|
||||
route(rootRoute) {
|
||||
route(route) {
|
||||
serveVisions(this, visions)
|
||||
}
|
||||
}
|
||||
root.route(route) {
|
||||
serveVisions(this, visions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a page, potentially containing any number of visions at a given [route] with given [headers].
|
||||
* Compile a fragment to string and serve visions from it
|
||||
*/
|
||||
public fun serveVisionsFromFragment(
|
||||
route: String,
|
||||
fragment: HtmlVisionFragment,
|
||||
): String = createHTML().apply {
|
||||
val visions = visionFragment(
|
||||
visionManager,
|
||||
embedData = true,
|
||||
fetchUpdatesUrl = "$serverUrl$route/ws",
|
||||
renderScript = true,
|
||||
fragment = fragment
|
||||
)
|
||||
serveVisions(route, visions)
|
||||
}.finalize()
|
||||
|
||||
/**
|
||||
* Serve a page, potentially containing any number of visions at a given [pagePath] with given [headers].
|
||||
*
|
||||
*/
|
||||
public fun page(
|
||||
route: String = DEFAULT_PAGE,
|
||||
title: String = "VisionForge server page '$route'",
|
||||
pagePath: String = DEFAULT_PAGE,
|
||||
title: String = "VisionForge server page '$pagePath'",
|
||||
headers: List<HtmlFragment> = emptyList(),
|
||||
visionFragment: HtmlVisionFragment,
|
||||
) {
|
||||
@ -223,35 +216,34 @@ public class VisionServer internal constructor(
|
||||
val cachedHtml: String? = if (cacheFragments) {
|
||||
//Create and cache page html and map of visions
|
||||
createHTML(true).html {
|
||||
visions.putAll(visionPage(title, headers, visionFragment))
|
||||
visions.putAll(visionPage(title, pagePath, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
application.routing {
|
||||
route(rootRoute) {
|
||||
route(route) {
|
||||
serveVisions(this, visions)
|
||||
//filled pages
|
||||
get {
|
||||
if (cachedHtml == null) {
|
||||
//re-create html and vision list on each call
|
||||
call.respondHtml {
|
||||
visions.clear()
|
||||
visions.putAll(visionPage(title, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
//Use cached html
|
||||
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
|
||||
}
|
||||
root.route(pagePath) {
|
||||
serveVisions(this, visions)
|
||||
//filled pages
|
||||
get {
|
||||
if (cachedHtml == null) {
|
||||
//re-create html and vision list on each call
|
||||
call.respondHtml {
|
||||
visions.clear()
|
||||
visions.putAll(visionPage(title, pagePath, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
//Use cached html
|
||||
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val DEFAULT_PORT: Int = 7777
|
||||
public const val DEFAULT_PAGE: String = "/"
|
||||
public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval")
|
||||
}
|
||||
@ -286,7 +278,11 @@ public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() -
|
||||
/**
|
||||
* Attach VisionForge server application to given server
|
||||
*/
|
||||
public fun Application.visionServer(context: Context, route: String = DEFAULT_PAGE): VisionServer {
|
||||
public fun Application.visionServer(
|
||||
visionManager: VisionManager,
|
||||
webServerUrl: Url,
|
||||
path: String = DEFAULT_PAGE,
|
||||
): VisionServer {
|
||||
if (featureOrNull(WebSockets) == null) {
|
||||
install(WebSockets)
|
||||
}
|
||||
@ -301,17 +297,15 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA
|
||||
install(CallLogging)
|
||||
}
|
||||
|
||||
val visionManager = context.fetch(VisionManager)
|
||||
val serverRoute = (featureOrNull(Routing) ?: install(Routing)).createRouteFromPath(path)
|
||||
|
||||
routing {
|
||||
route(route) {
|
||||
static {
|
||||
resources()
|
||||
}
|
||||
serverRoute {
|
||||
static {
|
||||
resources()
|
||||
}
|
||||
}
|
||||
|
||||
return VisionServer(visionManager, this, route)
|
||||
return VisionServer(visionManager, webServerUrl.copy(encodedPath = path), serverRoute)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,10 +313,11 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA
|
||||
*/
|
||||
public fun VisionManager.serve(
|
||||
host: String = "localhost",
|
||||
port: Int = 7777,
|
||||
port: Int = VisionServer.DEFAULT_PORT,
|
||||
block: VisionServer.() -> Unit,
|
||||
): ApplicationEngine = context.embeddedServer(CIO, port, host) {
|
||||
visionServer(context).apply(block)
|
||||
val url = URLBuilder(host = host, port = port).build()
|
||||
visionServer(this@serve, url).apply(block)
|
||||
}.start()
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,7 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
||||
obj.onPropertyChange { name ->
|
||||
when {
|
||||
name.startsWith(Solid.GEOMETRY_KEY) -> {
|
||||
val oldGeometry = mesh.geometry as BufferGeometry
|
||||
val oldGeometry = mesh.geometry
|
||||
val newGeometry = buildGeometry(obj)
|
||||
oldGeometry.attributes = newGeometry.attributes
|
||||
//mesh.applyWireFrame(obj)
|
||||
|
@ -53,7 +53,7 @@ public class ThreeCanvas(
|
||||
private val mousePosition: Vector2 = Vector2()
|
||||
|
||||
private val scene: Scene = Scene().apply {
|
||||
options.useProperty(Canvas3DOptions::axes, this) { axesConfig ->
|
||||
options.useProperty(Canvas3DOptions::axes, this) {
|
||||
getObjectByName(AXES_NAME)?.let { remove(it) }
|
||||
val axesObject = AxesHelper(axes.size.toInt()).apply { visible = axes.visible }
|
||||
axesObject.name = AXES_NAME
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.visionforge.three.server
|
||||
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.page
|
||||
@ -16,7 +16,7 @@ public fun VisionServer.useThreeJs(): Unit {
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun Context.makeThreeJsFile(
|
||||
public fun VisionManager.makeThreeJsFile(
|
||||
content: HtmlVisionFragment,
|
||||
path: Path? = null,
|
||||
title: String = "VisionForge page",
|
||||
|
Loading…
Reference in New Issue
Block a user