Forms implemented

This commit is contained in:
Alexander Nozik 2021-12-31 13:59:27 +03:00
parent 104e8f8f6f
commit 6b8e166978
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
27 changed files with 445 additions and 151 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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