0.2.0 #71

Merged
altavir merged 139 commits from dev into master 2022-01-24 09:44:18 +03:00
22 changed files with 343 additions and 305 deletions
Showing only changes of commit 43285de33c - Show all commits

View File

@ -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)

View File

@ -1,39 +1,25 @@
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") {
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)
}
}

View File

@ -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:"

View File

@ -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())

View File

@ -9,12 +9,12 @@ kotlin {
sourceSets {
commonMain{
dependencies{
api(projects.visionforge.visionforgeCore)
api(projects.visionforgeCore)
}
}
jvmMain {
dependencies {
implementation(project(":visionforge-server"))
api(projects.visionforgeServer)
}
}
}

View 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()
}
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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()) }
}
}
}

View File

@ -61,6 +61,6 @@ include(
":demo:playground",
":demo:plotly-fx",
":demo:js-playground",
":jupyter:visionforge-jupyter-base",
":jupyter:jupyter-base",
":jupyter:visionforge-jupyter-gdml"
)

View File

@ -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
@ -16,8 +15,3 @@ 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

View File

@ -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(
}
}
}
if (renderScript) {
val id = "fragment[${fragment.hashCode()}/${Random.nextUInt()}]"
div {
this.id = id
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 {
div {
this.id = id.toString()
embedVisionFragment(manager, embedData, fetchData, fetchUpdates, idPrefix, fragment)
}
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
)

View File

@ -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,

View File

@ -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 {
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 = id
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
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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) {
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,15 +216,13 @@ 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) {
root.route(pagePath) {
serveVisions(this, visions)
//filled pages
get {
@ -239,7 +230,7 @@ public class VisionServer internal constructor(
//re-create html and vision list on each call
call.respondHtml {
visions.clear()
visions.putAll(visionPage(title, headers, visionFragment))
visions.putAll(visionPage(title, pagePath, headers, visionFragment))
}
} else {
//Use cached html
@ -247,11 +238,12 @@ public class VisionServer internal constructor(
}
}
}
}
}
}
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) {
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()
/**

View File

@ -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)

View File

@ -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

View File

@ -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",