From fd8f693151381f1bab1f2e8d27215cfec21c1264 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 3 Dec 2022 14:51:32 +0300 Subject: [PATCH] Refactor server API --- .../visionforge/ElementVisionRenderer.kt | 10 ++-- .../kscience/visionforge/VisionClient.kt | 49 ++++++++++++------- .../kscience/visionforge/inputRenderers.kt | 36 ++++++++------ .../visionforge/markup/MarkupPlugin.kt | 3 +- 4 files changed, 61 insertions(+), 37 deletions(-) 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 bf9f00df..75ec785a 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,7 @@ 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, vision: Vision, meta: Meta = Meta.EMPTY) + public fun render(element: Element, name: Name, vision: Vision, meta: Meta = Meta.EMPTY) public companion object { public const val TYPE: String = "elementVisionRenderer" @@ -49,7 +49,7 @@ public interface ElementVisionRenderer : Named { public class SingleTypeVisionRenderer( public val kClass: KClass, private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, - private val renderFunction: TagConsumer.(vision: T, meta: Meta) -> Unit, + private val renderFunction: TagConsumer.(name: Name, vision: T, meta: Meta) -> Unit, ) : ElementVisionRenderer { @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) @@ -60,15 +60,15 @@ public class SingleTypeVisionRenderer( override fun rateVision(vision: Vision): Int = if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING - override fun render(element: Element, vision: Vision, meta: Meta) { + override fun render(element: Element, name: Name, vision: Vision, meta: Meta) { element.clear() element.append { - renderFunction(kClass.cast(vision), meta) + renderFunction(name, kClass.cast(vision), meta) } } } public inline fun ElementVisionRenderer( acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, - noinline renderFunction: TagConsumer.(vision: T, meta: Meta) -> Unit, + noinline renderFunction: TagConsumer.(name: Name, vision: T, meta: Meta) -> Unit, ): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction) diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt index 81655335..efb8a9fe 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -13,6 +13,7 @@ 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.dataforge.names.parseAsName import space.kscience.visionforge.html.VisionTagConsumer import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE @@ -67,17 +68,29 @@ public class VisionClient : AbstractPlugin() { changeCollector.propertyChanged(visionName, propertyName, item) } + public fun visionPropertyChanged(visionName: Name, propertyName: Name, item: Boolean) { + visionPropertyChanged(visionName, propertyName, Meta(item)) + } + + public fun visionPropertyChanged(visionName: Name, propertyName: Name, item: String) { + visionPropertyChanged(visionName, propertyName, Meta(item)) + } + + public fun visionPropertyChanged(visionName: Name, propertyName: Name, item: Number) { + visionPropertyChanged(visionName, propertyName, Meta(item)) + } + public fun visionChanged(name: Name?, child: Vision?) { changeCollector.setChild(name, child) } - private fun renderVision(element: Element, vision: Vision, outputMeta: Meta) { + 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, vision, outputMeta) + renderer.render(element, name, vision, outputMeta) } - private fun updateVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) { + private fun updateVision(element: Element, name: Name, vision: Vision?, outputMeta: Meta) { element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) { val endpoint = resolveEndpoint(element) @@ -89,7 +102,7 @@ public class VisionClient : AbstractPlugin() { URL(attr.value) }.apply { protocol = "ws" - searchParams.append("name", name) + searchParams.append("name", name.toString()) } logger.info { "Updating vision data from $wsUrl" } @@ -106,7 +119,7 @@ public class VisionClient : AbstractPlugin() { // If change contains root vision replacement, do it change.vision?.let { vision -> - renderVision(element, vision, outputMeta) + renderVision(element, name, vision, outputMeta) } logger.debug { "Got update $change for output with name $name" } @@ -152,7 +165,7 @@ public class VisionClient : AbstractPlugin() { */ public fun renderVisionIn(element: Element) { if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element") - val name = resolveName(element) ?: error("The element is not a vision output") + val name = resolveName(element)?.parseAsName() ?: error("The element is not a vision output") if (element.attributes[OUTPUT_RENDERED]?.value == "true") { logger.info { "VF output in element $element is already rendered" } @@ -179,7 +192,7 @@ public class VisionClient : AbstractPlugin() { } else { URL(attr.value) }.apply { - searchParams.append("name", name) + searchParams.append("name", name.toString()) } logger.info { "Fetching vision data from $fetchUrl" } @@ -187,8 +200,8 @@ public class VisionClient : AbstractPlugin() { if (response.ok) { response.text().then { text -> val vision = visionManager.decodeFromString(text) - renderVision(element, vision, outputMeta) - updateVision(name, element, vision, outputMeta) + renderVision(element, name, vision, outputMeta) + updateVision(element, name, vision, outputMeta) } } else { logger.error { "Failed to fetch initial vision state from $fetchUrl" } @@ -203,13 +216,13 @@ public class VisionClient : AbstractPlugin() { visionManager.decodeFromString(it) } logger.info { "Found embedded vision for output with name $name" } - renderVision(element, embeddedVision, outputMeta) - updateVision(name, element, embeddedVision, outputMeta) + renderVision(element, name, embeddedVision, outputMeta) + updateVision(element, name, embeddedVision, outputMeta) } //Try to load vision via websocket element.attributes[OUTPUT_CONNECT_ATTRIBUTE] != null -> { - updateVision(name, element, null, outputMeta) + updateVision(element, name, null, outputMeta) } else -> error("No embedded vision data / fetch url for $name") @@ -217,11 +230,13 @@ public class VisionClient : AbstractPlugin() { element.setAttribute(OUTPUT_RENDERED, "true") } - override fun content(target: String): Map = if (target == ElementVisionRenderer.TYPE) mapOf( - numberVisionRenderer.name to numberVisionRenderer, - textVisionRenderer.name to textVisionRenderer, - formVisionRenderer.name to formVisionRenderer - ) else super.content(target) + override fun content(target: String): Map = if (target == ElementVisionRenderer.TYPE) { + listOf( + numberVisionRenderer(this), + textVisionRenderer(this), + formVisionRenderer(this) + ).toMap() + } else super.content(target) public companion object : PluginFactory { override fun build(context: Context, meta: Meta): VisionClient = VisionClient() 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 c6d87b19..86324d89 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt @@ -16,42 +16,46 @@ import space.kscience.visionforge.html.VisionOfHtmlForm import space.kscience.visionforge.html.VisionOfNumberField import space.kscience.visionforge.html.VisionOfTextField -public val textVisionRenderer: ElementVisionRenderer = ElementVisionRenderer { vision, _ -> - val name = vision.name ?: "input[${vision.hashCode().toUInt()}]" +internal fun textVisionRenderer( + client: VisionClient, +): ElementVisionRenderer = ElementVisionRenderer { name, vision, _ -> + val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]" vision.label?.let { label { - htmlFor = name + htmlFor = fieldName +it } } input { type = InputType.text - this.name = name + this.name = fieldName vision.useProperty(VisionOfTextField::text) { value = it ?: "" } onChangeFunction = { - vision.text = value +// client.visionPropertyChanged(name, VisionOfTextField::text.name.pa, value) } } } -public val numberVisionRenderer: ElementVisionRenderer = ElementVisionRenderer { vision, _ -> - val name = vision.name ?: "input[${vision.hashCode().toUInt()}]" +internal fun numberVisionRenderer( + client: VisionClient, +): ElementVisionRenderer = ElementVisionRenderer { name, vision, _ -> + val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]" vision.label?.let { label { - htmlFor = name + htmlFor = fieldName +it } } input { type = InputType.text - this.name = name + this.name = fieldName vision.useProperty(VisionOfNumberField::value) { value = it?.toDouble() ?: 0.0 } onChangeFunction = { - vision.value = value.toDoubleOrNull() +// vision.value = value.toDoubleOrNull() } } } @@ -61,7 +65,8 @@ internal fun FormData.toMeta(): Meta { //val res = js("Object.fromEntries(formData);") val `object` = js("{}") //language=JavaScript - js(""" + js( + """ formData.forEach(function(value, key){ // Reflect.has in favor of: object.hasOwnProperty(key) if(!Reflect.has(object, key)){ @@ -73,11 +78,14 @@ internal fun FormData.toMeta(): Meta { } object[key].push(value); }); - """) + """ + ) return DynamicMeta(`object`) } -public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer { vision, _ -> +internal fun formVisionRenderer( + client: VisionClient, +): ElementVisionRenderer = ElementVisionRenderer { name, vision, _ -> val form = document.getElementById(vision.formId) as? HTMLFormElement ?: error("An element with id = '${vision.formId} is not a form") @@ -95,7 +103,7 @@ public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer ElementVisionRenderer.ZERO_RATING } - override fun render(element: Element, vision: Vision, meta: Meta) { + override fun render(element: Element, name: Name, vision: Vision, meta: Meta) { require(vision is VisionOfMarkup) { "The vision is not a markup vision" } val div = document.createElement("div") val flavour = when (vision.format) {