forked from kscience/visionforge
First server iteration.
This commit is contained in:
parent
2be4576495
commit
ebb7bf72d1
@ -2,6 +2,7 @@ package hep.dataforge.vision.html
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vision.Vision
|
||||
import kotlinx.html.FlowContent
|
||||
import kotlinx.html.TagConsumer
|
||||
|
||||
public class BindingHtmlOutputScope<T, V : Vision>(
|
||||
@ -16,3 +17,11 @@ public class BindingHtmlOutputScope<T, V : Vision>(
|
||||
_bindings[htmlOutput.name] = vision
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> TagConsumer<T>.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
|
||||
return BindingHtmlOutputScope<T, Vision>(this).apply(fragment.content).bindings
|
||||
}
|
||||
|
||||
public fun FlowContent.visionFragment(fragment: HtmlVisionFragment<Vision>): Map<Name, Vision> {
|
||||
return BindingHtmlOutputScope<Any?, Vision>(consumer).apply(fragment.content).bindings
|
||||
}
|
@ -3,10 +3,7 @@ package hep.dataforge.vision.html
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import kotlinx.html.DIV
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.id
|
||||
import kotlinx.html.*
|
||||
|
||||
public class HtmlOutput<V : Vision>(
|
||||
public val outputScope: HtmlOutputScope<*, V>,
|
||||
@ -28,7 +25,9 @@ public abstract class HtmlOutputScope<R, V : Vision>(
|
||||
name: Name,
|
||||
crossinline block: HtmlOutput<V>.() -> Unit = {},
|
||||
): T = div {
|
||||
this.id = resolveId(name)
|
||||
id = resolveId(name)
|
||||
classes = setOf(OUTPUT_CLASS)
|
||||
attributes[NAME_ATTRIBUTE] = name.toString()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
HtmlOutput(this@HtmlOutputScope, name, this).block()
|
||||
}
|
||||
@ -49,4 +48,20 @@ public abstract class HtmlOutputScope<R, V : Vision>(
|
||||
renderVision(this, vision)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the resulting object produced by [TagConsumer]
|
||||
*/
|
||||
protected open fun processResult(result: R) {
|
||||
//do nothing by default
|
||||
}
|
||||
|
||||
override fun finalize(): R {
|
||||
return root.finalize().also { processResult(it) }
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val OUTPUT_CLASS: String = "visionforge-output"
|
||||
public const val NAME_ATTRIBUTE: String = "data-output-name"
|
||||
}
|
||||
}
|
@ -1,8 +1,20 @@
|
||||
package hep.dataforge.vision.html
|
||||
|
||||
import hep.dataforge.vision.Vision
|
||||
import kotlinx.html.FlowContent
|
||||
import kotlinx.html.TagConsumer
|
||||
|
||||
public class HtmlVisionFragment<V : Vision>(public val layout: HtmlOutputScope<out Any, V>.() -> Unit)
|
||||
public class HtmlFragment(public val content: TagConsumer<*>.() -> Unit)
|
||||
|
||||
public fun buildVisionFragment(visit: HtmlOutputScope<out Any, Vision>.() -> Unit): HtmlVisionFragment<Vision> =
|
||||
HtmlVisionFragment(visit)
|
||||
public fun TagConsumer<*>.fragment(fragment: HtmlFragment) {
|
||||
fragment.content(this)
|
||||
}
|
||||
|
||||
public fun FlowContent.fragment(fragment: HtmlFragment) {
|
||||
fragment.content(consumer)
|
||||
}
|
||||
|
||||
public class HtmlVisionFragment<V : Vision>(public val content: HtmlOutputScope<*, V>.() -> Unit)
|
||||
|
||||
public fun buildVisionFragment(block: HtmlOutputScope<*, Vision>.() -> Unit): HtmlVisionFragment<Vision> =
|
||||
HtmlVisionFragment(block)
|
||||
|
@ -22,7 +22,7 @@ public fun <T : Any> HtmlVisionFragment<Vision>.renderToObject(
|
||||
root: TagConsumer<T>,
|
||||
prefix: String? = null,
|
||||
renderer: HtmlVisionRenderer<Vision>,
|
||||
): T = StaticHtmlOutputScope(root, prefix, renderer).apply(layout).finalize()
|
||||
): T = StaticHtmlOutputScope(root, prefix, renderer).apply(content).finalize()
|
||||
|
||||
public fun HtmlVisionFragment<Vision>.renderToString(renderer: HtmlVisionRenderer<Vision>): String =
|
||||
renderToObject(createHTML(), null, renderer)
|
@ -1,31 +0,0 @@
|
||||
package hep.dataforge.vision.html
|
||||
|
||||
import hep.dataforge.vision.Vision
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.html.TagConsumer
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
public interface HtmlVisionBinding<in V: Vision>{
|
||||
public fun bind(element: Element, vision: V): Unit
|
||||
}
|
||||
|
||||
public fun <V: Vision> Map<String, V>.bind(binder: HtmlVisionBinding<V>){
|
||||
forEach { (id, vision) ->
|
||||
val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||
binder.bind(element, vision)
|
||||
}
|
||||
}
|
||||
|
||||
public fun HtmlVisionFragment<Vision>.bindToDocument(
|
||||
root: TagConsumer<HTMLElement>,
|
||||
binder: HtmlVisionBinding<Vision>,
|
||||
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(layout).let { scope ->
|
||||
scope.finalize().apply {
|
||||
scope.bindings.forEach { (name, vision) ->
|
||||
val id = scope.resolveId(name)
|
||||
val element = document.getElementById(id) ?: error("Could not find element with name $name and id $id")
|
||||
binder.bind(element, vision)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package hep.dataforge.vision.html
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.html.TagConsumer
|
||||
import org.w3c.dom.*
|
||||
|
||||
public interface ElementVisionRenderer<in V : Vision> {
|
||||
public fun render(element: Element, vision: V): Unit
|
||||
}
|
||||
|
||||
public fun <V : Vision> Map<String, V>.bind(renderer: ElementVisionRenderer<V>) {
|
||||
forEach { (id, vision) ->
|
||||
val element = document.getElementById(id) ?: error("Could not find element with id $id")
|
||||
renderer.render(element, vision)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <V : Vision> Element.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?) {
|
||||
val elements = getElementsByClassName(HtmlOutputScope.OUTPUT_CLASS)
|
||||
elements.asList().forEach { element ->
|
||||
val name = element.attributes[HtmlOutputScope.NAME_ATTRIBUTE]?.value
|
||||
if (name == null) {
|
||||
console.error("Attribute ${HtmlOutputScope.NAME_ATTRIBUTE} not defined in the output element")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <V : Vision> Document.renderVisions(renderer: ElementVisionRenderer<V>, visionProvider: (Name) -> V?): Unit {
|
||||
documentElement?.renderVisions(renderer, visionProvider)
|
||||
}
|
||||
|
||||
public fun HtmlVisionFragment<Vision>.renderInDocument(
|
||||
root: TagConsumer<HTMLElement>,
|
||||
renderer: ElementVisionRenderer<Vision>,
|
||||
): HTMLElement = BindingHtmlOutputScope<HTMLElement, Vision>(root).apply(content).let { scope ->
|
||||
scope.finalize().apply {
|
||||
scope.bindings.forEach { (name, vision) ->
|
||||
val id = scope.resolveId(name)
|
||||
val element = document.getElementById(id) ?: error("Could not find element with name $name and id $id")
|
||||
renderer.render(element, vision)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +1,137 @@
|
||||
//package hep.dataforge.vision.server
|
||||
//
|
||||
//import hep.dataforge.meta.*
|
||||
//import hep.dataforge.names.Name
|
||||
//import hep.dataforge.names.toName
|
||||
//import hep.dataforge.vision.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
//import io.ktor.application.Application
|
||||
//import io.ktor.application.featureOrNull
|
||||
//import io.ktor.application.install
|
||||
//import io.ktor.features.CORS
|
||||
//import io.ktor.http.content.resources
|
||||
//import io.ktor.http.content.static
|
||||
//import io.ktor.routing.Routing
|
||||
//import io.ktor.routing.route
|
||||
//import io.ktor.routing.routing
|
||||
//import io.ktor.server.engine.ApplicationEngine
|
||||
//import io.ktor.websocket.WebSockets
|
||||
//import kotlinx.html.TagConsumer
|
||||
//import java.awt.Desktop
|
||||
//import java.net.URI
|
||||
//import kotlin.text.get
|
||||
//
|
||||
//public enum class ServerUpdateMode {
|
||||
// NONE,
|
||||
// PUSH,
|
||||
// PULL
|
||||
//}
|
||||
//
|
||||
//public class VisionServer internal constructor(
|
||||
// private val routing: Routing,
|
||||
// private val rootRoute: String,
|
||||
//) : Configurable {
|
||||
// override val config: Config = Config()
|
||||
// public var updateMode: ServerUpdateMode by config.enum(ServerUpdateMode.NONE, key = UPDATE_MODE_KEY)
|
||||
// public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||
// public var embedData: Boolean by config.boolean(false)
|
||||
//
|
||||
// /**
|
||||
// * a list of headers that should be applied to all pages
|
||||
// */
|
||||
// private val globalHeaders: ArrayList<HtmlFragment> = ArrayList<HtmlFragment>()
|
||||
//
|
||||
// public fun header(block: TagConsumer<*>.() -> Unit) {
|
||||
// globalHeaders.add(HtmlFragment(block))
|
||||
// }
|
||||
//
|
||||
// public fun page(
|
||||
// plotlyFragment: PlotlyFragment,
|
||||
// route: String = DEFAULT_PAGE,
|
||||
// title: String = "Plotly server page '$route'",
|
||||
// headers: List<HtmlFragment> = emptyList(),
|
||||
// ) {
|
||||
// routing.createRouteFromPath(rootRoute).apply {
|
||||
// val plots = HashMap<String, Plot>()
|
||||
// route(route) {
|
||||
// //Update websocket
|
||||
// webSocket("ws/{id}") {
|
||||
// val plotId: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||
//
|
||||
// application.log.debug("Opened server socket for $plotId")
|
||||
//
|
||||
// val plot = plots[plotId] ?: error("Plot with id='$plotId' not registered")
|
||||
//
|
||||
// try {
|
||||
// plot.collectUpdates(plotId, this, updateInterval).collect { update ->
|
||||
// val json = update.toJson()
|
||||
// outgoing.send(Frame.Text(json.toString()))
|
||||
// }
|
||||
// } catch (ex: Exception) {
|
||||
// application.log.debug("Closed server socket for $plotId")
|
||||
// }
|
||||
// }
|
||||
// //Plots in their json representation
|
||||
// get("data/{id}") {
|
||||
// val id: String = call.parameters["id"] ?: error("Plot id not defined")
|
||||
//
|
||||
// val plot: Plot? = plots[id]
|
||||
// if (plot == null) {
|
||||
// call.respond(HttpStatusCode.NotFound, "Plot with id = $id not found")
|
||||
// } else {
|
||||
// call.respondText(
|
||||
// plot.toJsonString(),
|
||||
// contentType = ContentType.Application.Json,
|
||||
// status = HttpStatusCode.OK
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// //filled pages
|
||||
// get {
|
||||
package hep.dataforge.vision.server
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.flowChanges
|
||||
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.html.respondHtml
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.content.resources
|
||||
import io.ktor.http.content.static
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import io.ktor.websocket.WebSockets
|
||||
import io.ktor.websocket.webSocket
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.createHTML
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
import kotlin.time.milliseconds
|
||||
|
||||
public class VisionServer internal constructor(
|
||||
private val visionManager: VisionManager,
|
||||
private val routing: Routing,
|
||||
private val rootRoute: String,
|
||||
) : Configurable {
|
||||
override val config: Config = Config()
|
||||
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||
public var cacheFragments: Boolean by config.boolean(true)
|
||||
|
||||
/**
|
||||
* a list of headers that should be applied to all pages
|
||||
*/
|
||||
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
|
||||
|
||||
public fun header(block: TagConsumer<*>.() -> Unit) {
|
||||
globalHeaders.add(HtmlFragment(block))
|
||||
}
|
||||
|
||||
private fun HTML.buildPage(
|
||||
visionFragment: HtmlVisionFragment<Vision>,
|
||||
title: String,
|
||||
headers: List<HtmlFragment>,
|
||||
): Map<Name, Vision> {
|
||||
lateinit var result: Map<Name, Vision>
|
||||
|
||||
head {
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
(globalHeaders + headers).forEach {
|
||||
fragment(it)
|
||||
}
|
||||
}
|
||||
title(title)
|
||||
}
|
||||
body {
|
||||
result = visionFragment(visionFragment)
|
||||
script {
|
||||
type = "text/javascript"
|
||||
|
||||
val normalizedRoute = if (rootRoute.endsWith("/")) {
|
||||
rootRoute
|
||||
} else {
|
||||
"$rootRoute/"
|
||||
}
|
||||
|
||||
src = TODO()//"${normalizedRoute}js/plotlyConnect.js"
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public fun page(
|
||||
visionFragment: HtmlVisionFragment<Vision>,
|
||||
route: String = DEFAULT_PAGE,
|
||||
title: String = "VisionForge server page '$route'",
|
||||
headers: List<HtmlFragment> = emptyList(),
|
||||
) {
|
||||
val visions = HashMap<Name, Vision>()
|
||||
|
||||
val cachedHtml: String? = if (cacheFragments) {
|
||||
createHTML(true).html {
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
}
|
||||
} else {
|
||||
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.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")
|
||||
}
|
||||
}
|
||||
//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)
|
||||
@ -95,125 +140,68 @@
|
||||
// port = origin.port
|
||||
// encodedPath = origin.uri
|
||||
// }.build()
|
||||
// call.respondHtml {
|
||||
// val normalizedRoute = if (rootRoute.endsWith("/")) {
|
||||
// rootRoute
|
||||
// } else {
|
||||
// "$rootRoute/"
|
||||
// }
|
||||
//
|
||||
// head {
|
||||
// meta {
|
||||
// charset = "utf-8"
|
||||
// (globalHeaders + headers).forEach {
|
||||
// it.visit(consumer)
|
||||
// }
|
||||
// script {
|
||||
// type = "text/javascript"
|
||||
// src = "${normalizedRoute}js/plotly.min.js"
|
||||
// }
|
||||
// script {
|
||||
// type = "text/javascript"
|
||||
// src = "${normalizedRoute}js/plotlyConnect.js"
|
||||
// }
|
||||
// }
|
||||
// title(title)
|
||||
// }
|
||||
// body {
|
||||
// val container =
|
||||
// ServerPlotlyRenderer(url, updateMode, updateInterval, embedData) { plotId, plot ->
|
||||
// plots[plotId] = plot
|
||||
// }
|
||||
// with(plotlyFragment) {
|
||||
// render(container)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public fun page(
|
||||
// route: String = DEFAULT_PAGE,
|
||||
// title: String = "Plotly server page '$route'",
|
||||
// headers: List<HtmlFragment> = emptyList(),
|
||||
// content: FlowContent.(renderer: PlotlyRenderer) -> Unit,
|
||||
// ) {
|
||||
// page(PlotlyFragment(content), route, title, headers)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// 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()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * Attach plotly application to given server
|
||||
// */
|
||||
//public fun Application.visionModule(route: String = DEFAULT_PAGE): VisionServer {
|
||||
// if (featureOrNull(WebSockets) == null) {
|
||||
// install(WebSockets)
|
||||
// }
|
||||
//
|
||||
// if (featureOrNull(CORS) == null) {
|
||||
// install(CORS) {
|
||||
// anyHost()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// val routing = routing {
|
||||
// route(route) {
|
||||
// static {
|
||||
// resources()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return VisionServer(routing, route)
|
||||
//}
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * Configure server to start sending updates in push mode. Does not affect loaded pages
|
||||
// */
|
||||
//public fun VisionServer.pushUpdates(interval: Long = 100): VisionServer = apply {
|
||||
// updateMode = ServerUpdateMode.PUSH
|
||||
// updateInterval = interval
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Configure client to request regular updates from server. Pull updates are more expensive than push updates since
|
||||
// * they contain the full plot data and server can't decide what to send.
|
||||
// */
|
||||
//public fun VisionServer.pullUpdates(interval: Long = 1000): VisionServer = apply {
|
||||
// updateMode = ServerUpdateMode.PULL
|
||||
// updateInterval = interval
|
||||
//}
|
||||
//
|
||||
/////**
|
||||
//// * Start static server (updates via reload)
|
||||
//// */
|
||||
////@OptIn(KtorExperimentalAPI::class)
|
||||
////public fun Plotly.serve(
|
||||
//// scope: CoroutineScope = GlobalScope,
|
||||
//// host: String = "localhost",
|
||||
//// port: Int = 7777,
|
||||
//// block: PlotlyServer.() -> Unit,
|
||||
////): ApplicationEngine = scope.embeddedServer(io.ktor.server.cio.CIO, port, host) {
|
||||
//// plotlyModule().apply(block)
|
||||
////}.start()
|
||||
//
|
||||
//
|
||||
//public fun ApplicationEngine.show() {
|
||||
// val connector = environment.connectors.first()
|
||||
// val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
||||
// Desktop.getDesktop().browse(uri)
|
||||
//}
|
||||
//
|
||||
//public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
||||
if (cachedHtml == null) {
|
||||
call.respondHtml {
|
||||
visions.putAll(buildPage(visionFragment, title, headers))
|
||||
}
|
||||
} else {
|
||||
call.respondText(cachedHtml, ContentType.Text.Html.withCharset(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun page(
|
||||
route: String = DEFAULT_PAGE,
|
||||
title: String = "Plotly server page '$route'",
|
||||
headers: List<HtmlFragment> = emptyList(),
|
||||
content: HtmlOutputScope<*, Vision>.() -> Unit,
|
||||
) {
|
||||
page(buildVisionFragment(content), route, title, headers)
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach plotly application to given server
|
||||
*/
|
||||
public fun Application.visionModule(context: Context, route: String = DEFAULT_PAGE): VisionServer {
|
||||
if (featureOrNull(WebSockets) == null) {
|
||||
install(WebSockets)
|
||||
}
|
||||
|
||||
if (featureOrNull(CORS) == null) {
|
||||
install(CORS) {
|
||||
anyHost()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val routing = routing {
|
||||
route(route) {
|
||||
static {
|
||||
resources()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val visionManager = context.plugins.fetch(VisionManager)
|
||||
|
||||
return VisionServer(visionManager, routing, route)
|
||||
}
|
||||
|
||||
public fun ApplicationEngine.show() {
|
||||
val connector = environment.connectors.first()
|
||||
val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
||||
Desktop.getDesktop().browse(uri)
|
||||
}
|
||||
|
||||
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
@ -14,11 +14,5 @@ kotlin {
|
||||
api(project(":visionforge-core"))
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation(npm("three", "0.122.0"))
|
||||
implementation(npm("three-csg-ts", "1.0.1"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.html.HtmlVisionBinding
|
||||
import hep.dataforge.vision.html.ElementVisionRenderer
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.visible
|
||||
@ -15,7 +15,7 @@ import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
import info.laht.threekt.objects.Group as ThreeGroup
|
||||
|
||||
public class ThreePlugin : AbstractPlugin(), HtmlVisionBinding<Solid> {
|
||||
public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer<Solid> {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
public val solidManager: SolidManager by require(SolidManager)
|
||||
@ -122,7 +122,7 @@ public class ThreePlugin : AbstractPlugin(), HtmlVisionBinding<Solid> {
|
||||
attach(element)
|
||||
}
|
||||
|
||||
override fun bind(element: Element, vision: Solid) {
|
||||
override fun render(element: Element, vision: Solid) {
|
||||
createCanvas(element).render(vision)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user