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.
* @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<T : Vision>(
public val kClass: KClass<T>,
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 {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
@ -60,15 +60,15 @@ 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, 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 <reified T : Vision> ElementVisionRenderer(
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)

View File

@ -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<Name, Any> = 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<Name, Any> = if (target == ElementVisionRenderer.TYPE) {
listOf(
numberVisionRenderer(this),
textVisionRenderer(this),
formVisionRenderer(this)
).toMap()
} else super.content(target)
public companion object : PluginFactory<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.VisionOfTextField
public val textVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { vision, _ ->
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]"
internal fun textVisionRenderer(
client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfTextField> { 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<VisionOfNumberField> { vision, _ ->
val name = vision.name ?: "input[${vision.hashCode().toUInt()}]"
internal fun numberVisionRenderer(
client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfNumberField> { 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<VisionOfHtmlForm> { vision, _ ->
internal fun formVisionRenderer(
client: VisionClient,
): ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlForm> { 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<Vis
event.preventDefault()
val formData = FormData(form).toMeta()
//console.log(formData.toString())
vision.values = formData
//vision.values = formData
false
}
}

View File

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