Refactor server API

This commit is contained in:
Alexander Nozik 2022-12-03 14:51:32 +03:00
parent c8141c6338
commit fd8f693151
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 61 additions and 37 deletions

View File

@ -34,7 +34,7 @@ public interface ElementVisionRenderer : Named {
* Display the [vision] inside a given [element] replacing its current content. * Display the [vision] inside a given [element] replacing its current content.
* @param meta additional parameters for rendering container * @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 companion object {
public const val TYPE: String = "elementVisionRenderer" public const val TYPE: String = "elementVisionRenderer"
@ -49,7 +49,7 @@ public interface ElementVisionRenderer : Named {
public class SingleTypeVisionRenderer<T : Vision>( public class SingleTypeVisionRenderer<T : Vision>(
public val kClass: KClass<T>, public val kClass: KClass<T>,
private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
private val renderFunction: TagConsumer<HTMLElement>.(vision: T, meta: Meta) -> Unit, private val renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
) : ElementVisionRenderer { ) : ElementVisionRenderer {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
@ -60,15 +60,15 @@ public class SingleTypeVisionRenderer<T : Vision>(
override fun rateVision(vision: Vision): Int = override fun rateVision(vision: Vision): Int =
if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING 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.clear()
element.append { element.append {
renderFunction(kClass.cast(vision), meta) renderFunction(name, kClass.cast(vision), meta)
} }
} }
} }
public inline fun <reified T : Vision> ElementVisionRenderer( public inline fun <reified T : Vision> ElementVisionRenderer(
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING, acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
noinline renderFunction: TagConsumer<HTMLElement>.(vision: T, meta: Meta) -> Unit, noinline renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction) ): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction)

View File

@ -13,6 +13,7 @@ import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.html.VisionTagConsumer import space.kscience.visionforge.html.VisionTagConsumer
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
@ -67,17 +68,29 @@ public class VisionClient : AbstractPlugin() {
changeCollector.propertyChanged(visionName, propertyName, item) 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?) { public fun visionChanged(name: Name?, child: Vision?) {
changeCollector.setChild(name, child) 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) vision.setAsRoot(visionManager)
val renderer = findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}") 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 -> element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) { val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
val endpoint = resolveEndpoint(element) val endpoint = resolveEndpoint(element)
@ -89,7 +102,7 @@ public class VisionClient : AbstractPlugin() {
URL(attr.value) URL(attr.value)
}.apply { }.apply {
protocol = "ws" protocol = "ws"
searchParams.append("name", name) searchParams.append("name", name.toString())
} }
logger.info { "Updating vision data from $wsUrl" } logger.info { "Updating vision data from $wsUrl" }
@ -106,7 +119,7 @@ public class VisionClient : AbstractPlugin() {
// If change contains root vision replacement, do it // If change contains root vision replacement, do it
change.vision?.let { vision -> change.vision?.let { vision ->
renderVision(element, vision, outputMeta) renderVision(element, name, vision, outputMeta)
} }
logger.debug { "Got update $change for output with name $name" } logger.debug { "Got update $change for output with name $name" }
@ -152,7 +165,7 @@ public class VisionClient : AbstractPlugin() {
*/ */
public fun renderVisionIn(element: Element) { public fun renderVisionIn(element: Element) {
if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output 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") { if (element.attributes[OUTPUT_RENDERED]?.value == "true") {
logger.info { "VF output in element $element is already rendered" } logger.info { "VF output in element $element is already rendered" }
@ -179,7 +192,7 @@ public class VisionClient : AbstractPlugin() {
} else { } else {
URL(attr.value) URL(attr.value)
}.apply { }.apply {
searchParams.append("name", name) searchParams.append("name", name.toString())
} }
logger.info { "Fetching vision data from $fetchUrl" } logger.info { "Fetching vision data from $fetchUrl" }
@ -187,8 +200,8 @@ public class VisionClient : AbstractPlugin() {
if (response.ok) { if (response.ok) {
response.text().then { text -> response.text().then { text ->
val vision = visionManager.decodeFromString(text) val vision = visionManager.decodeFromString(text)
renderVision(element, vision, outputMeta) renderVision(element, name, vision, outputMeta)
updateVision(name, element, vision, outputMeta) updateVision(element, name, vision, outputMeta)
} }
} else { } else {
logger.error { "Failed to fetch initial vision state from $fetchUrl" } logger.error { "Failed to fetch initial vision state from $fetchUrl" }
@ -203,13 +216,13 @@ public class VisionClient : AbstractPlugin() {
visionManager.decodeFromString(it) visionManager.decodeFromString(it)
} }
logger.info { "Found embedded vision for output with name $name" } logger.info { "Found embedded vision for output with name $name" }
renderVision(element, embeddedVision, outputMeta) renderVision(element, name, embeddedVision, outputMeta)
updateVision(name, element, embeddedVision, outputMeta) updateVision(element, name, embeddedVision, outputMeta)
} }
//Try to load vision via websocket //Try to load vision via websocket
element.attributes[OUTPUT_CONNECT_ATTRIBUTE] != null -> { 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") else -> error("No embedded vision data / fetch url for $name")
@ -217,11 +230,13 @@ public class VisionClient : AbstractPlugin() {
element.setAttribute(OUTPUT_RENDERED, "true") element.setAttribute(OUTPUT_RENDERED, "true")
} }
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) mapOf( override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) {
numberVisionRenderer.name to numberVisionRenderer, listOf(
textVisionRenderer.name to textVisionRenderer, numberVisionRenderer(this),
formVisionRenderer.name to formVisionRenderer textVisionRenderer(this),
) else super.content(target) formVisionRenderer(this)
).toMap()
} else super.content(target)
public companion object : PluginFactory<VisionClient> { public companion object : PluginFactory<VisionClient> {
override fun build(context: Context, meta: Meta): VisionClient = VisionClient() override fun build(context: Context, meta: Meta): VisionClient = VisionClient()

View File

@ -16,42 +16,46 @@ import space.kscience.visionforge.html.VisionOfHtmlForm
import space.kscience.visionforge.html.VisionOfNumberField import space.kscience.visionforge.html.VisionOfNumberField
import space.kscience.visionforge.html.VisionOfTextField import space.kscience.visionforge.html.VisionOfTextField
public val textVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { vision, _ -> internal fun textVisionRenderer(
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]" client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { name, vision, _ ->
val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]"
vision.label?.let { vision.label?.let {
label { label {
htmlFor = name htmlFor = fieldName
+it +it
} }
} }
input { input {
type = InputType.text type = InputType.text
this.name = name this.name = fieldName
vision.useProperty(VisionOfTextField::text) { vision.useProperty(VisionOfTextField::text) {
value = it ?: "" value = it ?: ""
} }
onChangeFunction = { onChangeFunction = {
vision.text = value // client.visionPropertyChanged(name, VisionOfTextField::text.name.pa, value)
} }
} }
} }
public val numberVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfNumberField> { vision, _ -> internal fun numberVisionRenderer(
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]" client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfNumberField> { name, vision, _ ->
val fieldName = vision.name ?: "input[${vision.hashCode().toUInt()}]"
vision.label?.let { vision.label?.let {
label { label {
htmlFor = name htmlFor = fieldName
+it +it
} }
} }
input { input {
type = InputType.text type = InputType.text
this.name = name this.name = fieldName
vision.useProperty(VisionOfNumberField::value) { vision.useProperty(VisionOfNumberField::value) {
value = it?.toDouble() ?: 0.0 value = it?.toDouble() ?: 0.0
} }
onChangeFunction = { 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 res = js("Object.fromEntries(formData);")
val `object` = js("{}") val `object` = js("{}")
//language=JavaScript //language=JavaScript
js(""" js(
"""
formData.forEach(function(value, key){ formData.forEach(function(value, key){
// Reflect.has in favor of: object.hasOwnProperty(key) // Reflect.has in favor of: object.hasOwnProperty(key)
if(!Reflect.has(object, key)){ if(!Reflect.has(object, key)){
@ -73,11 +78,14 @@ internal fun FormData.toMeta(): Meta {
} }
object[key].push(value); object[key].push(value);
}); });
""") """
)
return DynamicMeta(`object`) return DynamicMeta(`object`)
} }
public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlForm> { vision, _ -> internal fun formVisionRenderer(
client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlForm> { name, vision, _ ->
val form = document.getElementById(vision.formId) as? HTMLFormElement val form = document.getElementById(vision.formId) as? HTMLFormElement
?: error("An element with id = '${vision.formId} is not a form") ?: error("An element with id = '${vision.formId} is not a form")
@ -95,7 +103,7 @@ public val formVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<Vis
event.preventDefault() event.preventDefault()
val formData = FormData(form).toMeta() val formData = FormData(form).toMeta()
//console.log(formData.toString()) //console.log(formData.toString())
vision.values = formData //vision.values = formData
false false
} }
} }

View File

@ -11,6 +11,7 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.* import space.kscience.visionforge.*
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
@ -26,7 +27,7 @@ public actual class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
else -> ElementVisionRenderer.ZERO_RATING else -> 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" } require(vision is VisionOfMarkup) { "The vision is not a markup vision" }
val div = document.createElement("div") val div = document.createElement("div")
val flavour = when (vision.format) { val flavour = when (vision.format) {