Forms implemented
This commit is contained in:
parent
104e8f8f6f
commit
6b8e166978
@ -21,7 +21,7 @@ import space.kscience.visionforge.gdml.markLayers
|
|||||||
import space.kscience.visionforge.gdml.toVision
|
import space.kscience.visionforge.gdml.toVision
|
||||||
import space.kscience.visionforge.ring.ThreeCanvasWithControls
|
import space.kscience.visionforge.ring.ThreeCanvasWithControls
|
||||||
import space.kscience.visionforge.ring.tab
|
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.Solid
|
||||||
import space.kscience.visionforge.solid.Solids
|
import space.kscience.visionforge.solid.Solids
|
||||||
import styled.css
|
import styled.css
|
||||||
@ -50,7 +50,7 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
|
|||||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||||
val gdml = Gdml.decodeFromString(data)
|
val gdml = Gdml.decodeFromString(data)
|
||||||
gdml.toVision().apply {
|
gdml.toVision().apply {
|
||||||
root(visionManager)
|
setAsRoot(visionManager)
|
||||||
console.info("Marking layers for file $name")
|
console.info("Marking layers for file $name")
|
||||||
markLayers()
|
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 ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
|
||||||
import space.kscience.visionforge.VisionManager
|
import space.kscience.visionforge.VisionManager
|
||||||
import space.kscience.visionforge.removeAll
|
import space.kscience.visionforge.removeAll
|
||||||
import space.kscience.visionforge.root
|
import space.kscience.visionforge.setAsRoot
|
||||||
import space.kscience.visionforge.setProperty
|
import space.kscience.visionforge.setProperty
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
@ -37,7 +37,7 @@ class Model(val manager: VisionManager) {
|
|||||||
var tracks: SolidGroup
|
var tracks: SolidGroup
|
||||||
|
|
||||||
val root: SolidGroup = SolidGroup().apply {
|
val root: SolidGroup = SolidGroup().apply {
|
||||||
root(this@Model.manager)
|
setAsRoot(this@Model.manager)
|
||||||
material {
|
material {
|
||||||
wireframe
|
wireframe
|
||||||
color("darkgreen")
|
color("darkgreen")
|
||||||
|
@ -76,9 +76,6 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
|
|||||||
attrs {
|
attrs {
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
context.launch {
|
context.launch {
|
||||||
// val event = props.connection.get<Event>(
|
|
||||||
// "http://localhost:8080/event"
|
|
||||||
// )
|
|
||||||
val event = window.fetch(
|
val event = window.fetch(
|
||||||
"http://localhost:8080/event",
|
"http://localhost:8080/event",
|
||||||
RequestInit("GET")
|
RequestInit("GET")
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
kotlin("multiplatform")
|
||||||
|
kotlin("jupyter.api")
|
||||||
|
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
mavenCentral()
|
||||||
maven("https://kotlin.bintray.com/kotlinx")
|
maven("https://jitpack.io")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("https://repo.kotlin.link")
|
||||||
maven("https://dl.bintray.com/mipt-npm/dataforge")
|
|
||||||
maven("https://dl.bintray.com/mipt-npm/kscience")
|
|
||||||
maven("https://dl.bintray.com/mipt-npm/dev")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -28,6 +27,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jvm {
|
jvm {
|
||||||
|
withJava()
|
||||||
compilations.all {
|
compilations.all {
|
||||||
kotlinOptions.jvmTarget = "11"
|
kotlinOptions.jvmTarget = "11"
|
||||||
}
|
}
|
||||||
@ -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.stream.createHTML
|
||||||
import kotlinx.html.unsafe
|
|
||||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
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.context.Context
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.gdml.Gdml
|
import space.kscience.gdml.Gdml
|
||||||
@ -15,7 +12,7 @@ import space.kscience.visionforge.Vision
|
|||||||
import space.kscience.visionforge.gdml.toVision
|
import space.kscience.visionforge.gdml.toVision
|
||||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||||
import space.kscience.visionforge.html.Page
|
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.PlotlyPlugin
|
||||||
import space.kscience.visionforge.plotly.asVision
|
import space.kscience.visionforge.plotly.asVision
|
||||||
import space.kscience.visionforge.solid.Solids
|
import space.kscience.visionforge.solid.Solids
|
||||||
@ -29,27 +26,19 @@ public class VisionForgePlayGroundForJupyter : JupyterIntegration() {
|
|||||||
plugin(PlotlyPlugin)
|
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 var counter = 0
|
||||||
|
|
||||||
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().div {
|
private fun produceHtmlVisionString(fragment: HtmlVisionFragment) = createHTML().apply {
|
||||||
val id = "visionforge.vision[${counter++}]"
|
embedAndRenderVisionFragment(context.visionManager, counter++, fragment = fragment)
|
||||||
div {
|
}.finalize()
|
||||||
this.id = id
|
|
||||||
embedVisionFragment(context.visionManager, fragment = fragment)
|
|
||||||
}
|
|
||||||
script {
|
|
||||||
type = "text/javascript"
|
|
||||||
unsafe { +"window.renderAllVisionsById(\"$id\");" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Builder.onLoaded() {
|
override fun Builder.onLoaded() {
|
||||||
resource(jsResource)
|
|
||||||
|
resources {
|
||||||
|
js("VisionForge"){
|
||||||
|
classPath("js/visionforge-playground.js")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
import(
|
import(
|
||||||
"space.kscience.gdml.*",
|
"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 {
|
}.toVision {
|
||||||
configure { parent, solid, material ->
|
configure { _, solid, _ ->
|
||||||
//disable visibility for the world box
|
//disable visibility for the world box
|
||||||
if(solid.name == "world"){
|
if(solid.name == "world"){
|
||||||
visible = false
|
visible = false
|
@ -6,9 +6,7 @@ import kotlinx.serialization.json.Json
|
|||||||
import space.kscience.visionforge.solid.SolidGroup
|
import space.kscience.visionforge.solid.SolidGroup
|
||||||
import space.kscience.visionforge.solid.Solids
|
import space.kscience.visionforge.solid.Solids
|
||||||
|
|
||||||
@ExperimentalSerializationApi
|
private val json = Json {
|
||||||
fun main() {
|
|
||||||
val schema = Json {
|
|
||||||
serializersModule = Solids.serializersModuleForSolids
|
serializersModule = Solids.serializersModuleForSolids
|
||||||
prettyPrintIndent = " "
|
prettyPrintIndent = " "
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
@ -16,6 +14,10 @@ fun main() {
|
|||||||
isLenient = true
|
isLenient = true
|
||||||
coerceInputValues = true
|
coerceInputValues = true
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
}.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
|
}
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
fun main() {
|
||||||
|
val schema = json.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false)
|
||||||
println(schema)
|
println(schema)
|
||||||
}
|
}
|
@ -1,13 +1,11 @@
|
|||||||
package ru.mipt.npm.sat
|
package ru.mipt.npm.sat
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.set
|
import space.kscience.dataforge.meta.set
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
import space.kscience.visionforge.style
|
import space.kscience.visionforge.style
|
||||||
import space.kscience.visionforge.useStyle
|
import space.kscience.visionforge.useStyle
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
internal fun visionOfSatellite(
|
internal fun visionOfSatellite(
|
||||||
layers: Int = 10,
|
layers: Int = 10,
|
||||||
layerHeight: Number = 10,
|
layerHeight: Number = 10,
|
||||||
|
@ -6,7 +6,6 @@ import io.ktor.server.engine.embeddedServer
|
|||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import org.jetbrains.kotlinx.jupyter.api.HTML
|
import org.jetbrains.kotlinx.jupyter.api.HTML
|
||||||
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
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.Context
|
||||||
import space.kscience.dataforge.context.ContextAware
|
import space.kscience.dataforge.context.ContextAware
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
@ -53,12 +52,6 @@ public abstract class JupyterPluginBase(
|
|||||||
server = null
|
server = null
|
||||||
}
|
}
|
||||||
|
|
||||||
resources {
|
|
||||||
js("three") {
|
|
||||||
classPath("js/gdml-jupyter.js")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import(
|
import(
|
||||||
"kotlinx.html.*",
|
"kotlinx.html.*",
|
||||||
"space.kscience.visionforge.html.Page",
|
"space.kscience.visionforge.html.Page",
|
||||||
|
@ -59,7 +59,6 @@ include(
|
|||||||
":demo:muon-monitor",
|
":demo:muon-monitor",
|
||||||
":demo:sat-demo",
|
":demo:sat-demo",
|
||||||
":demo:playground",
|
":demo:playground",
|
||||||
":demo:jupyter-playground",
|
|
||||||
":demo:plotly-fx",
|
":demo:plotly-fx",
|
||||||
":demo:js-playground",
|
":demo:js-playground",
|
||||||
":jupyter:visionforge-jupyter-base",
|
":jupyter:visionforge-jupyter-base",
|
||||||
|
@ -83,7 +83,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
|
|||||||
useEffect {
|
useEffect {
|
||||||
props.context.launch {
|
props.context.launch {
|
||||||
solid = props.builderOfSolid.await().also {
|
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 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(
|
override fun getPropertyValue(
|
||||||
name: Name,
|
name: Name,
|
||||||
|
@ -14,6 +14,17 @@ import space.kscience.dataforge.values.Null
|
|||||||
import kotlin.jvm.Synchronized
|
import kotlin.jvm.Synchronized
|
||||||
import kotlin.time.Duration
|
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]
|
* 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
|
* Isolate collected changes by creating detached copies of given visions
|
||||||
*/
|
*/
|
||||||
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
public fun deepCopy(): VisionChange = VisionChange(
|
||||||
reset,
|
reset,
|
||||||
vision?.isolate(manager),
|
vision?.deepCopy(),
|
||||||
if (propertyChange.isEmpty()) null else propertyChange.seal(),
|
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 delete flag showing that this vision child should be removed
|
||||||
* @param vision a new value for vision content
|
* @param vision a new value for vision content
|
||||||
@ -78,8 +83,8 @@ public data class VisionChange(
|
|||||||
public val children: Map<Name, VisionChange>? = null,
|
public val children: Map<Name, VisionChange>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
|
public inline fun VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
|
||||||
VisionChangeBuilder().apply(block).isolate(manager)
|
VisionChangeBuilder().apply(block).deepCopy()
|
||||||
|
|
||||||
|
|
||||||
@OptIn(DFExperimental::class)
|
@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(
|
public fun Vision.flowChanges(
|
||||||
manager: VisionManager,
|
|
||||||
collectionDuration: Duration,
|
collectionDuration: Duration,
|
||||||
): Flow<VisionChange> = flow {
|
): Flow<VisionChange> = flow {
|
||||||
|
|
||||||
@ -126,7 +132,7 @@ public fun Vision.flowChanges(
|
|||||||
collectChange(Name.EMPTY, this@flowChanges) { collector }
|
collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||||
|
|
||||||
//Send initial vision state
|
//Send initial vision state
|
||||||
val initialChange = VisionChange(vision = isolate(manager))
|
val initialChange = VisionChange(vision = deepCopy())
|
||||||
emit(initialChange)
|
emit(initialChange)
|
||||||
|
|
||||||
while (currentCoroutineContext().isActive) {
|
while (currentCoroutineContext().isActive) {
|
||||||
@ -135,7 +141,7 @@ public fun Vision.flowChanges(
|
|||||||
//Propagate updates only if something is changed
|
//Propagate updates only if something is changed
|
||||||
if (!collector.isEmpty()) {
|
if (!collector.isEmpty()) {
|
||||||
//emit changes
|
//emit changes
|
||||||
emit(collector.isolate(manager))
|
emit(collector.deepCopy())
|
||||||
//Reset the collector
|
//Reset the collector
|
||||||
collector = VisionChangeBuilder()
|
collector = VisionChangeBuilder()
|
||||||
}
|
}
|
||||||
|
@ -160,8 +160,9 @@ public open class VisionGroupBase(
|
|||||||
internal class RootVisionGroup(override val manager: VisionManager) : 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)
|
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.toJson
|
||||||
import space.kscience.dataforge.meta.toMeta
|
import space.kscience.dataforge.meta.toMeta
|
||||||
import space.kscience.dataforge.names.Name
|
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
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||||
@ -66,6 +70,10 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
default { VisionBase.serializer() }
|
default { VisionBase.serializer() }
|
||||||
subclass(VisionBase.serializer())
|
subclass(VisionBase.serializer())
|
||||||
subclass(VisionGroupBase.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.dataforge.names.asName
|
||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.VisionManager
|
import space.kscience.visionforge.VisionManager
|
||||||
import space.kscience.visionforge.root
|
import space.kscience.visionforge.setAsRoot
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@DslMarker
|
@DslMarker
|
||||||
@ -81,11 +81,11 @@ public abstract class VisionTagConsumer<R>(
|
|||||||
@OptIn(DFExperimental::class)
|
@OptIn(DFExperimental::class)
|
||||||
public inline fun <T> TagConsumer<T>.vision(
|
public inline fun <T> TagConsumer<T>.vision(
|
||||||
name: Name,
|
name: Name,
|
||||||
visionProvider: VisionOutput.() -> Vision,
|
@OptIn(DFExperimental::class) visionProvider: VisionOutput.() -> Vision,
|
||||||
): T {
|
): T {
|
||||||
val output = VisionOutput(manager)
|
val output = VisionOutput(manager)
|
||||||
val vision = output.visionProvider()
|
val vision = output.visionProvider()
|
||||||
vision.root(manager)
|
vision.setAsRoot(manager)
|
||||||
return vision(name, vision, output.meta)
|
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.context.*
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.MetaSerializer
|
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.RENDER_FUNCTION_NAME
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer
|
import space.kscience.visionforge.html.VisionTagConsumer
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
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.reflect.KClass
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
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() {
|
public class VisionClient : AbstractPlugin() {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
private val visionManager: VisionManager by require(VisionManager)
|
private val visionManager: VisionManager by require(VisionManager)
|
||||||
@ -102,10 +108,12 @@ public class VisionClient : AbstractPlugin() {
|
|||||||
//Backward change propagation
|
//Backward change propagation
|
||||||
var feedbackJob: Job? = null
|
var feedbackJob: Job? = null
|
||||||
|
|
||||||
|
//Feedback changes aggregation time in milliseconds
|
||||||
|
val feedbackAggregationTime = meta["aggregationTime"]?.int ?: 300
|
||||||
|
|
||||||
onopen = {
|
onopen = {
|
||||||
feedbackJob = vision.flowChanges(
|
feedbackJob = vision.flowChanges(
|
||||||
visionManager,
|
feedbackAggregationTime.milliseconds
|
||||||
300.milliseconds
|
|
||||||
).onEach { change ->
|
).onEach { change ->
|
||||||
send(visionManager.encodeToString(change))
|
send(visionManager.encodeToString(change))
|
||||||
}.launchIn(visionManager.context)
|
}.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> {
|
public companion object : PluginFactory<VisionClient> {
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): VisionClient = 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 {
|
try {
|
||||||
withContext(visionManager.context.coroutineContext) {
|
withContext(visionManager.context.coroutineContext) {
|
||||||
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
|
vision.flowChanges(updateInterval.milliseconds).collect { update ->
|
||||||
val json = visionManager.jsonFormat.encodeToString(
|
val json = visionManager.jsonFormat.encodeToString(
|
||||||
VisionChange.serializer(),
|
VisionChange.serializer(),
|
||||||
update
|
update
|
||||||
|
@ -20,7 +20,7 @@ class VisionUpdateTest {
|
|||||||
val targetVision = SolidGroup {
|
val targetVision = SolidGroup {
|
||||||
box(200,200,200, name = "origin")
|
box(200,200,200, name = "origin")
|
||||||
}
|
}
|
||||||
val dif = VisionChange(visionManager){
|
val dif = VisionChange{
|
||||||
group("top") {
|
group("top") {
|
||||||
color(123)
|
color(123)
|
||||||
box(100,100,100)
|
box(100,100,100)
|
||||||
@ -36,7 +36,7 @@ class VisionUpdateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testVisionChangeSerialization(){
|
fun testVisionChangeSerialization(){
|
||||||
val change = VisionChange(visionManager){
|
val change = VisionChange{
|
||||||
group("top") {
|
group("top") {
|
||||||
color(123)
|
color(123)
|
||||||
box(100,100,100)
|
box(100,100,100)
|
||||||
|
Loading…
Reference in New Issue
Block a user