0.2.0 #71

Merged
altavir merged 139 commits from dev into master 2022-01-24 09:44:18 +03:00
20 changed files with 199 additions and 167 deletions
Showing only changes of commit 104e8f8f6f - Show all commits

View File

@ -1,7 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.project") id("ru.mipt.npm.gradle.project")
// kotlin("multiplatform") version "1.5.30" apply false
// kotlin("js") version "1.5.30" apply false
} }
val dataforgeVersion by extra("0.5.2") val dataforgeVersion by extra("0.5.2")

View File

@ -46,6 +46,7 @@ private object RootDecoder {
private val refCache: List<RefEntry>, private val refCache: List<RefEntry>,
) : KSerializer<T> by tSerializer { ) : KSerializer<T> by tSerializer {
@Suppress("UNCHECKED_CAST")
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
val input = decoder as JsonDecoder val input = decoder as JsonDecoder
val element = input.decodeJsonElement() val element = input.decodeJsonElement()

View File

@ -9,7 +9,7 @@ import org.w3c.files.FileReader
import org.w3c.files.get import org.w3c.files.get
import react.Props import react.Props
import react.dom.h2 import react.dom.h2
import react.functionComponent import react.fc
import react.useMemo import react.useMemo
import react.useState import react.useState
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -34,7 +34,7 @@ external interface GDMLAppProps : Props {
} }
@JsExport @JsExport
val GDMLApp = functionComponent<GDMLAppProps>("GDMLApp") { props -> val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager } val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
var deferredVision: Deferred<Solid?> by useState { var deferredVision: Deferred<Solid?> by useState {
CompletableDeferred(props.vision) CompletableDeferred(props.vision)

View File

@ -3,7 +3,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.Props import react.Props
import react.functionComponent import react.fc
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.plotly.layout import space.kscience.plotly.layout
import space.kscience.plotly.models.Trace import space.kscience.plotly.models.Trace
@ -20,7 +20,7 @@ external interface DemoProps : Props {
var context: Context var context: Context
} }
val GravityDemo = functionComponent<DemoProps> { props -> val GravityDemo = fc<DemoProps> { props ->
val velocityTrace = Trace{ val velocityTrace = Trace{
name = "velocity" name = "velocity"
} }

View File

@ -7,7 +7,7 @@ import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import react.Props import react.Props
import react.functionComponent import react.fc
import react.useEffect import react.useEffect
import react.useRef import react.useRef
import space.kscience.visionforge.markup.VisionOfMarkup import space.kscience.visionforge.markup.VisionOfMarkup
@ -20,7 +20,7 @@ external interface MarkupProps : Props {
var markup: VisionOfMarkup? var markup: VisionOfMarkup?
} }
val Markup = functionComponent<MarkupProps>("Markup") { props -> val Markup = fc<MarkupProps>("Markup") { props ->
val elementRef = useRef<Element>(null) val elementRef = useRef<Element>(null)
useEffect(props.markup, elementRef) { useEffect(props.markup, elementRef) {

View File

@ -14,7 +14,7 @@ external interface PlotlyProps : Props {
} }
val Plotly = functionComponent<PlotlyProps>("Plotly") { props -> val Plotly = fc<PlotlyProps>("Plotly") { props ->
val elementRef = useRef<Element>(null) val elementRef = useRef<Element>(null)
useEffect(props.plot, elementRef) { useEffect(props.plot, elementRef) {

View File

@ -52,8 +52,6 @@ kotlin {
jsMain { jsMain {
dependencies { dependencies {
implementation(project(":ui:ring")) implementation(project(":ui:ring"))
implementation(npmlibs.ktor.client.js)
implementation(npmlibs.ktor.client.serialization)
implementation(project(":visionforge-threejs")) implementation(project(":visionforge-threejs"))
//implementation(devNpm("webpack-bundle-analyzer", "4.4.0")) //implementation(devNpm("webpack-bundle-analyzer", "4.4.0"))
} }

View File

@ -1,17 +1,19 @@
package ru.mipt.npm.muon.monitor package ru.mipt.npm.muon.monitor
import io.ktor.client.HttpClient import kotlinx.browser.window
import io.ktor.client.request.get
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.Json
import org.w3c.fetch.RequestInit
import react.Props import react.Props
import react.dom.attrs import react.dom.attrs
import react.dom.button import react.dom.button
import react.dom.p import react.dom.p
import react.functionComponent import react.fc
import react.useMemo import react.useMemo
import react.useState import react.useState
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -31,13 +33,12 @@ import kotlin.math.PI
external interface MMAppProps : Props { external interface MMAppProps : Props {
var model: Model var model: Model
var context: Context var context: Context
var connection: HttpClient
var selected: Name? var selected: Name?
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@JsExport @JsExport
val MMApp = functionComponent<MMAppProps>("Muon monitor") { props -> val MMApp = fc<MMAppProps>("Muon monitor") { props ->
val mmOptions = useMemo { val mmOptions = useMemo {
Canvas3DOptions { Canvas3DOptions {
@ -75,9 +76,21 @@ val MMApp = functionComponent<MMAppProps>("Muon monitor") { props ->
attrs { attrs {
onClickFunction = { onClickFunction = {
context.launch { context.launch {
val event = props.connection.get<Event>( // val event = props.connection.get<Event>(
"http://localhost:8080/event" // "http://localhost:8080/event"
) // )
val event = window.fetch(
"http://localhost:8080/event",
RequestInit("GET")
).then { response ->
if (response.ok) {
response.text()
} else {
error("Failed to get event")
}
}.then { body ->
Json.decodeFromString(Event.serializer(), body)
}.await()
events = events + event events = events + event
props.model.displayEvent(event) props.model.displayEvent(event)
} }

View File

@ -1,10 +1,6 @@
package ru.mipt.npm.muon.monitor package ru.mipt.npm.muon.monitor
import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.browser.document import kotlinx.browser.document
import react.child
import react.dom.render import react.dom.render
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
@ -15,12 +11,6 @@ import space.kscience.visionforge.startApplication
private class MMDemoApp : Application { private class MMDemoApp : Application {
private val connection = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
override fun start(state: Map<String, Any>) { override fun start(state: Map<String, Any>) {
val context = Context("MM-demo") { val context = Context("MM-demo") {
@ -35,7 +25,6 @@ private class MMDemoApp : Application {
child(MMApp) { child(MMApp) {
attrs { attrs {
this.model = model this.model = model
this.connection = this@MMDemoApp.connection
this.context = context this.context = context
} }
} }

View File

@ -35,7 +35,7 @@ fun main() {
} }
} }
server.show() server.openInBrowser()
GlobalScope.launch { GlobalScope.launch {
while (isActive) { while (isActive) {

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -9,12 +9,12 @@ import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import org.w3c.files.Blob import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag import org.w3c.files.BlobPropertyBag
import react.FunctionComponent import react.FC
import react.Props import react.Props
import react.RBuilder import react.RBuilder
import react.dom.attrs import react.dom.attrs
import react.dom.button import react.dom.button
import react.functionComponent import react.fc
import space.kscience.dataforge.meta.withDefault import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString import space.kscience.visionforge.encodeToString
@ -47,7 +47,7 @@ public external interface CanvasControlsProps : Props {
public var vision: Vision? public var vision: Vision?
} }
public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionComponent("CanvasControls") { props -> public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
css { css {

View File

@ -18,7 +18,7 @@ public external interface TabProps : PropsWithChildren {
} }
@JsExport @JsExport
public val Tab: FunctionComponent<TabProps> = functionComponent { props -> public val Tab: FC<TabProps> = fc { props ->
props.children() props.children()
} }
@ -27,7 +27,7 @@ public external interface TabPaneProps : PropsWithChildren {
} }
@JsExport @JsExport
public val TabPane: FunctionComponent<TabPaneProps> = functionComponent("TabPane") { props -> public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
var activeTab: String? by useState(props.activeTab) var activeTab: String? by useState(props.activeTab)
val children: Array<out ReactElement?> = Children.map(props.children) { val children: Array<out ReactElement?> = Children.map(props.children) {

View File

@ -2,11 +2,11 @@ package space.kscience.visionforge.bootstrap
import kotlinx.css.* import kotlinx.css.*
import kotlinx.css.properties.border import kotlinx.css.properties.border
import react.FunctionComponent import react.FC
import react.PropsWithChildren import react.PropsWithChildren
import react.RBuilder import react.RBuilder
import react.dom.h2 import react.dom.h2
import react.functionComponent import react.fc
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
@ -24,7 +24,7 @@ public external interface ThreeControlsProps : PropsWithChildren {
} }
@JsExport @JsExport
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionComponent { props -> public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
tabPane(if (props.selected != null) "Properties" else null) { tabPane(if (props.selected != null) "Properties" else null) {
tab("Canvas") { tab("Canvas") {
card("Canvas configuration") { card("Canvas configuration") {

View File

@ -42,7 +42,7 @@ public external interface MetaViewerProps : Props {
public var descriptor: MetaDescriptor? public var descriptor: MetaDescriptor?
} }
private val MetaViewerItem: FunctionComponent<MetaViewerProps> = functionComponent("MetaViewerItem") { props -> private val MetaViewerItem: FC<MetaViewerProps> = fc("MetaViewerItem") { props ->
metaViewerItem(props) metaViewerItem(props)
} }
@ -127,7 +127,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
} }
@JsExport @JsExport
public val MetaViewer: FunctionComponent<MetaViewerProps> = functionComponent("MetaViewer") { props -> public val MetaViewer: FC<MetaViewerProps> = fc("MetaViewer") { props ->
child(MetaViewerItem) { child(MetaViewerItem) {
attrs { attrs {
this.key = "" this.key = ""

View File

@ -76,8 +76,7 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styled
} }
@JsExport @JsExport
public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> = public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props ->
functionComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected } var selected by useState { props.selected }
var solid: Solid? by useState(null) var solid: Solid? by useState(null)

View File

@ -9,12 +9,12 @@ import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import org.w3c.files.Blob import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag import org.w3c.files.BlobPropertyBag
import react.FunctionComponent import react.FC
import react.Props import react.Props
import react.RBuilder import react.RBuilder
import react.dom.attrs import react.dom.attrs
import react.dom.button import react.dom.button
import react.functionComponent import react.fc
import ringui.Island import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
@ -52,7 +52,7 @@ internal external interface CanvasControlsProps : Props {
public var vision: Vision? public var vision: Vision?
} }
internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functionComponent("CanvasControls") { props -> internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
css { css {
@ -94,7 +94,7 @@ public external interface ThreeControlsProps : Props {
} }
@JsExport @JsExport
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionComponent { props -> public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
SmartTabs("Tree") { SmartTabs("Tree") {
props.vision?.let { props.vision?.let {
Tab("Tree") { Tab("Tree") {

View File

@ -15,7 +15,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api("space.kscience:dataforge-context:$dataforgeVersion") api("space.kscience:dataforge-context:$dataforgeVersion")
api("org.jetbrains.kotlinx:kotlinx-html:${ru.mipt.npm.gradle.KScienceVersions.htmlVersion}") api(npmlibs.kotlinx.html)
api("org.jetbrains.kotlin-wrappers:kotlin-css") api("org.jetbrains.kotlin-wrappers:kotlin-css")
} }
} }

View File

@ -2,12 +2,9 @@ plugins {
id("ru.mipt.npm.gradle.jvm") id("ru.mipt.npm.gradle.jvm")
} }
val ktorVersion = ru.mipt.npm.gradle.KScienceVersions.ktorVersion
dependencies { dependencies {
api(project(":visionforge-core")) api(project(":visionforge-core"))
api("io.ktor:ktor-server-cio:$ktorVersion") api(npmlibs.ktor.server.cio)
//api("io.ktor:ktor-server-netty:$ktorVersion") api(npmlibs.ktor.html.builder)
api("io.ktor:ktor-html-builder:$ktorVersion") api(npmlibs.ktor.websockets)
api("io.ktor:ktor-websockets:$ktorVersion")
} }

View File

@ -38,10 +38,7 @@ import space.kscience.visionforge.html.*
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
import java.awt.Desktop import java.awt.Desktop
import java.net.URI import java.net.URI
import kotlin.collections.set
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.ExperimentalTime
/** /**
@ -53,10 +50,30 @@ public class VisionServer internal constructor(
private val rootRoute: String, private val rootRoute: String,
) : Configurable, CoroutineScope by application { ) : Configurable, CoroutineScope by application {
override val meta: ObservableMutableMeta = MutableMeta() override val meta: ObservableMutableMeta = MutableMeta()
/**
* Update minimal interval between updates in milliseconds (if there are no updates, push will not happen
*/
public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY) public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY)
/**
* Cache page fragments. If false, pages will be reconstructed on each call. Default: `true`
*/
public var cacheFragments: Boolean by meta.boolean(true) public var cacheFragments: Boolean by meta.boolean(true)
/**
* Embed the initial state of the vision inside its html tag. Default: `true`
*/
public var dataEmbed: Boolean by meta.boolean(true, Name.parse("data.embed")) public var dataEmbed: Boolean by meta.boolean(true, Name.parse("data.embed"))
/**
* Fetch data on vision load. Overrides embedded data. Default: `false`
*/
public var dataFetch: Boolean by meta.boolean(false, Name.parse("data.fetch")) public var dataFetch: Boolean by meta.boolean(false, Name.parse("data.fetch"))
/**
* Connect to server to get pushes. The address of the server is embedded in the tag. Default: `true`
*/
public var dataConnect: Boolean by meta.boolean(true, Name.parse("data.connect")) public var dataConnect: Boolean by meta.boolean(true, Name.parse("data.connect"))
/** /**
@ -64,6 +81,9 @@ public class VisionServer internal constructor(
*/ */
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList() private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
/**
* Add a header to all pages produced by this server
*/
public fun header(block: TagConsumer<*>.() -> Unit) { public fun header(block: TagConsumer<*>.() -> Unit) {
globalHeaders.add(block) globalHeaders.add(block)
} }
@ -102,7 +122,7 @@ public class VisionServer internal constructor(
* Server a map of visions without providing explicit html page for them * Server a map of visions without providing explicit html page for them
*/ */
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
public fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route { internal fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
application.log.info("Serving visions $visions at $route") application.log.info("Serving visions $visions at $route")
//Update websocket //Update websocket
@ -158,7 +178,12 @@ public class VisionServer internal constructor(
* Create a static html page and serve visions produced in the process * Create a static html page and serve visions produced in the process
*/ */
@DFExperimental @DFExperimental
public fun createHtmlAndServe(route: String, title: String, headers: List<HtmlFragment>, visionFragment: HtmlVisionFragment): String{ public fun createHtmlAndServe(
route: String,
title: String,
headers: List<HtmlFragment>,
visionFragment: HtmlVisionFragment,
): String {
val htmlString = createHTML().apply { val htmlString = createHTML().apply {
html { html {
visionPage(title, headers, visionFragment).also { visionPage(title, headers, visionFragment).also {
@ -171,7 +196,7 @@ public class VisionServer internal constructor(
} }
/** /**
* Serv visions in a given [route] without providing a page template * Serve visions in a given [route] without providing a page template
*/ */
public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit { public fun serveVisions(route: String, visions: Map<Name, Vision>): Unit {
application.routing { application.routing {
@ -245,6 +270,9 @@ public inline fun VisionServer.useScript(src: String, crossinline block: SCRIPT.
} }
} }
/**
* Use css with given stylesheet link as a global header for all pages.
*/
public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() -> Unit = {}) { public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() -> Unit = {}) {
header { header {
link { link {
@ -256,7 +284,7 @@ public inline fun VisionServer.useCss(href: String, crossinline block: LINK.() -
} }
/** /**
* Attach plotly application to given server * Attach VisionForge server application to given server
*/ */
public fun Application.visionServer(context: Context, route: String = DEFAULT_PAGE): VisionServer { public fun Application.visionServer(context: Context, route: String = DEFAULT_PAGE): VisionServer {
if (featureOrNull(WebSockets) == null) { if (featureOrNull(WebSockets) == null) {
@ -286,6 +314,9 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA
return VisionServer(visionManager, this, route) return VisionServer(visionManager, this, route)
} }
/**
* Start a stand-alone VisionForge server at given host/port
*/
public fun VisionManager.serve( public fun VisionManager.serve(
host: String = "localhost", host: String = "localhost",
port: Int = 7777, port: Int = 7777,
@ -294,10 +325,16 @@ public fun VisionManager.serve(
visionServer(context).apply(block) visionServer(context).apply(block)
}.start() }.start()
public fun ApplicationEngine.show() { /**
* Connect to a given Ktor server using browser
*/
public fun ApplicationEngine.openInBrowser() {
val connector = environment.connectors.first() val connector = environment.connectors.first()
val uri = URI("http", null, connector.host, connector.port, null, null, null) val uri = URI("http", null, connector.host, connector.port, null, null, null)
Desktop.getDesktop().browse(uri) Desktop.getDesktop().browse(uri)
} }
/**
* Stop the server with default timeouts
*/
public fun ApplicationEngine.close(): Unit = stop(1000, 5000) public fun ApplicationEngine.close(): Unit = stop(1000, 5000)