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 {
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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,19 @@
package ru.mipt.npm.muon.monitor
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import kotlinx.browser.window
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.await
import kotlinx.coroutines.launch
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.Json
import org.w3c.fetch.RequestInit
import react.Props
import react.dom.attrs
import react.dom.button
import react.dom.p
import react.functionComponent
import react.fc
import react.useMemo
import react.useState
import space.kscience.dataforge.context.Context
@ -31,13 +33,12 @@ import kotlin.math.PI
external interface MMAppProps : Props {
var model: Model
var context: Context
var connection: HttpClient
var selected: Name?
}
@OptIn(DelicateCoroutinesApi::class)
@JsExport
val MMApp = functionComponent<MMAppProps>("Muon monitor") { props ->
val MMApp = fc<MMAppProps>("Muon monitor") { props ->
val mmOptions = useMemo {
Canvas3DOptions {
@ -75,9 +76,21 @@ val MMApp = functionComponent<MMAppProps>("Muon monitor") { props ->
attrs {
onClickFunction = {
context.launch {
val event = props.connection.get<Event>(
"http://localhost:8080/event"
)
// val event = props.connection.get<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
props.model.displayEvent(event)
}
@ -102,7 +115,7 @@ val MMApp = functionComponent<MMAppProps>("Muon monitor") { props ->
}
+" : "
styledSpan {
css{
css {
color = Color.blue
}
+event.hits.toString()

View File

@ -1,10 +1,6 @@
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 react.child
import react.dom.render
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
@ -15,15 +11,9 @@ import space.kscience.visionforge.startApplication
private class MMDemoApp : Application {
private val connection = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
override fun start(state: Map<String, Any>) {
val context = Context("MM-demo"){
val context = Context("MM-demo") {
plugin(ThreePlugin)
}
val visionManager = context.fetch(VisionManager)
@ -35,7 +25,6 @@ private class MMDemoApp : Application {
child(MMApp) {
attrs {
this.model = model
this.connection = this@MMDemoApp.connection
this.context = context
}
}

View File

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

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View File

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

View File

@ -18,7 +18,7 @@ public external interface TabProps : PropsWithChildren {
}
@JsExport
public val Tab: FunctionComponent<TabProps> = functionComponent { props ->
public val Tab: FC<TabProps> = fc { props ->
props.children()
}
@ -27,7 +27,7 @@ public external interface TabPaneProps : PropsWithChildren {
}
@JsExport
public val TabPane: FunctionComponent<TabPaneProps> = functionComponent("TabPane") { props ->
public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
var activeTab: String? by useState(props.activeTab)
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.properties.border
import react.FunctionComponent
import react.FC
import react.PropsWithChildren
import react.RBuilder
import react.dom.h2
import react.functionComponent
import react.fc
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
@ -24,7 +24,7 @@ public external interface ThreeControlsProps : PropsWithChildren {
}
@JsExport
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionComponent { props ->
public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
tabPane(if (props.selected != null) "Properties" else null) {
tab("Canvas") {
card("Canvas configuration") {

View File

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

View File

@ -76,110 +76,109 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styled
}
@JsExport
public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> =
functionComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
var solid: Solid? by useState(null)
public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
var solid: Solid? by useState(null)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await().also {
it?.root(props.context.visionManager)
}
}
}
val onSelect: (Name?) -> Unit = {
selected = it
}
val options = useMemo(props.options) {
(props.options?: Canvas3DOptions()).apply {
this.onSelect = onSelect
}
}
val selectedVision: Vision? = useMemo(props.builderOfSolid, selected) {
selected?.let {
when {
it.isEmpty() -> solid
else -> (solid as? VisionGroup)?.get(it)
}
}
}
flexRow {
css {
height = 100.pct
width = 100.pct
flexWrap = FlexWrap.wrap
alignItems = Align.stretch
alignContent = Align.stretch
}
flexColumn {
css {
height = 100.pct
minWidth = 600.px
flex(10.0, 1.0, FlexBasis("600px"))
position = Position.relative
}
if (solid == null) {
LoaderScreen {
attrs {
message = "Loading Three vision"
}
}
} else {
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = solid
this.selected = selected
this.options = options
}
}
}
selectedVision?.let { vision ->
styledDiv {
css {
position = Position.absolute
top = 5.px
right = 5.px
width = 450.px
}
Island {
IslandHeader {
attrs {
border = true
}
nameCrumbs(selected) { selected = it }
}
IslandContent {
propertyEditor(
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = vision.descriptor,
key = selected
)
}
}
}
}
}
flexColumn {
css {
padding(4.px)
minWidth = 400.px
height = 100.pct
overflowY = Overflow.auto
flex(1.0, 10.0, FlexBasis("300px"))
}
ringThreeControls(options, solid, selected, onSelect, additionalTabs = props.additionalTabs)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await().also {
it?.root(props.context.visionManager)
}
}
}
val onSelect: (Name?) -> Unit = {
selected = it
}
val options = useMemo(props.options) {
(props.options ?: Canvas3DOptions()).apply {
this.onSelect = onSelect
}
}
val selectedVision: Vision? = useMemo(props.builderOfSolid, selected) {
selected?.let {
when {
it.isEmpty() -> solid
else -> (solid as? VisionGroup)?.get(it)
}
}
}
flexRow {
css {
height = 100.pct
width = 100.pct
flexWrap = FlexWrap.wrap
alignItems = Align.stretch
alignContent = Align.stretch
}
flexColumn {
css {
height = 100.pct
minWidth = 600.px
flex(10.0, 1.0, FlexBasis("600px"))
position = Position.relative
}
if (solid == null) {
LoaderScreen {
attrs {
message = "Loading Three vision"
}
}
} else {
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = solid
this.selected = selected
this.options = options
}
}
}
selectedVision?.let { vision ->
styledDiv {
css {
position = Position.absolute
top = 5.px
right = 5.px
width = 450.px
}
Island {
IslandHeader {
attrs {
border = true
}
nameCrumbs(selected) { selected = it }
}
IslandContent {
propertyEditor(
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = vision.descriptor,
key = selected
)
}
}
}
}
}
flexColumn {
css {
padding(4.px)
minWidth = 400.px
height = 100.pct
overflowY = Overflow.auto
flex(1.0, 10.0, FlexBasis("300px"))
}
ringThreeControls(options, solid, selected, onSelect, additionalTabs = props.additionalTabs)
}
}
}

View File

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

View File

@ -15,7 +15,7 @@ kotlin {
commonMain {
dependencies {
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")
}
}

View File

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

View File

@ -38,10 +38,7 @@ import space.kscience.visionforge.html.*
import space.kscience.visionforge.three.server.VisionServer.Companion.DEFAULT_PAGE
import java.awt.Desktop
import java.net.URI
import kotlin.collections.set
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.ExperimentalTime
/**
@ -53,10 +50,30 @@ public class VisionServer internal constructor(
private val rootRoute: String,
) : Configurable, CoroutineScope by application {
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)
/**
* Cache page fragments. If false, pages will be reconstructed on each call. Default: `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"))
/**
* Fetch data on vision load. Overrides embedded data. Default: `false`
*/
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"))
/**
@ -64,6 +81,9 @@ public class VisionServer internal constructor(
*/
private val globalHeaders: ArrayList<HtmlFragment> = ArrayList()
/**
* Add a header to all pages produced by this server
*/
public fun header(block: TagConsumer<*>.() -> Unit) {
globalHeaders.add(block)
}
@ -73,7 +93,7 @@ public class VisionServer internal constructor(
headers: List<HtmlFragment>,
visionFragment: HtmlVisionFragment,
): Map<Name, Vision> {
var visionMap: Map<Name,Vision>? = null
var visionMap: Map<Name, Vision>? = null
head {
meta {
@ -102,7 +122,7 @@ public class VisionServer internal constructor(
* Server a map of visions without providing explicit html page for them
*/
@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")
//Update websocket
@ -158,8 +178,13 @@ public class VisionServer internal constructor(
* Create a static html page and serve visions produced in the process
*/
@DFExperimental
public fun createHtmlAndServe(route: String, title: String, headers: List<HtmlFragment>, visionFragment: HtmlVisionFragment): String{
val htmlString = createHTML().apply {
public fun createHtmlAndServe(
route: String,
title: String,
headers: List<HtmlFragment>,
visionFragment: HtmlVisionFragment,
): String {
val htmlString = createHTML().apply {
html {
visionPage(title, headers, visionFragment).also {
serveVisions(route, it)
@ -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 {
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 = {}) {
header {
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 {
if (featureOrNull(WebSockets) == null) {
@ -286,6 +314,9 @@ public fun Application.visionServer(context: Context, route: String = DEFAULT_PA
return VisionServer(visionManager, this, route)
}
/**
* Start a stand-alone VisionForge server at given host/port
*/
public fun VisionManager.serve(
host: String = "localhost",
port: Int = 7777,
@ -294,10 +325,16 @@ public fun VisionManager.serve(
visionServer(context).apply(block)
}.start()
public fun ApplicationEngine.show() {
/**
* Connect to a given Ktor server using browser
*/
public fun ApplicationEngine.openInBrowser() {
val connector = environment.connectors.first()
val uri = URI("http", null, connector.host, connector.port, null, null, null)
Desktop.getDesktop().browse(uri)
}
/**
* Stop the server with default timeouts
*/
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)