Html input events

This commit is contained in:
Alexander Nozik 2023-12-05 18:50:26 +03:00
parent 9fc6f1e34c
commit bce61c0fb0
7 changed files with 67 additions and 22 deletions

View File

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

View File

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

View File

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

View File

@ -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<VisionControlEvent>()
override val controlEventFlow: SharedFlow<VisionControlEvent>
get() = mutableControlEventFlow
override fun dispatchControlEvent(event: VisionControlEvent) {
if(event is VisionValueChangeEvent){
this.value = event.value
}
mutableControlEventFlow.tryEmit(event)
}
}
@Suppress("UnusedReceiverParameter")

View File

@ -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<T : Vision>(
public val kClass: KClass<T>,
private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
) : ElementVisionRenderer {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
@ -60,15 +66,21 @@ public class SingleTypeVisionRenderer<T : Vision>(
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 <reified T : Vision> ElementVisionRenderer(
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
noinline renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
noinline renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction)

View File

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

View File

@ -40,7 +40,7 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
}
internal val htmlVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfPlainHtml> { _, vision, _ ->
ElementVisionRenderer<VisionOfPlainHtml> { _, _, vision, _ ->
div {}.also { div ->
div.subscribeToVision(vision)
vision.useProperty(VisionOfPlainHtml::content) {
@ -50,12 +50,14 @@ internal val htmlVisionRenderer: ElementVisionRenderer =
}
internal val inputVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfHtmlInput>(acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1) { _, vision, _ ->
ElementVisionRenderer<VisionOfHtmlInput>(
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<VisionOfCheckbox> { _, vision, _ ->
ElementVisionRenderer<VisionOfCheckbox> { 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<VisionOfTextField> { _, vision, _ ->
ElementVisionRenderer<VisionOfTextField> { 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<VisionOfNumberField> { _, vision, _ ->
ElementVisionRenderer<VisionOfNumberField> { 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<VisionOfRangeField> { _, vision, _ ->
ElementVisionRenderer<VisionOfRangeField> { 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) {