From bce61c0fb0885a30c0bc0ce860425128d70e2ef0 Mon Sep 17 00:00:00 2001 From: darksnake Date: Tue, 5 Dec 2023 18:50:26 +0300 Subject: [PATCH] Html input events --- build.gradle.kts | 2 +- .../kscience/visionforge/ControlVision.kt | 9 +++++- .../kscience/visionforge/VisionClient.kt | 2 +- .../kscience/visionforge/html/VisionOfHtml.kt | 21 +++++++++++++- .../visionforge/ElementVisionRenderer.kt | 22 +++++++++++---- .../kscience/visionforge/JsVisionClient.kt | 5 ++-- .../kscience/visionforge/inputRenderers.kt | 28 +++++++++++-------- 7 files changed, 67 insertions(+), 22 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b578696d..1a9de8d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val fxVersion by extra("11") allprojects { group = "space.kscience" - version = "0.3.0-dev-17" + version = "0.3.0-dev-18" } subprojects { diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt index e0d44930..20525b90 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MetaRepr import space.kscience.dataforge.meta.MutableMeta +import space.kscience.dataforge.meta.Value @Serializable @SerialName("control") @@ -60,4 +61,10 @@ public fun ClickControl.onClick(scope: CoroutineScope, block: suspend VisionClic @Serializable @SerialName("control.valueChange") -public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEvent() \ No newline at end of file +public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEvent() { + + public val value: Value? get() = meta.value +} + +public fun VisionValueChangeEvent(value: Value?): VisionValueChangeEvent = + VisionValueChangeEvent(Meta { this.value = value }) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt index 20974f0c..e337fea0 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -34,7 +34,7 @@ public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: St notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item)) } -public fun VisionClient.sendEvent(targetName: Name, payload: MetaRepr): Unit { +public fun VisionClient.sendMetaEvent(targetName: Name, payload: MetaRepr): Unit { context.launch { sendEvent(targetName, VisionMetaEvent(payload.toMeta())) } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt index 9cc223e5..5e597702 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt @@ -1,13 +1,19 @@ package space.kscience.visionforge.html +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.html.InputType import kotlinx.html.TagConsumer import kotlinx.html.stream.createHTML import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.asName import space.kscience.visionforge.AbstractVision +import space.kscience.visionforge.ControlVision +import space.kscience.visionforge.VisionControlEvent +import space.kscience.visionforge.VisionValueChangeEvent @Serializable @@ -53,10 +59,23 @@ public enum class InputFeedbackMode { public open class VisionOfHtmlInput( public val inputType: String, public val feedbackMode: InputFeedbackMode = InputFeedbackMode.ONCHANGE, -) : VisionOfHtml() { +) : VisionOfHtml(), ControlVision { public var value: Value? by properties.value() public var disabled: Boolean by properties.boolean { false } public var fieldName: String? by properties.string() + + @Transient + private val mutableControlEventFlow = MutableSharedFlow() + + override val controlEventFlow: SharedFlow + get() = mutableControlEventFlow + + override fun dispatchControlEvent(event: VisionControlEvent) { + if(event is VisionValueChangeEvent){ + this.value = event.value + } + mutableControlEventFlow.tryEmit(event) + } } @Suppress("UnusedReceiverParameter") diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/ElementVisionRenderer.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/ElementVisionRenderer.kt index 8e1254d9..f67172e5 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/ElementVisionRenderer.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/ElementVisionRenderer.kt @@ -34,7 +34,13 @@ public interface ElementVisionRenderer : Named { * Display the [vision] inside a given [element] replacing its current content. * @param meta additional parameters for rendering container */ - public fun render(element: Element, name: Name, vision: Vision, meta: Meta = Meta.EMPTY) + public fun render( + element: Element, + client: VisionClient, + name: Name, + vision: Vision, + meta: Meta = Meta.EMPTY, + ) public companion object { public const val TYPE: String = "elementVisionRenderer" @@ -49,7 +55,7 @@ public interface ElementVisionRenderer : Named { public class SingleTypeVisionRenderer( public val kClass: KClass, private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, - private val renderFunction: TagConsumer.(name: Name, vision: T, meta: Meta) -> Unit, + private val renderFunction: TagConsumer.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit, ) : ElementVisionRenderer { @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) @@ -60,15 +66,21 @@ public class SingleTypeVisionRenderer( override fun rateVision(vision: Vision): Int = if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING - override fun render(element: Element, name: Name, vision: Vision, meta: Meta) { + override fun render( + element: Element, + client: VisionClient, + name: Name, + vision: Vision, + meta: Meta, + ) { element.clear() element.append { - renderFunction(name, kClass.cast(vision), meta) + renderFunction(name, client, kClass.cast(vision), meta) } } } public inline fun ElementVisionRenderer( acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, - noinline renderFunction: TagConsumer.(name: Name, vision: T, meta: Meta) -> Unit, + noinline renderFunction: TagConsumer.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit, ): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction) diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/JsVisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/JsVisionClient.kt index c7ada5fe..c24ab493 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/JsVisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/JsVisionClient.kt @@ -94,8 +94,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient { private fun renderVision(element: Element, name: Name, vision: Vision, outputMeta: Meta) { vision.setAsRoot(visionManager) - val renderer = findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}") - renderer.render(element, name, vision, outputMeta) + val renderer: ElementVisionRenderer = + findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}") + renderer.render(element, this, name, vision, outputMeta) } private fun startVisionUpdate(element: Element, visionName: Name, vision: Vision, outputMeta: Meta) { diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt index e1410752..f17ac78f 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt @@ -40,7 +40,7 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) { } internal val htmlVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer { _, vision, _ -> + ElementVisionRenderer { _, _, vision, _ -> div {}.also { div -> div.subscribeToVision(vision) vision.useProperty(VisionOfPlainHtml::content) { @@ -50,12 +50,14 @@ internal val htmlVisionRenderer: ElementVisionRenderer = } internal val inputVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer(acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1) { _, vision, _ -> + ElementVisionRenderer( + acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1 + ) { name, client, vision, _ -> input { type = InputType.text }.also { htmlInputElement -> val onEvent: (Event) -> Unit = { - vision.value = htmlInputElement.value.asValue() + client.sendEvent(name, VisionValueChangeEvent(htmlInputElement.value.asValue())) } @@ -74,12 +76,12 @@ internal val inputVisionRenderer: ElementVisionRenderer = } internal val checkboxVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer { _, vision, _ -> + ElementVisionRenderer { name, client, vision, _ -> input { type = InputType.checkBox }.also { htmlInputElement -> val onEvent: (Event) -> Unit = { - vision.checked = htmlInputElement.checked + client.sendEvent(name, VisionValueChangeEvent(htmlInputElement.checked.asValue())) } @@ -98,12 +100,12 @@ internal val checkboxVisionRenderer: ElementVisionRenderer = } internal val textVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer { _, vision, _ -> + ElementVisionRenderer { name, client, vision, _ -> input { type = InputType.text }.also { htmlInputElement -> val onEvent: (Event) -> Unit = { - vision.text = htmlInputElement.value + client.sendEvent(name, VisionValueChangeEvent(htmlInputElement.value.asValue())) } @@ -122,13 +124,15 @@ internal val textVisionRenderer: ElementVisionRenderer = } internal val numberVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer { _, vision, _ -> + ElementVisionRenderer { name, client, vision, _ -> input { type = InputType.text }.also { htmlInputElement -> val onEvent: (Event) -> Unit = { - htmlInputElement.value.toDoubleOrNull()?.let { vision.number = it } + htmlInputElement.value.toDoubleOrNull()?.let { + client.sendEvent(name, VisionValueChangeEvent(it.asValue())) + } } when (vision.feedbackMode) { @@ -145,7 +149,7 @@ internal val numberVisionRenderer: ElementVisionRenderer = } internal val rangeVisionRenderer: ElementVisionRenderer = - ElementVisionRenderer { _, vision, _ -> + ElementVisionRenderer { name, client, vision, _ -> input { type = InputType.text min = vision.min.toString() @@ -154,7 +158,9 @@ internal val rangeVisionRenderer: ElementVisionRenderer = }.also { htmlInputElement -> val onEvent: (Event) -> Unit = { - htmlInputElement.value.toDoubleOrNull()?.let { vision.number = it } + htmlInputElement.value.toDoubleOrNull()?.let { + client.sendEvent(name, VisionValueChangeEvent(it.asValue())) + } } when (vision.feedbackMode) {