diff --git a/demo/sat-demo/build.gradle.kts b/demo/sat-demo/build.gradle.kts index d3a7d140..efaaff60 100644 --- a/demo/sat-demo/build.gradle.kts +++ b/demo/sat-demo/build.gradle.kts @@ -1,24 +1,59 @@ plugins { id("ru.mipt.npm.mpp") + application } -val kvisionVersion: String = "3.16.2" + +group = "ru.mipt.npm" + +//val kvisionVersion: String = "3.16.2" kscience{ + useSerialization{ + json() + } application() } +val ktorVersion: String by rootProject.extra + kotlin { + afterEvaluate { + val jsBrowserDistribution by tasks.getting + + jvm { + withJava() + compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME]?.apply { + tasks.getByName(processResourcesTaskName) { + dependsOn(jsBrowserDistribution) + afterEvaluate { + from(jsBrowserDistribution) + } + } + } + + } + } + sourceSets { commonMain { dependencies { implementation(project(":visionforge-solid")) } } + jvmMain { + dependencies { + implementation(project(":visionforge-server")) + } + } jsMain { dependencies { implementation(project(":visionforge-threejs")) } } } -} \ No newline at end of file +} + +application { + mainClass.set("ru.mipt.npm.sat.SatServerKt") +} diff --git a/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt b/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt index 39f2541d..45e0283c 100644 --- a/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt +++ b/demo/sat-demo/src/jsMain/kotlin/ru/mipt/npm/sat/SatDemoApp.kt @@ -1,34 +1,40 @@ package ru.mipt.npm.sat -import hep.dataforge.Application import hep.dataforge.context.Global -import hep.dataforge.meta.invoke -import hep.dataforge.startApplication +import hep.dataforge.vision.client.VisionClient +import hep.dataforge.vision.client.fetchAndRenderAllVisions import hep.dataforge.vision.solid.three.ThreePlugin -import hep.dataforge.vision.solid.three.render -import kotlinx.browser.document -import org.w3c.dom.HTMLElement +import kotlinx.browser.window -private class SatDemoApp : Application { - - override fun start(state: Map) { - val element = document.getElementById("canvas") as? HTMLElement - ?: error("Element with id 'canvas' not found on page") - val three = Global.plugins.fetch(ThreePlugin) - val sat = visionOfSatellite( - ySegments = 3, - ) - three.render(element, sat){ - minSize = 500 - axes{ - size = 500.0 - visible = true - } - } - } - -} +//private class SatDemoApp : Application { +// +// override fun start(state: Map) { +// val element = document.getElementById("canvas") as? HTMLElement +// ?: error("Element with id 'canvas' not found on page") +// val three = Global.plugins.fetch(ThreePlugin) +// +// val sat = visionOfSatellite( +// ySegments = 3, +// ) +// three.render(element, sat){ +// minSize = 500 +// axes{ +// size = 500.0 +// visible = true +// } +// } +// } +// +//} +// +//fun main() { +// startApplication(::SatDemoApp) +//} fun main() { - startApplication(::SatDemoApp) + //Loading three-js renderer + Global.plugins.load(ThreePlugin) + window.onload = { + Global.plugins.fetch(VisionClient).fetchAndRenderAllVisions() + } } \ No newline at end of file diff --git a/demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt b/demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt new file mode 100644 index 00000000..6ac3e3c2 --- /dev/null +++ b/demo/sat-demo/src/jvmMain/kotlin/ru/mipt/npm/sat/satServer.kt @@ -0,0 +1,35 @@ +package ru.mipt.npm.sat + + +import hep.dataforge.context.Global +import hep.dataforge.names.asName +import hep.dataforge.vision.server.visionModule +import hep.dataforge.vision.solid.SolidManager +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import io.ktor.util.KtorExperimentalAPI +import kotlinx.html.script + +@OptIn(KtorExperimentalAPI::class) +fun main() { + val sat = visionOfSatellite( + ySegments = 3, + ) + + val context = Global.context("SAT"){ + plugin(SolidManager) + } + + embeddedServer(CIO, 8080, host = "localhost"){ + visionModule(context).apply { + header { + script { + src = "sat-demo.js" + } + } + page { + vision("main".asName(), sat) + } + } + }.start(wait = true) +} \ No newline at end of file diff --git a/playground/build.gradle.kts b/playground/build.gradle.kts index 4eeabfa2..428bb779 100644 --- a/playground/build.gradle.kts +++ b/playground/build.gradle.kts @@ -12,16 +12,30 @@ repositories{ } kotlin { + jvm() js(IR) { - browser {} + browser { + } + binaries.executable() } sourceSets { commonMain { dependencies { - api(project(":visionforge-solid")) - api(project(":visionforge-gdml")) - api(project(":ui:bootstrap")) + implementation(project(":visionforge-solid")) + implementation(project(":visionforge-gdml")) + + } + } + val jsMain by getting{ + dependencies { + implementation(project(":ui:bootstrap")) + } + } + + val jvmMain by getting{ + dependencies { + implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") } } } diff --git a/visionforge-solid/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt similarity index 93% rename from visionforge-solid/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt rename to playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt index 9d3c332b..a7ccea62 100644 --- a/visionforge-solid/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt +++ b/playground/src/jvmMain/kotlin/hep/dataforge/vision/solid/generateSchema.kt @@ -1,4 +1,22 @@ -//package hep.dataforge.vision.solid +package hep.dataforge.vision.solid + +import com.github.ricky12awesome.jss.encodeToSchema +import kotlinx.serialization.json.Json + +fun main() { + val schema = Json { + serializersModule = SolidManager.serializersModuleForSolids + prettyPrintIndent = " " + prettyPrint = true + ignoreUnknownKeys = true + isLenient = true + coerceInputValues = true + encodeDefaults = true + }.encodeToSchema(SolidGroup.serializer(), generateDefinitions = false) + println(schema) +} + + // //import hep.dataforge.meta.JSON_PRETTY //import kotlinx.serialization.* diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionManager.kt index 14a72d2a..7c0e14f6 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionManager.kt @@ -54,6 +54,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) { private val defaultSerialModule: SerializersModule = SerializersModule { polymorphic(Vision::class) { + default { VisionBase.serializer() } subclass(VisionBase.serializer()) subclass(VisionGroupBase.serializer()) } diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlOutputScope.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlOutputScope.kt index 0fcd81dd..87901ca9 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlOutputScope.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/html/HtmlOutputScope.kt @@ -63,5 +63,7 @@ public abstract class HtmlOutputScope( public companion object { public const val OUTPUT_CLASS: String = "visionforge-output" public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name" + public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" + public const val DEFAULT_ENDPOINT: String = "." } } \ No newline at end of file diff --git a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/ClientVisionManager.kt b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/ClientVisionManager.kt deleted file mode 100644 index 7866c814..00000000 --- a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/ClientVisionManager.kt +++ /dev/null @@ -1,43 +0,0 @@ -package hep.dataforge.vision.client - -import hep.dataforge.context.Context -import hep.dataforge.context.Global -import hep.dataforge.vision.VisionManager -import hep.dataforge.vision.html.HtmlOutputScope -import kotlinx.browser.window -import org.w3c.dom.Element -import org.w3c.dom.get -import org.w3c.dom.url.URL - -@JsExport -public class ClientVisionManager { - private val visionForgeContext: Context = Global.context("client") { - plugin(VisionManager) - } - - private val visionManager: VisionManager = visionForgeContext.plugins.fetch(VisionManager) - - /** - * Up-going tree traversal in search for endpoint attribute - */ - private fun resolveEndpoint(element: Element?): String { - if(element == null) return DEFAULT_ENDPOINT - val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE] - return attribute?.value ?: resolveEndpoint(element.parentElement) - } - - public fun renderVision(element: Element){ - if(!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element") - val endpoint = URL(resolveEndpoint(element)) - window.fetch("$endpoint/vision").then {response-> - TODO() - } - } - - public companion object { - public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" - public const val DEFAULT_ENDPOINT: String = ".." - } -} - - diff --git a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt new file mode 100644 index 00000000..ce74f85f --- /dev/null +++ b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/VisionClient.kt @@ -0,0 +1,124 @@ +package hep.dataforge.vision.client + +import hep.dataforge.context.* +import hep.dataforge.meta.Meta +import hep.dataforge.vision.Vision +import hep.dataforge.vision.VisionManager +import hep.dataforge.vision.html.HtmlOutputScope +import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_ENDPOINT_ATTRIBUTE +import hep.dataforge.vision.html.HtmlOutputScope.Companion.OUTPUT_NAME_ATTRIBUTE +import kotlinx.browser.document +import kotlinx.browser.window +import org.w3c.dom.Element +import org.w3c.dom.WebSocket +import org.w3c.dom.asList +import org.w3c.dom.get +import org.w3c.dom.url.URL +import kotlin.reflect.KClass + +public class VisionClient : AbstractPlugin() { + override val tag: PluginTag get() = Companion.tag + private val visionManager: VisionManager by require(VisionManager) + + /** + * Up-going tree traversal in search for endpoint attribute + */ + private fun resolveEndpoint(element: Element?): String { + if (element == null) return window.location.href + val attribute = element.attributes[OUTPUT_ENDPOINT_ATTRIBUTE] + return attribute?.value ?: resolveEndpoint(element.parentElement) + } + + private fun resolveName(element: Element): String? { + val attribute = element.attributes[OUTPUT_NAME_ATTRIBUTE] + return attribute?.value + } + + private fun getRenderers() = context.gather(ElementVisionRenderer.TYPE).values + + public fun findRendererFor(vision: Vision): ElementVisionRenderer? = getRenderers().maxByOrNull { it.rateVision(vision) } + + /** + * Fetch from server and render a vision, described in a given with [HtmlOutputScope.OUTPUT_CLASS] class. + */ + public fun fetchAndRenderVision(element: Element, requestUpdates: Boolean = true) { + val name = resolveName(element) ?: error("The element is not a vision output") + console.info("Found DF output with name $name") + if (!element.classList.contains(HtmlOutputScope.OUTPUT_CLASS)) error("The element $element is not an output element") + val endpoint = resolveEndpoint(element) + console.info("Vision server is resolved to $endpoint") + val fetchUrl = URL(endpoint).apply { + searchParams.append("name", name) + pathname += "/vision" + } + window.fetch(fetchUrl).then { response -> + if (response.ok) { + response.text().then { text -> + val vision = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text) + + val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") + renderer.render(element, vision) + if (requestUpdates) { + val wsUrl = URL(endpoint).apply { + pathname += "/ws" + protocol = "ws" + searchParams.append("name", name) + } + val ws = WebSocket(wsUrl.toString()).apply { + onmessage = { messageEvent -> + val stringData: String? = messageEvent.data as? String + if (stringData != null) { + val update = visionManager.jsonFormat.decodeFromString(Vision.serializer(), text) + vision.update(update) + } else { + console.error("WebSocket message data is not a string") + } + } + onopen = { + console.info("WebSocket update channel established for output '$name'") + } + onclose = { + console.info("WebSocket update channel closed for output '$name'") + } + onerror = { + console.error("WebSocket update channel error for output '$name'") + } + } + + } + } + } else { + console.error("Failed to fetch initial vision state from $endpoint") + } + + } + } + + public companion object : PluginFactory { + + override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient() + + override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP) + + override val type: KClass = VisionClient::class + } +} + +/** + * Fetch and render visions for all elements with [HtmlOutputScope.OUTPUT_CLASS] class inside given [element]. + */ +public fun VisionClient.fetchVisionsInChildren(element: Element, requestUpdates: Boolean = true) { + val elements = element.getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS) + console.info("Finished search for outputs. Found ${elements.length} items") + elements.asList().forEach { child -> + fetchAndRenderVision(child, requestUpdates) + } +} + +/** + * Fetch visions from the server for all elements with [HtmlOutputScope.OUTPUT_CLASS] class in the document body + */ +public fun VisionClient.fetchAndRenderAllVisions(requestUpdates: Boolean = true){ + val element = document.body ?: error("Document does not have a body") + fetchVisionsInChildren(element, requestUpdates) +} \ No newline at end of file diff --git a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt index 9fe3b43e..3673ecc2 100644 --- a/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt +++ b/visionforge-core/src/jsMain/kotlin/hep/dataforge/vision/client/elementOutput.kt @@ -1,7 +1,9 @@ package hep.dataforge.vision.client +import hep.dataforge.meta.DFExperimental import hep.dataforge.names.Name import hep.dataforge.names.toName +import hep.dataforge.provider.Type import hep.dataforge.vision.Vision import hep.dataforge.vision.html.BindingHtmlOutputScope import hep.dataforge.vision.html.HtmlOutputScope @@ -10,18 +12,38 @@ import kotlinx.browser.document import kotlinx.html.TagConsumer import org.w3c.dom.* -public interface ElementVisionRenderer { - public fun render(element: Element, vision: V): Unit -} +@Type(ElementVisionRenderer.TYPE) +public interface ElementVisionRenderer { -public fun Map.bind(renderer: ElementVisionRenderer) { - forEach { (id, vision) -> - val element = document.getElementById(id) ?: error("Could not find element with id $id") - renderer.render(element, vision) + /** + * Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer + * can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify + * higher value in order to "steal" rendering job + */ + public fun rateVision(vision: Vision): Int + + /** + * Display the [vision] inside a given [element] replacing its current content + */ + public fun render(element: Element, vision: Vision): Unit + + public companion object { + public const val TYPE: String = "elementVisionRenderer" + public const val ZERO_RATING: Int = 0 + public const val DEFAULT_RATING: Int = 10 } } -public fun Element.renderVisions(renderer: ElementVisionRenderer, visionProvider: (Name) -> V?) { +@DFExperimental +public fun Map.bind(rendererFactory: (Vision) -> ElementVisionRenderer) { + forEach { (id, vision) -> + val element = document.getElementById(id) ?: error("Could not find element with id $id") + rendererFactory(vision).render(element, vision) + } +} + +@DFExperimental +public fun Element.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer) { val elements = getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS) elements.asList().forEach { element -> val name = element.attributes[HtmlOutputScope.OUTPUT_NAME_ATTRIBUTE]?.value @@ -30,21 +52,19 @@ public fun Element.renderVisions(renderer: ElementVisionRenderer return@forEach } val vision = visionProvider(name.toName()) - if (vision == null) { - console.error("Vision with name $name is not resolved") - return@forEach - } - renderer.render(element, vision) + rendererFactory(vision).render(element, vision) } } -public fun Document.renderVisions(renderer: ElementVisionRenderer, visionProvider: (Name) -> V?): Unit { - documentElement?.renderVisions(renderer, visionProvider) +@DFExperimental +public fun Document.renderAllVisions(visionProvider: (Name) -> Vision, rendererFactory: (Vision) -> ElementVisionRenderer): Unit { + documentElement?.renderAllVisions(visionProvider,rendererFactory) } +@DFExperimental public fun HtmlVisionFragment.renderInDocument( root: TagConsumer, - renderer: ElementVisionRenderer, + renderer: ElementVisionRenderer, ): HTMLElement = BindingHtmlOutputScope(root).apply(content).let { scope -> scope.finalize().apply { scope.bindings.forEach { (name, vision) -> diff --git a/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt b/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt index 77f18d81..c306fae4 100644 --- a/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/hep/dataforge/vision/server/VisionServer.kt @@ -14,6 +14,7 @@ import hep.dataforge.vision.html.* import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE import io.ktor.application.* import io.ktor.features.CORS +import io.ktor.features.CallLogging import io.ktor.html.respondHtml import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode @@ -23,7 +24,10 @@ import io.ktor.http.content.static import io.ktor.http.withCharset import io.ktor.response.respond import io.ktor.response.respondText -import io.ktor.routing.* +import io.ktor.routing.application +import io.ktor.routing.get +import io.ktor.routing.route +import io.ktor.routing.routing import io.ktor.server.engine.ApplicationEngine import io.ktor.websocket.WebSockets import io.ktor.websocket.webSocket @@ -34,9 +38,12 @@ import java.awt.Desktop import java.net.URI import kotlin.time.milliseconds +/** + * A ktor plugin container with given [routing] + */ public class VisionServer internal constructor( private val visionManager: VisionManager, - private val routing: Routing, + private val application: Application, private val rootRoute: String, ) : Configurable { override val config: Config = Config() @@ -57,7 +64,7 @@ public class VisionServer internal constructor( title: String, headers: List, ): Map { - lateinit var result: Map + lateinit var visionMap: Map head { meta { @@ -69,21 +76,26 @@ public class VisionServer internal constructor( title(title) } body { - result = visionFragment(visionFragment) - script { - type = "text/javascript" - - val normalizedRoute = if (rootRoute.endsWith("/")) { - rootRoute - } else { - "$rootRoute/" - } - - src = TODO()//"${normalizedRoute}js/plotlyConnect.js" - } +// attributes[OUTPUT_ENDPOINT_ATTRIBUTE] = if (rootRoute.endsWith("/")) { +// rootRoute +// } else { +// "$rootRoute/" +// } + //Load the fragment and remember all loaded visions + visionMap = visionFragment(visionFragment) +// //The script runs when all headers already with required libraries are already loaded +// script { +// type = "text/javascript" +// +// val normalizedRoute = +// unsafe { +// //language=JavaScript +// +"fetchAndRenderAllVisions()" +// } +// } } - return result + return visionMap } public fun page( @@ -102,56 +114,50 @@ public class VisionServer internal constructor( null } - routing.createRouteFromPath(rootRoute).apply { - route(route) { - //Update websocket - webSocket("ws") { - val name: String = call.request.queryParameters["name"] - ?: error("Vision name is not defined in parameters") + application.routing { + route(rootRoute) { + route(route) { + //Update websocket + webSocket("ws") { + val name: String = call.request.queryParameters["name"] + ?: error("Vision name is not defined in parameters") - application.log.debug("Opened server socket for $name") - val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") - try { - vision.flowChanges(this, updateInterval.milliseconds).collect { update -> - val json = visionManager.encodeToString(update) - outgoing.send(Frame.Text(json)) + application.log.debug("Opened server socket for $name") + val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") + try { + vision.flowChanges(this, updateInterval.milliseconds).collect { update -> + val json = visionManager.encodeToString(update) + outgoing.send(Frame.Text(json)) + } + } catch (ex: Exception) { + application.log.debug("Closed server socket for $name") } - } catch (ex: Exception) { - application.log.debug("Closed server socket for $name") } - } - //Plots in their json representation - get("vision") { - val name: String = call.request.queryParameters["name"] - ?: error("Vision name is not defined in parameters") + //Plots in their json representation + get("vision") { + val name: String = call.request.queryParameters["name"] + ?: error("Vision name is not defined in parameters") - val vision: Vision? = visions[name.toName()] - if (vision == null) { - call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found") - } else { - call.respondText( - visionManager.encodeToString(vision), - contentType = ContentType.Application.Json, - status = HttpStatusCode.OK - ) - } - } - //filled pages - get { -// val origin = call.request.origin -// val url = URLBuilder().apply { -// protocol = URLProtocol.createOrDefault(origin.scheme) -// //workaround for https://github.com/ktorio/ktor/issues/1663 -// host = if (origin.host.startsWith("0:")) "[${origin.host}]" else origin.host -// port = origin.port -// encodedPath = origin.uri -// }.build() - if (cachedHtml == null) { - call.respondHtml { - visions.putAll(buildPage(visionFragment, title, headers)) + val vision: Vision? = visions[name.toName()] + if (vision == null) { + call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found") + } else { + call.respondText( + visionManager.encodeToString(vision), + contentType = ContentType.Application.Json, + status = HttpStatusCode.OK + ) + } + } + //filled pages + get { + if (cachedHtml == null) { + call.respondHtml { + visions.putAll(buildPage(visionFragment, title, headers)) + } + } else { + call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8)) } - } else { - call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8)) } } } @@ -160,7 +166,7 @@ public class VisionServer internal constructor( public fun page( route: String = DEFAULT_PAGE, - title: String = "Plotly server page '$route'", + title: String = "VisionForge server page '$route'", headers: List = emptyList(), content: HtmlOutputScope<*, Vision>.() -> Unit, ) { @@ -170,7 +176,6 @@ public class VisionServer internal constructor( public companion object { public const val DEFAULT_PAGE: String = "/" - public val UPDATE_MODE_KEY: Name = "update.mode".toName() public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName() } } @@ -190,8 +195,12 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA } } + if(featureOrNull(CallLogging) == null){ + install(CallLogging) + } - val routing = routing { + + routing { route(route) { static { resources() @@ -201,7 +210,7 @@ public fun Application.visionModule(context: Context, route: String = DEFAULT_PA val visionManager = context.plugins.fetch(VisionManager) - return VisionServer(visionManager, routing, route) + return VisionServer(visionManager, this, route) } public fun ApplicationEngine.show() { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Box.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Box.kt index 5bbbcc7d..08d8199f 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Box.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Box.kt @@ -12,7 +12,7 @@ public class Box( public val xSize: Float, public val ySize: Float, public val zSize: Float -) : BasicSolid(), GeometrySolid { +) : SolidBase(), GeometrySolid { //TODO add helper for color configuration override fun toGeometry(geometryBuilder: GeometryBuilder) { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt index ec036419..566be58a 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt @@ -19,7 +19,7 @@ public class Composite( public val compositeType: CompositeType, public val first: Solid, public val second: Solid -) : BasicSolid(), Solid, VisionGroup { +) : SolidBase(), Solid, VisionGroup { init { first.parent = this diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/ConeSegment.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/ConeSegment.kt index 15116d93..ddd329e0 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/ConeSegment.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/ConeSegment.kt @@ -18,7 +18,7 @@ public class ConeSegment( public var upperRadius: Float, public var startAngle: Float = 0f, public var angle: Float = PI2 -) : BasicSolid(), GeometrySolid { +) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder) { val segments = detail ?: 8 diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Convex.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Convex.kt index c6425d5c..7fac91c5 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Convex.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Convex.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("solid.convex") -public class Convex(public val points: List) : BasicSolid(), Solid +public class Convex(public val points: List) : SolidBase(), Solid public inline fun VisionContainerBuilder.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}): Convex = ConvexBuilder().apply(action).build().also { set(name, it) } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Extruded.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Extruded.kt index dc539387..e9c0d3a1 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Extruded.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Extruded.kt @@ -39,7 +39,7 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo public class Extruded( public var shape: List = ArrayList(), public var layers: MutableList = ArrayList() -) : BasicSolid(), GeometrySolid { +) : SolidBase(), GeometrySolid { public fun shape(block: Shape2DBuilder.() -> Unit) { this.shape = Shape2DBuilder().apply(block).build() diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt index f55cc89a..7305011e 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("solid.line") -public class PolyLine(public var points: List) : BasicSolid(), Solid { +public class PolyLine(public var points: List) : SolidBase(), Solid { //var lineType by string() public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/BasicSolid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt similarity index 95% rename from visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/BasicSolid.kt rename to visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt index 93fef60f..f6d836a1 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/BasicSolid.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidBase.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("solid") -public open class BasicSolid : VisionBase(), Solid { +public open class SolidBase : VisionBase(), Solid { override val descriptor: NodeDescriptor get() = Solid.descriptor override var position: Point3D? = null diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidLabel.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidLabel.kt index bb033215..b829c7e1 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidLabel.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidLabel.kt @@ -11,7 +11,7 @@ public class SolidLabel( public var text: String, public var fontSize: Double, public var fontFamily: String, -) : BasicSolid(), Solid +) : SolidBase(), Solid public fun VisionContainerBuilder.label( text: String, diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt index 1800865d..34bfed6b 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidManager.kt @@ -7,10 +7,7 @@ import hep.dataforge.context.PluginTag import hep.dataforge.meta.Meta import hep.dataforge.names.Name import hep.dataforge.names.toName -import hep.dataforge.vision.Vision -import hep.dataforge.vision.VisionGroup -import hep.dataforge.vision.VisionGroupBase -import hep.dataforge.vision.VisionManager +import hep.dataforge.vision.* import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET import kotlinx.serialization.json.Json import kotlinx.serialization.modules.PolymorphicModuleBuilder @@ -42,6 +39,7 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) { subclass(Composite.serializer()) subclass(Tube.serializer()) subclass(Box.serializer()) + subclass(ConeSegment.serializer()) subclass(Convex.serializer()) subclass(Extruded.serializer()) subclass(PolyLine.serializer()) @@ -51,11 +49,13 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) { public val serializersModuleForSolids: SerializersModule = SerializersModule { polymorphic(Vision::class) { + subclass(VisionBase.serializer()) subclass(VisionGroupBase.serializer()) solids() } polymorphic(Solid::class) { + default { SolidBase.serializer() } solids() } } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt index cf652b8b..3895171e 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlin.collections.set -public abstract class AbstractReference : BasicSolid(), VisionGroup { +public abstract class AbstractReference : SolidBase(), VisionGroup { public abstract val prototype: Solid override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Sphere.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Sphere.kt index 7a122c44..b04eb76b 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Sphere.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Sphere.kt @@ -16,7 +16,7 @@ public class Sphere( public var phi: Float = PI2, public var thetaStart: Float = 0f, public var theta: Float = PI.toFloat(), -) : BasicSolid(), GeometrySolid { +) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder) { fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Tube.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Tube.kt index 6d486da4..b48e7f6f 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Tube.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Tube.kt @@ -19,7 +19,7 @@ public class Tube( public var innerRadius: Float = 0f, public var startAngle: Float = 0f, public var angle: Float = PI2, -) : BasicSolid(), GeometrySolid { +) : SolidBase(), GeometrySolid { init { require(radius > 0) diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index cd6c76c6..63740483 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -15,7 +15,7 @@ import kotlin.collections.set import kotlin.reflect.KClass import info.laht.threekt.objects.Group as ThreeGroup -public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { +public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { override val tag: PluginTag get() = Companion.tag public val solidManager: SolidManager by require(SolidManager) @@ -122,8 +122,19 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { attach(element) } - override fun render(element: Element, vision: Solid) { - createCanvas(element).render(vision) + override fun content(target: String): Map { + return when (target) { + ElementVisionRenderer.TYPE -> mapOf("three".asName() to this) + else -> super.content(target) + } + } + + override fun rateVision(vision: Vision): Int { + return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING + } + + override fun render(element: Element, vision: Vision) { + createCanvas(element).render(vision as? Solid ?: error("Only solids are rendered")) } public companion object : PluginFactory { diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt index 16d90280..67870229 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeVision.kt @@ -1,11 +1,11 @@ package hep.dataforge.vision.solid.three -import hep.dataforge.vision.solid.BasicSolid +import hep.dataforge.vision.solid.SolidBase import info.laht.threekt.core.Object3D /** * A custom visual object that has its own Three.js renderer */ -public abstract class ThreeVision : BasicSolid() { +public abstract class ThreeVision : SolidBase() { public abstract fun render(): Object3D }