0.2.0 #71
@ -21,7 +21,7 @@ import space.kscience.visionforge.gdml.markLayers
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.ring.ThreeCanvasWithControls
|
||||
import space.kscience.visionforge.ring.tab
|
||||
import space.kscience.visionforge.root
|
||||
import space.kscience.visionforge.setAsRoot
|
||||
import space.kscience.visionforge.solid.Solid
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import styled.css
|
||||
@ -50,7 +50,7 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||
val gdml = Gdml.decodeFromString(data)
|
||||
gdml.toVision().apply {
|
||||
root(visionManager)
|
||||
setAsRoot(visionManager)
|
||||
console.info("Marking layers for file $name")
|
||||
markLayers()
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("jupyter.api")
|
||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven("https://repo.kotlin.link")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":demo:playground"))
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
|
||||
kotlinOptions {
|
||||
jvmTarget = ru.mipt.npm.gradle.KScienceVersions.JVM_TARGET.toString()
|
||||
}
|
||||
}
|
||||
|
||||
extensions.findByType<JavaPluginExtension>()?.apply {
|
||||
targetCompatibility = ru.mipt.npm.gradle.KScienceVersions.JVM_TARGET
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.processJupyterApiResources {
|
||||
libraryProducers = listOf("playground.VisionForgePlayGroundForJupyter")
|
||||
}
|
||||
|
||||
tasks.findByName("shadowJar")?.dependsOn("processJupyterApiResources")
|
@ -5,7 +5,7 @@ import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
|
||||
import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.removeAll
|
||||
import space.kscience.visionforge.root
|
||||
import space.kscience.visionforge.setAsRoot
|
||||
import space.kscience.visionforge.setProperty
|
||||
import space.kscience.visionforge.solid.*
|
||||
import kotlin.math.PI
|
||||
@ -37,7 +37,7 @@ class Model(val manager: VisionManager) {
|
||||
var tracks: SolidGroup
|
||||
|
||||
val root: SolidGroup = SolidGroup().apply {
|
||||
root(this@Model.manager)
|
||||
setAsRoot(this@Model.manager)
|
||||
material {
|
||||
wireframe
|
||||
color("darkgreen")
|
||||
|
@ -76,9 +76,6 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
context.launch {
|
||||
// val event = props.connection.get<Event>(
|
||||
// "http://localhost:8080/event"
|
||||
// )
|
||||
val event = window.fetch(
|
||||
"http://localhost:8080/event",
|
||||
RequestInit("GET")
|
||||
|
@ -1,14 +1,13 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
kotlin("jupyter.api")
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
}
|
||||
|
||||
repositories{
|
||||
jcenter()
|
||||
maven("https://kotlin.bintray.com/kotlinx")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
maven("https://dl.bintray.com/mipt-npm/dataforge")
|
||||
maven("https://dl.bintray.com/mipt-npm/kscience")
|
||||
maven("https://dl.bintray.com/mipt-npm/dev")
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://jitpack.io")
|
||||
maven("https://repo.kotlin.link")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@ -27,7 +26,8 @@ kotlin {
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
jvm{
|
||||
jvm {
|
||||
withJava()
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
}
|
||||
@ -37,7 +37,7 @@ kotlin {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution")
|
||||
val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution")
|
||||
|
||||
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||
dependsOn(jsBrowserDistribution)
|
||||
@ -59,14 +59,14 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
val jsMain by getting{
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(project(":ui:ring"))
|
||||
api(project(":visionforge-threejs"))
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting{
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
api(project(":visionforge-server"))
|
||||
api("ch.qos.logback:logback-classic:1.2.3")
|
||||
@ -75,3 +75,9 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask> {
|
||||
libraryProducers = listOf("space.kscience.visionforge.examples.VisionForgePlayGroundForJupyter")
|
||||
}
|
||||
|
||||
tasks.findByName("shadowJar")?.dependsOn("processJupyterApiResources")
|
@ -1,12 +1,9 @@
|
||||
package playground
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.id
|
||||
import kotlinx.html.script
|
||||
import kotlinx.html.stream.createHTML
|
||||
import kotlinx.html.unsafe
|
||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||
import org.jetbrains.kotlinx.jupyter.api.libraries.*
|
||||
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
|
||||
@ -15,7 +12,7 @@ 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.embedVisionFragment
|
||||
import space.kscience.visionforge.html.embedAndRenderVisionFragment
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.plotly.asVision
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
@ -29,27 +26,19 @@ public class VisionForgePlayGroundForJupyter : JupyterIntegration() {
|
||||
plugin(PlotlyPlugin)
|
||||
}
|
||||
|
||||
private val jsBundle = ResourceFallbacksBundle(listOf(
|
||||
ResourceLocation("js/visionforge-playground.js", ResourcePathType.CLASSPATH_PATH))
|
||||
)
|
||||
private val jsResource = LibraryResource(name = "VisionForge", type = ResourceType.JS, bundles = listOf(jsBundle))
|
||||
|
||||
private var counter = 0
|
||||
|
||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().div {
|
||||
val id = "visionforge.vision[${counter++}]"
|
||||
div {
|
||||
this.id = id
|
||||
embedVisionFragment(context.visionManager, fragment = fragment)
|
||||
}
|
||||
script {
|
||||
type = "text/javascript"
|
||||
unsafe { +"window.renderAllVisionsById(\"$id\");" }
|
||||
}
|
||||
}
|
||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||
}.finalize()
|
||||
|
||||
override fun Builder.onLoaded() {
|
||||
resource(jsResource)
|
||||
|
||||
resources {
|
||||
js("VisionForge"){
|
||||
classPath("js/visionforge-playground.js")
|
||||
}
|
||||
}
|
||||
|
||||
import(
|
||||
"space.kscience.gdml.*",
|
67
demo/playground/src/jvmMain/kotlin/formServer.kt
Normal file
67
demo/playground/src/jvmMain/kotlin/formServer.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import kotlinx.html.*
|
||||
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.onPropertyChange
|
||||
import space.kscience.visionforge.three.server.close
|
||||
import space.kscience.visionforge.three.server.openInBrowser
|
||||
import space.kscience.visionforge.three.server.serve
|
||||
import space.kscience.visionforge.three.server.useScript
|
||||
|
||||
fun main() {
|
||||
val visionManager = Global.fetch(VisionManager)
|
||||
|
||||
val server = visionManager.serve {
|
||||
useScript("js/visionforge-playground.js")
|
||||
page {
|
||||
val form = visionOfForm("form") {
|
||||
label {
|
||||
htmlFor = "fname"
|
||||
+"First name:"
|
||||
}
|
||||
br()
|
||||
input {
|
||||
type = InputType.text
|
||||
id = "fname"
|
||||
name = "fname"
|
||||
value = "John"
|
||||
}
|
||||
br()
|
||||
label {
|
||||
htmlFor = "lname"
|
||||
+"Last name:"
|
||||
}
|
||||
br()
|
||||
input {
|
||||
type = InputType.text
|
||||
id = "lname"
|
||||
name = "lname"
|
||||
value = "Doe"
|
||||
}
|
||||
br()
|
||||
br()
|
||||
input {
|
||||
type = InputType.submit
|
||||
value = "Submit"
|
||||
}
|
||||
}
|
||||
|
||||
vision("form".asName(), form)
|
||||
form.onPropertyChange {
|
||||
println(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.openInBrowser()
|
||||
|
||||
while (readln() != "exit") {
|
||||
|
||||
}
|
||||
|
||||
server.close()
|
||||
}
|
@ -226,7 +226,7 @@ fun main() {
|
||||
}
|
||||
}
|
||||
}.toVision {
|
||||
configure { parent, solid, material ->
|
||||
configure { _, solid, _ ->
|
||||
//disable visibility for the world box
|
||||
if(solid.name == "world"){
|
||||
visible = false
|
@ -6,16 +6,18 @@ import kotlinx.serialization.json.Json
|
||||
import space.kscience.visionforge.solid.SolidGroup
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
|
||||
private val json = Json {
|
||||
serializersModule = Solids.serializersModuleForSolids
|
||||
prettyPrintIndent = " "
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
coerceInputValues = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
@ExperimentalSerializationApi
|
||||
fun main() {
|
||||
val schema = Json {
|
||||
serializersModule = Solids.serializersModuleForSolids
|
||||
prettyPrintIndent = " "
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
coerceInputValues = true
|
||||
encodeDefaults = true
|
||||
}.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
|
||||
val schema = json.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
|
||||
println(schema)
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
package ru.mipt.npm.sat
|
||||
|
||||
import space.kscience.dataforge.meta.set
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.style
|
||||
import space.kscience.visionforge.useStyle
|
||||
import kotlin.math.PI
|
||||
|
||||
@DFExperimental
|
||||
internal fun visionOfSatellite(
|
||||
layers: Int = 10,
|
||||
layerHeight: Number = 10,
|
||||
|
@ -6,7 +6,6 @@ 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
|
||||
@ -53,12 +52,6 @@ public abstract class JupyterPluginBase(
|
||||
server = null
|
||||
}
|
||||
|
||||
resources {
|
||||
js("three") {
|
||||
classPath("js/gdml-jupyter.js")
|
||||
}
|
||||
}
|
||||
|
||||
import(
|
||||
"kotlinx.html.*",
|
||||
"space.kscience.visionforge.html.Page",
|
||||
|
@ -59,7 +59,6 @@ include(
|
||||
":demo:muon-monitor",
|
||||
":demo:sat-demo",
|
||||
":demo:playground",
|
||||
":demo:jupyter-playground",
|
||||
":demo:plotly-fx",
|
||||
":demo:js-playground",
|
||||
":jupyter:visionforge-jupyter-base",
|
||||
|
@ -83,7 +83,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
|
||||
useEffect {
|
||||
props.context.launch {
|
||||
solid = props.builderOfSolid.await().also {
|
||||
it?.root(props.context.visionManager)
|
||||
it?.setAsRoot(props.context.visionManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public open class VisionBase(
|
||||
override fun hashCode(): Int = Meta.hashCode(this)
|
||||
}
|
||||
|
||||
override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
|
||||
final override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
|
||||
|
||||
override fun getPropertyValue(
|
||||
name: Name,
|
||||
|
@ -14,6 +14,17 @@ import space.kscience.dataforge.values.Null
|
||||
import kotlin.jvm.Synchronized
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Create a deep copy of given Vision without external connections.
|
||||
*/
|
||||
private fun Vision.deepCopy(): Vision {
|
||||
//Assuming that unrooted visions are already isolated
|
||||
val manager = this.manager ?: return this
|
||||
//TODO replace by efficient deep copy
|
||||
val json = manager.encodeToJsonElement(this)
|
||||
return manager.decodeFromJson(json)
|
||||
}
|
||||
|
||||
/**
|
||||
* An update for a [Vision] or a [VisionGroup]
|
||||
*/
|
||||
@ -50,20 +61,14 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
/**
|
||||
* Isolate collected changes by creating detached copies of given visions
|
||||
*/
|
||||
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
||||
public fun deepCopy(): VisionChange = VisionChange(
|
||||
reset,
|
||||
vision?.isolate(manager),
|
||||
vision?.deepCopy(),
|
||||
if (propertyChange.isEmpty()) null else propertyChange.seal(),
|
||||
if (children.isEmpty()) null else children.mapValues { it.value.isolate(manager) }
|
||||
if (children.isEmpty()) null else children.mapValues { it.value.deepCopy() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun Vision.isolate(manager: VisionManager): Vision {
|
||||
//TODO replace by efficient deep copy
|
||||
val json = manager.encodeToJsonElement(this)
|
||||
return manager.decodeFromJson(json)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delete flag showing that this vision child should be removed
|
||||
* @param vision a new value for vision content
|
||||
@ -78,8 +83,8 @@ public data class VisionChange(
|
||||
public val children: Map<Name, VisionChange>? = null,
|
||||
)
|
||||
|
||||
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
|
||||
VisionChangeBuilder().apply(block).isolate(manager)
|
||||
public inline fun VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
|
||||
VisionChangeBuilder().apply(block).deepCopy()
|
||||
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
@ -115,9 +120,10 @@ private fun CoroutineScope.collectChange(
|
||||
}
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
/**
|
||||
* Generate a flow of changes of this vision and its children
|
||||
*/
|
||||
public fun Vision.flowChanges(
|
||||
manager: VisionManager,
|
||||
collectionDuration: Duration,
|
||||
): Flow<VisionChange> = flow {
|
||||
|
||||
@ -126,7 +132,7 @@ public fun Vision.flowChanges(
|
||||
collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||
|
||||
//Send initial vision state
|
||||
val initialChange = VisionChange(vision = isolate(manager))
|
||||
val initialChange = VisionChange(vision = deepCopy())
|
||||
emit(initialChange)
|
||||
|
||||
while (currentCoroutineContext().isActive) {
|
||||
@ -135,7 +141,7 @@ public fun Vision.flowChanges(
|
||||
//Propagate updates only if something is changed
|
||||
if (!collector.isEmpty()) {
|
||||
//emit changes
|
||||
emit(collector.isolate(manager))
|
||||
emit(collector.deepCopy())
|
||||
//Reset the collector
|
||||
collector = VisionChangeBuilder()
|
||||
}
|
||||
|
@ -160,8 +160,9 @@ public open class VisionGroupBase(
|
||||
internal class RootVisionGroup(override val manager: VisionManager) : VisionGroupBase()
|
||||
|
||||
/**
|
||||
* Designate this [VisionGroup] as a root group and assign a [VisionManager] as its parent
|
||||
* Designate this [VisionGroup] as a root and assign a [VisionManager] as its parent
|
||||
*/
|
||||
public fun Vision.root(manager: VisionManager) {
|
||||
public fun Vision.setAsRoot(manager: VisionManager) {
|
||||
if (parent != null) error("This Vision already has a parent. It could not be set as root")
|
||||
parent = RootVisionGroup(manager)
|
||||
}
|
@ -13,6 +13,10 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.toJson
|
||||
import space.kscience.dataforge.meta.toMeta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.html.VisionOfCheckbox
|
||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
||||
import space.kscience.visionforge.html.VisionOfNumberField
|
||||
import space.kscience.visionforge.html.VisionOfTextField
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
@ -66,6 +70,10 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
default { VisionBase.serializer() }
|
||||
subclass(VisionBase.serializer())
|
||||
subclass(VisionGroupBase.serializer())
|
||||
subclass(VisionOfNumberField.serializer())
|
||||
subclass(VisionOfTextField.serializer())
|
||||
subclass(VisionOfCheckbox.serializer())
|
||||
subclass(VisionOfHtmlForm.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.FORM
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.form
|
||||
import kotlinx.html.id
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.node
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.form")
|
||||
public class VisionOfHtmlForm(
|
||||
public val formId: String,
|
||||
) : VisionOfHtmlInput() {
|
||||
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()
|
||||
}
|
||||
return VisionOfHtmlForm(id)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.dataforge.meta.number
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.visionforge.VisionBase
|
||||
|
||||
@Serializable
|
||||
public abstract class VisionOfHtmlInput : VisionBase() {
|
||||
public var disabled: Boolean by meta.boolean(false)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.text")
|
||||
public class VisionOfTextField(
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var text: String? by meta.string()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.checkbox")
|
||||
public class VisionOfCheckbox(
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var checked: Boolean? by meta.boolean()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.number")
|
||||
public class VisionOfNumberField(
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var value: Number? by meta.number()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.range")
|
||||
public class VisionOfRangeField(
|
||||
public val min: Double,
|
||||
public val max: Double,
|
||||
public val step: Double = 1.0,
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var value: Number? by meta.number()
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.root
|
||||
import space.kscience.visionforge.setAsRoot
|
||||
import kotlin.collections.set
|
||||
|
||||
@DslMarker
|
||||
@ -81,11 +81,11 @@ public abstract class VisionTagConsumer<R>(
|
||||
@OptIn(DFExperimental::class)
|
||||
public inline fun <T> TagConsumer<T>.vision(
|
||||
name: Name,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
@OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
|
||||
): T {
|
||||
val output = VisionOutput(manager)
|
||||
val vision = output.visionProvider()
|
||||
vision.root(manager)
|
||||
vision.setAsRoot(manager)
|
||||
return vision(name, vision, output.meta)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,74 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.serializerOrNull
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.Named
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.cast
|
||||
|
||||
/**
|
||||
* A browser renderer for a [Vision].
|
||||
*/
|
||||
@Type(ElementVisionRenderer.TYPE)
|
||||
public interface ElementVisionRenderer : Named {
|
||||
|
||||
/**
|
||||
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
||||
* can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
|
||||
* higher value in order to "steal" rendering job
|
||||
*/
|
||||
public fun rateVision(vision: Vision): Int
|
||||
|
||||
/**
|
||||
* Display the [vision] inside a given [element] replacing its current content.
|
||||
* @param meta additional parameters for rendering container
|
||||
*/
|
||||
public fun render(element: Element, vision: Vision, meta: Meta = Meta.EMPTY)
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "elementVisionRenderer"
|
||||
public const val ZERO_RATING: Int = 0
|
||||
public const val DEFAULT_RATING: Int = 10
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A browser renderer for element of given type
|
||||
*/
|
||||
public class SingleTypeVisionRenderer<T : Vision>(
|
||||
public val kClass: KClass<T>,
|
||||
private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
|
||||
private val renderFunction: TagConsumer<HTMLElement>.(vision: T, meta: Meta) -> Unit,
|
||||
) : ElementVisionRenderer {
|
||||
|
||||
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
|
||||
override val name: Name
|
||||
get() = kClass.serializerOrNull()?.descriptor?.serialName?.parseAsName()
|
||||
?: kClass.toString().asName()
|
||||
|
||||
override fun rateVision(vision: Vision): Int =
|
||||
if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING
|
||||
|
||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||
element.clear()
|
||||
element.append {
|
||||
renderFunction(kClass.cast(vision), meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <reified T : Vision> ElementVisionRenderer(
|
||||
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
|
||||
noinline renderFunction: TagConsumer<HTMLElement>.(vision: T, meta: Meta) -> Unit,
|
||||
): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction)
|
@ -10,6 +10,9 @@ import org.w3c.dom.url.URL
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaSerializer
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.html.RENDER_FUNCTION_NAME
|
||||
import space.kscience.visionforge.html.VisionTagConsumer
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||
@ -19,6 +22,9 @@ import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_A
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* A Kotlin-browser plugin that renders visions based on provided renderers and governs communication with the server.
|
||||
*/
|
||||
public class VisionClient : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
private val visionManager: VisionManager by require(VisionManager)
|
||||
@ -102,10 +108,12 @@ public class VisionClient : AbstractPlugin() {
|
||||
//Backward change propagation
|
||||
var feedbackJob: Job? = null
|
||||
|
||||
//Feedback changes aggregation time in milliseconds
|
||||
val feedbackAggregationTime = meta["aggregationTime"]?.int ?: 300
|
||||
|
||||
onopen = {
|
||||
feedbackJob = vision.flowChanges(
|
||||
visionManager,
|
||||
300.milliseconds
|
||||
feedbackAggregationTime.milliseconds
|
||||
).onEach { change ->
|
||||
send(visionManager.encodeToString(change))
|
||||
}.launchIn(visionManager.context)
|
||||
@ -180,6 +188,12 @@ public class VisionClient : AbstractPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) mapOf(
|
||||
numberVisionRenderer.name to numberVisionRenderer,
|
||||
textVisionRenderer.name to textVisionRenderer,
|
||||
formVisionRenderer.name to formVisionRenderer
|
||||
) else super.content(target)
|
||||
|
||||
public companion object : PluginFactory<VisionClient> {
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
|
||||
|
@ -1,27 +0,0 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import org.w3c.dom.Element
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.Type
|
||||
|
||||
@Type(ElementVisionRenderer.TYPE)
|
||||
public interface ElementVisionRenderer {
|
||||
|
||||
/**
|
||||
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
||||
* can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
|
||||
* higher value in order to "steal" rendering job
|
||||
*/
|
||||
public fun rateVision(vision: Vision): Int
|
||||
|
||||
/**
|
||||
* Display the [vision] inside a given [element] replacing its current content
|
||||
*/
|
||||
public fun render(element: Element, vision: Vision, meta: Meta = Meta.EMPTY)
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "elementVisionRenderer"
|
||||
public const val ZERO_RATING: Int = 0
|
||||
public const val DEFAULT_RATING: Int = 10
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.js.input
|
||||
import kotlinx.html.js.label
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import org.w3c.dom.HTMLFormElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.xhr.FormData
|
||||
import space.kscience.dataforge.meta.DynamicMeta
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.valueSequence
|
||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
||||
import space.kscience.visionforge.html.VisionOfNumberField
|
||||
import space.kscience.visionforge.html.VisionOfTextField
|
||||
|
||||
public val textVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { vision, _ ->
|
||||
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]"
|
||||
vision.label?.let {
|
||||
label {
|
||||
htmlFor = name
|
||||
+it
|
||||
}
|
||||
}
|
||||
input {
|
||||
type = InputType.text
|
||||
this.name = name
|
||||
vision.useProperty(VisionOfTextField::text) {
|
||||
value = it ?: ""
|
||||
}
|
||||
onChangeFunction = {
|
||||
vision.text = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public val numberVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfNumberField> { vision, _ ->
|
||||
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]"
|
||||
vision.label?.let {
|
||||
label {
|
||||
htmlFor = name
|
||||
+it
|
||||
}
|
||||
}
|
||||
input {
|
||||
type = InputType.text
|
||||
this.name = name
|
||||
vision.useProperty(VisionOfNumberField::value) {
|
||||
value = it?.toDouble() ?: 0.0
|
||||
}
|
||||
onChangeFunction = {
|
||||
vision.value = value.toDoubleOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun FormData.toMeta(): Meta {
|
||||
@Suppress("UNUSED_VARIABLE") val formData = this
|
||||
//val res = js("Object.fromEntries(formData);")
|
||||
val `object` = js("{}")
|
||||
//language=JavaScript
|
||||
js("""
|
||||
formData.forEach(function(value, key){
|
||||
// Reflect.has in favor of: object.hasOwnProperty(key)
|
||||
if(!Reflect.has(object, key)){
|
||||
object[key] = value;
|
||||
return;
|
||||
}
|
||||
if(!Array.isArray(object[key])){
|
||||
object[key] = [object[key]];
|
||||
}
|
||||
object[key].push(value);
|
||||
});
|
||||
""")
|
||||
return DynamicMeta(`object`)
|
||||
}
|
||||
|
||||
public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlForm> { vision, _ ->
|
||||
|
||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||
?: error("An element with id = '${vision.formId} is not a form")
|
||||
|
||||
console.info("Adding hooks to form '$form'")
|
||||
|
||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||
val inputs = form.getElementsByTagName("input")
|
||||
values?.valueSequence()?.forEach { (token, value) ->
|
||||
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
form.onsubmit = { event ->
|
||||
event.preventDefault()
|
||||
val formData = FormData(form).toMeta()
|
||||
console.log(formData.toString())
|
||||
vision.values = formData
|
||||
false
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import org.w3c.xhr.FormData
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.stringList
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FormTest {
|
||||
@Test
|
||||
fun testFormConversion() {
|
||||
val fd = FormData()
|
||||
fd.append("a", "22")
|
||||
fd.append("b", "1")
|
||||
fd.append("b", "2")
|
||||
val meta = fd.toMeta()
|
||||
assertEquals(22, meta["a"].int)
|
||||
assertEquals(listOf("1","2"), meta["b"]?.stringList)
|
||||
}
|
||||
|
||||
}
|
@ -144,7 +144,7 @@ public class VisionServer internal constructor(
|
||||
|
||||
try {
|
||||
withContext(visionManager.context.coroutineContext) {
|
||||
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
|
||||
vision.flowChanges(updateInterval.milliseconds).collect { update ->
|
||||
val json = visionManager.jsonFormat.encodeToString(
|
||||
VisionChange.serializer(),
|
||||
update
|
||||
|
@ -20,7 +20,7 @@ class VisionUpdateTest {
|
||||
val targetVision = SolidGroup {
|
||||
box(200,200,200, name = "origin")
|
||||
}
|
||||
val dif = VisionChange(visionManager){
|
||||
val dif = VisionChange{
|
||||
group("top") {
|
||||
color(123)
|
||||
box(100,100,100)
|
||||
@ -36,7 +36,7 @@ class VisionUpdateTest {
|
||||
|
||||
@Test
|
||||
fun testVisionChangeSerialization(){
|
||||
val change = VisionChange(visionManager){
|
||||
val change = VisionChange{
|
||||
group("top") {
|
||||
color(123)
|
||||
box(100,100,100)
|
||||
|
Loading…
Reference in New Issue
Block a user