forked from kscience/visionforge
Change controls API
This commit is contained in:
parent
80284a99ef
commit
469655092e
@ -54,9 +54,6 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"outputs_hidden": false
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
@ -83,9 +80,6 @@
|
||||
"language": "kotlin",
|
||||
"name": "kotlin"
|
||||
},
|
||||
"ktnbPluginMetadata": {
|
||||
"isAddProjectLibrariesToClasspath": false
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": "text/x-kotlin",
|
||||
"file_extension": ".kt",
|
||||
@ -94,6 +88,9 @@
|
||||
"nbconvert_exporter": "",
|
||||
"pygments_lexer": "kotlin",
|
||||
"version": "1.8.20"
|
||||
},
|
||||
"ktnbPluginMetadata": {
|
||||
"projectLibraries": []
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
45
demo/playground/notebooks/controls.ipynb
Normal file
45
demo/playground/notebooks/controls.ipynb
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"USE(JupyterCommonIntegration())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"outputs": [],
|
||||
"source": [],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Kotlin",
|
||||
"language": "kotlin",
|
||||
"name": "kotlin"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "kotlin",
|
||||
"version": "1.9.0",
|
||||
"mimetype": "text/x-kotlin",
|
||||
"file_extension": ".kt",
|
||||
"pygments_lexer": "kotlin",
|
||||
"codemirror_mode": "text/x-kotlin",
|
||||
"nbconvert_exporter": ""
|
||||
},
|
||||
"ktnbPluginMetadata": {
|
||||
"projectDependencies": true
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
@ -25,10 +25,7 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"jupyter": {
|
||||
"outputs_hidden": false
|
||||
}
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@ -84,7 +81,7 @@
|
||||
"version": "1.8.0-dev-3517"
|
||||
},
|
||||
"ktnbPluginMetadata": {
|
||||
"isAddProjectLibrariesToClasspath": false
|
||||
"projectLibraries": []
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
@ -75,7 +75,7 @@ fun main() {
|
||||
|
||||
server.openInBrowser()
|
||||
|
||||
while (readln() != "exit") {
|
||||
while (readlnOrNull() != "exit") {
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@ -21,7 +21,7 @@ public abstract class VisionControlEvent : VisionEvent, MetaRepr {
|
||||
}
|
||||
|
||||
public interface ControlVision : Vision {
|
||||
public val controlEventFlow: Flow<VisionControlEvent>
|
||||
public val controlEventFlow: SharedFlow<VisionControlEvent>
|
||||
|
||||
public fun dispatchControlEvent(event: VisionControlEvent)
|
||||
|
||||
@ -32,21 +32,32 @@ public interface ControlVision : Vision {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param payload The optional payload associated with the click event.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("control.click")
|
||||
public class VisionClickEvent(override val meta: Meta) : VisionControlEvent()
|
||||
public class VisionClickEvent(public val payload: Meta = Meta.EMPTY) : VisionControlEvent() {
|
||||
override val meta: Meta get() = Meta { ::payload.name put payload }
|
||||
}
|
||||
|
||||
|
||||
public interface ClickControl : ControlVision {
|
||||
/**
|
||||
* Create and dispatch a click event
|
||||
*/
|
||||
public fun click(builder: MutableMeta.() -> Unit = {}) {
|
||||
dispatchControlEvent(VisionClickEvent(Meta(builder)))
|
||||
}
|
||||
}
|
||||
|
||||
public fun onClick(scope: CoroutineScope, block: suspend VisionClickEvent.() -> Unit): Job {
|
||||
return controlEventFlow.filterIsInstance<VisionClickEvent>().onEach(block).launchIn(scope)
|
||||
}
|
||||
/**
|
||||
* Register listener
|
||||
*/
|
||||
public fun ClickControl.onClick(scope: CoroutineScope, block: suspend VisionClickEvent.() -> Unit): Job =
|
||||
controlEventFlow.filterIsInstance<VisionClickEvent>().onEach(block).launchIn(scope)
|
||||
|
||||
public companion object {
|
||||
|
||||
}
|
||||
}
|
||||
@Serializable
|
||||
@SerialName("control.valueChange")
|
||||
public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEvent()
|
@ -0,0 +1,54 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.AbstractVision
|
||||
|
||||
|
||||
@Serializable
|
||||
public abstract class VisionOfHtml: AbstractVision(){
|
||||
public var classes: List<String> by properties.stringList(*emptyArray())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.input")
|
||||
public open class VisionOfHtmlInput(
|
||||
public val inputType: String,
|
||||
) : VisionOfHtml() {
|
||||
public var value : Value? by properties.value()
|
||||
public var disabled: Boolean by properties.boolean { false }
|
||||
public var fieldName: String? by properties.string()
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.text")
|
||||
public class VisionOfTextField : VisionOfHtmlInput(InputType.text.realValue) {
|
||||
public var text: String? by properties.string(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.checkbox")
|
||||
public class VisionOfCheckbox : VisionOfHtmlInput(InputType.checkBox.realValue) {
|
||||
public var checked: Boolean? by properties.boolean(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.number")
|
||||
public class VisionOfNumberField : VisionOfHtmlInput(InputType.number.realValue) {
|
||||
public var number: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.range")
|
||||
public class VisionOfRangeField(
|
||||
public val min: Double,
|
||||
public val max: Double,
|
||||
public val step: Double = 1.0,
|
||||
) : VisionOfHtmlInput(InputType.range.realValue) {
|
||||
public var number: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
@ -9,12 +9,15 @@ import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.node
|
||||
|
||||
/**
|
||||
* @param formId an id of the element in rendered DOM, this form is bound to
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("html.form")
|
||||
public class VisionOfHtmlForm(
|
||||
public val formId: String,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var values: Meta? by mutableProperties.node()
|
||||
) : VisionOfHtml() {
|
||||
public var values: Meta? by properties.node()
|
||||
}
|
||||
|
||||
public fun <R> TagConsumer<R>.bindForm(
|
||||
|
@ -1,58 +0,0 @@
|
||||
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.dataforge.names.Name
|
||||
import space.kscience.visionforge.AbstractVision
|
||||
import space.kscience.visionforge.Vision
|
||||
|
||||
//TODO replace by something
|
||||
internal val Vision.mutableProperties get() = properties.getMeta(Name.EMPTY, false, false)
|
||||
|
||||
@Serializable
|
||||
public abstract class VisionOfHtmlInput : AbstractVision() {
|
||||
public var disabled: Boolean by mutableProperties.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 mutableProperties.string()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.checkbox")
|
||||
public class VisionOfCheckbox(
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var checked: Boolean? by mutableProperties.boolean()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.number")
|
||||
public class VisionOfNumberField(
|
||||
public val label: String? = null,
|
||||
public val name: String? = null,
|
||||
) : VisionOfHtmlInput() {
|
||||
public var value: Number? by mutableProperties.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 mutableProperties.number()
|
||||
}
|
||||
|
@ -252,9 +252,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) {
|
||||
listOf(
|
||||
numberVisionRenderer(this),
|
||||
textVisionRenderer(this),
|
||||
formVisionRenderer(this)
|
||||
numberVisionRenderer(),
|
||||
textVisionRenderer(),
|
||||
formVisionRenderer()
|
||||
).associateByName()
|
||||
} else super<AbstractPlugin>.content(target)
|
||||
|
||||
|
@ -3,66 +3,63 @@ 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.HTMLElement
|
||||
import org.w3c.dom.HTMLFormElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.xhr.FormData
|
||||
import space.kscience.dataforge.context.debug
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.DynamicMeta
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.toMap
|
||||
import space.kscience.dataforge.meta.valueSequence
|
||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
||||
import space.kscience.visionforge.html.VisionOfNumberField
|
||||
import space.kscience.visionforge.html.VisionOfTextField
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.visionforge.html.*
|
||||
|
||||
internal fun textVisionRenderer(
|
||||
client: JsVisionClient,
|
||||
): ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { name, vision, _ ->
|
||||
val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]"
|
||||
vision.label?.let {
|
||||
label {
|
||||
htmlFor = fieldName
|
||||
+it
|
||||
}
|
||||
}
|
||||
input {
|
||||
type = InputType.text
|
||||
this.name = fieldName
|
||||
vision.useProperty(VisionOfTextField::text) {
|
||||
value = it ?: ""
|
||||
}
|
||||
onChangeFunction = {
|
||||
client.notifyPropertyChanged(name, VisionOfTextField::text.name, value)
|
||||
}
|
||||
|
||||
private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
||||
vision.useProperty(VisionOfHtml::classes) {
|
||||
classList.value = classes.joinToString(separator = " ")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun numberVisionRenderer(
|
||||
client: JsVisionClient,
|
||||
): ElementVisionRenderer = ElementVisionRenderer<VisionOfNumberField> { name, vision, _ ->
|
||||
val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]"
|
||||
vision.label?.let {
|
||||
label {
|
||||
htmlFor = fieldName
|
||||
+it
|
||||
}
|
||||
}
|
||||
input {
|
||||
type = InputType.text
|
||||
this.name = fieldName
|
||||
vision.useProperty(VisionOfNumberField::value) {
|
||||
value = it?.toDouble() ?: 0.0
|
||||
}
|
||||
onChangeFunction = {
|
||||
client.notifyPropertyChanged(name, VisionOfNumberField::value.name, value)
|
||||
}
|
||||
|
||||
private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
||||
subscribeToVision(inputVision)
|
||||
inputVision.useProperty(VisionOfHtmlInput::disabled) {
|
||||
disabled = it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun JsVisionClient.textVisionRenderer(): ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfTextField> { visionName, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
onChangeFunction = {
|
||||
notifyPropertyChanged(visionName, VisionOfTextField::text.name, value)
|
||||
}
|
||||
}.apply {
|
||||
subscribeToInput(vision)
|
||||
vision.useProperty(VisionOfTextField::text) {
|
||||
value = (it ?: "").asValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun JsVisionClient.numberVisionRenderer(): ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfNumberField> { visionName, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
onChangeFunction = {
|
||||
notifyPropertyChanged(visionName, VisionOfNumberField::value.name, value)
|
||||
}
|
||||
}.apply {
|
||||
subscribeToInput(vision)
|
||||
vision.useProperty(VisionOfNumberField::value) {
|
||||
value = (it?.double ?: 0.0).asValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun FormData.toMeta(): Meta {
|
||||
@Suppress("UNUSED_VARIABLE") val formData = this
|
||||
//val res = js("Object.fromEntries(formData);")
|
||||
@ -86,28 +83,29 @@ internal fun FormData.toMeta(): Meta {
|
||||
return DynamicMeta(`object`)
|
||||
}
|
||||
|
||||
internal fun formVisionRenderer(
|
||||
client: JsVisionClient,
|
||||
): ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlForm> { name, vision, _ ->
|
||||
internal fun JsVisionClient.formVisionRenderer(): ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfHtmlForm> { visionName, vision, _ ->
|
||||
|
||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||
?: error("An element with id = '${vision.formId} is not a form")
|
||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||
?: error("An element with id = '${vision.formId} is not a form")
|
||||
|
||||
client.logger.debug{"Adding hooks to form with id = '$vision.formId'"}
|
||||
form.subscribeToVision(vision)
|
||||
|
||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||
client.logger.debug{"Updating form '${vision.formId}' with values $values"}
|
||||
val inputs = form.getElementsByTagName("input")
|
||||
values?.valueSequence()?.forEach { (token, value) ->
|
||||
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
||||
logger.debug { "Adding hooks to form with id = '$vision.formId'" }
|
||||
|
||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||
logger.debug { "Updating form '${vision.formId}' with 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()
|
||||
client.notifyPropertyChanged(name, VisionOfHtmlForm::values.name, formData)
|
||||
console.info("Sent: ${formData.toMap()}")
|
||||
false
|
||||
}
|
||||
}
|
||||
form.onsubmit = { event ->
|
||||
event.preventDefault()
|
||||
val formData = FormData(form).toMeta()
|
||||
notifyPropertyChanged(visionName, VisionOfHtmlForm::values.name, formData)
|
||||
console.info("Sent: ${formData.toMap()}")
|
||||
false
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import org.jetbrains.kotlinx.jupyter.api.declare
|
||||
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.*
|
||||
@ -17,7 +16,6 @@ import kotlin.random.nextUInt
|
||||
/**
|
||||
* A base class for different Jupyter VF integrations
|
||||
*/
|
||||
@DFExperimental
|
||||
public abstract class VisionForgeIntegration(
|
||||
public val visionManager: VisionManager,
|
||||
) : JupyterIntegration(), ContextAware {
|
||||
|
@ -1,14 +1,14 @@
|
||||
package space.kscience.visionforge.jupyter
|
||||
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.p
|
||||
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
|
||||
import space.kscience.plotly.Plot
|
||||
import space.kscience.plotly.PlotlyPage
|
||||
import space.kscience.plotly.StaticPlotlyRenderer
|
||||
import space.kscience.tables.*
|
||||
import space.kscience.tables.Table
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
@ -21,7 +21,6 @@ import space.kscience.visionforge.tables.toVision
|
||||
import space.kscience.visionforge.visionManager
|
||||
|
||||
|
||||
@DFExperimental
|
||||
public class JupyterCommonIntegration : VisionForgeIntegration(CONTEXT.visionManager) {
|
||||
|
||||
override fun Builder.afterLoaded(vf: VisionForge) {
|
||||
|
Loading…
Reference in New Issue
Block a user