Compare commits

...

3 Commits

Author SHA1 Message Date
f0db286ce6 Finalize refactor for 0.4 2024-02-16 18:21:09 +03:00
d90c1edc6c refactor compose-html 2024-02-12 20:04:10 +03:00
38d6a9c419 rename modules 2024-02-12 16:44:15 +03:00
113 changed files with 1304 additions and 2628 deletions

View File

@ -5,8 +5,6 @@
### Added
### Changed
- **Breaking API** Move vision cache to upper level for renderers to avoid re-creating visions for page reload.
- **Breaking API** Forms refactor
### Deprecated
@ -16,6 +14,19 @@
### Security
## 0.4.0 - 2024-02-16
### Added
- Added compose-mpp rendering.
### Changed
- **Breaking API** Move vision cache to upper level for renderers to avoid re-creating visions for page reload.
- **Breaking API** Forms refactor.
- **Breaking API** Migrated from React to Compose-html.
- **Breaking API** changed js package for `visionforge-core` to `space.kscience.visionforge.html` to avoid mixing html and generic parts.
## 0.3.0 - 2023-12-23
### Added

View File

@ -70,7 +70,11 @@ To learn more about DataForge, please consult the following URLs:
>
> **Maturity**: EXPERIMENTAL
### [ui](ui)
### [visionforge-compose-html](visionforge-compose-html)
>
> **Maturity**: EXPERIMENTAL
### [visionforge-compose-multiplatform](visionforge-compose-multiplatform)
>
> **Maturity**: EXPERIMENTAL
@ -111,6 +115,10 @@ To learn more about DataForge, please consult the following URLs:
>
> **Maturity**: EXPERIMENTAL
### [demo/compose-desktop-demo](demo/compose-desktop-demo)
>
> **Maturity**: EXPERIMENTAL
### [demo/gdml](demo/gdml)
>
> **Maturity**: EXPERIMENTAL
@ -135,22 +143,6 @@ To learn more about DataForge, please consult the following URLs:
>
> **Maturity**: EXPERIMENTAL
### [ui/bootstrap](ui/bootstrap)
>
> **Maturity**: EXPERIMENTAL
### [ui/compose](ui/compose)
>
> **Maturity**: EXPERIMENTAL
### [ui/react](ui/react)
>
> **Maturity**: EXPERIMENTAL
### [ui/ring](ui/ring)
>
> **Maturity**: EXPERIMENTAL
### [visionforge-jupyter/visionforge-jupyter-common](visionforge-jupyter/visionforge-jupyter-common)
> Jupyter api artifact including all common modules
>

View File

@ -10,7 +10,7 @@ val dataforgeVersion by extra("0.8.0")
allprojects {
group = "space.kscience"
version = "0.4.0-dev-3"
version = "0.4.0"
}
subprojects {

View File

@ -0,0 +1,4 @@
# Module compose-desktop-demo

View File

@ -0,0 +1,17 @@
public final class ComposableSingletons$MainKt {
public static final field INSTANCE LComposableSingletons$MainKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public static field lambda-2 Lkotlin/jvm/functions/Function3;
public static field lambda-3 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$compose_desktop_demo ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-2$compose_desktop_demo ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$compose_desktop_demo ()Lkotlin/jvm/functions/Function3;
}
public final class MainKt {
public static final fun App (Landroidx/compose/runtime/Composer;I)V
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
}

View File

@ -0,0 +1,40 @@
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose)
}
kscience {
jvm()
useCoroutines()
commonMain{
implementation(projects.visionforgeSolid)
}
jvmMain {
implementation(projects.visionforgeComposeMultiplatform)
}
}
kotlin{
explicitApi = null
sourceSets{
commonMain{
dependencies {
implementation(compose.desktop.currentOs)
api(compose.preview)
}
}
}
}
compose{
desktop{
desktop {
application {
mainClass = "MainKt"
}
}
}
}

View File

@ -0,0 +1,33 @@
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import space.kscience.dataforge.meta.set
import space.kscience.visionforge.compose.PropertyEditor
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
@Composable
@Preview
fun App(){
val options = remember {
Canvas3DOptions{
meta["custom.field"] = 32
}
}
PropertyEditor(
properties = options.meta,
descriptor = Canvas3DOptions.descriptor,
expanded = true
)
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
MaterialTheme {
App()
}
}
}

View File

@ -6,14 +6,14 @@ import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.Document
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
import space.kscience.visionforge.compose.TreeStyles
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.Application
import space.kscience.visionforge.html.VisionForgeStyles
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication
private class GDMLDemoApp : Application {
@ -33,7 +33,7 @@ private class GDMLDemoApp : Application {
}
renderComposable(element) {
Style(TreeStyles)
Style(VisionForgeStyles)
Style {
"html" {
height(100.percent)

View File

@ -5,16 +5,16 @@ import org.w3c.dom.Document
import space.kscience.dataforge.context.Context
import space.kscience.plotly.models.Trace
import space.kscience.plotly.scatter
import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
import space.kscience.visionforge.compose.Tabs
import space.kscience.visionforge.compose.TreeStyles
import space.kscience.visionforge.html.Application
import space.kscience.visionforge.html.Tabs
import space.kscience.visionforge.html.VisionForgeStyles
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.compose.ThreeView
import space.kscience.visionforge.startApplication
import kotlin.random.Random
fun Trace.appendXYLatest(x: Number, y: Number, history: Int = 400, xErr: Number? = null, yErr: Number? = null) {
@ -40,7 +40,7 @@ private class JsPlaygroundApp : Application {
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
renderComposable(element) {
Style(TreeStyles)
Style(VisionForgeStyles)
Div({
style {
padding(0.pt)

View File

@ -15,8 +15,8 @@ import space.kscience.plotly.Plot
import space.kscience.plotly.layout
import space.kscience.plotly.models.Trace
import space.kscience.visionforge.Colors
import space.kscience.visionforge.compose.Vision
import space.kscience.visionforge.compose.zIndex
import space.kscience.visionforge.html.Vision
import space.kscience.visionforge.html.zIndex
import space.kscience.visionforge.markup.VisionOfMarkup
import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.solid.*

View File

@ -1,4 +1,5 @@
public final class ru/mipt/npm/muon/monitor/Event {
public static final field $stable I
public static final field Companion Lru/mipt/npm/muon/monitor/Event$Companion;
public fun <init> (ILjava/util/List;Ljava/util/Collection;)V
public final fun component1 ()I
@ -15,6 +16,7 @@ public final class ru/mipt/npm/muon/monitor/Event {
}
public final class ru/mipt/npm/muon/monitor/Event$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field $stable I
public static final field INSTANCE Lru/mipt/npm/muon/monitor/Event$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@ -30,6 +32,7 @@ public final class ru/mipt/npm/muon/monitor/Event$Companion {
}
public final class ru/mipt/npm/muon/monitor/Model {
public static final field $stable I
public fun <init> (Lspace/kscience/visionforge/VisionManager;)V
public final fun displayEvent (Lru/mipt/npm/muon/monitor/Event;)V
public final fun encodeToString ()Ljava/lang/String;
@ -40,6 +43,7 @@ public final class ru/mipt/npm/muon/monitor/Model {
}
public final class ru/mipt/npm/muon/monitor/Monitor {
public static final field $stable I
public static final field CENTRAL_LAYER_Z F
public static final field GEOMETRY_TOLERANCE D
public static final field INSTANCE Lru/mipt/npm/muon/monitor/Monitor;
@ -57,6 +61,7 @@ public final class ru/mipt/npm/muon/monitor/ReadResourceKt {
}
public final class ru/mipt/npm/muon/monitor/SC1 {
public static final field $stable I
public fun <init> (Ljava/lang/String;Lspace/kscience/visionforge/solid/Float32Vector3D;FFF)V
public synthetic fun <init> (Ljava/lang/String;Lspace/kscience/visionforge/solid/Float32Vector3D;FFFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCenter ()Lspace/kscience/visionforge/solid/Float32Vector3D;
@ -67,6 +72,7 @@ public final class ru/mipt/npm/muon/monitor/SC1 {
}
public final class ru/mipt/npm/muon/monitor/SC16 {
public static final field $stable I
public fun <init> (Ljava/lang/String;Lspace/kscience/visionforge/solid/Float32Vector3D;)V
public final fun getCenter ()Lspace/kscience/visionforge/solid/Float32Vector3D;
public final fun getName ()Ljava/lang/String;
@ -81,6 +87,7 @@ public final class ru/mipt/npm/muon/monitor/server/MMServerKt {
}
public final class ru/mipt/npm/muon/monitor/sim/Cos2TrackGenerator : ru/mipt/npm/muon/monitor/sim/TrackGenerator {
public static final field $stable I
public fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;DFF)V
public synthetic fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;DFFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun generate ()Lorg/apache/commons/math3/geometry/euclidean/threed/Line;
@ -91,6 +98,7 @@ public final class ru/mipt/npm/muon/monitor/sim/Cos2TrackGenerator : ru/mipt/npm
}
public final class ru/mipt/npm/muon/monitor/sim/FixedAngleGenerator : ru/mipt/npm/muon/monitor/sim/TrackGenerator {
public static final field $stable I
public fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;DDFF)V
public synthetic fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;DDFFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun generate ()Lorg/apache/commons/math3/geometry/euclidean/threed/Line;
@ -134,6 +142,7 @@ public abstract interface class ru/mipt/npm/muon/monitor/sim/TrackGenerator {
}
public final class ru/mipt/npm/muon/monitor/sim/UniformTrackGenerator : ru/mipt/npm/muon/monitor/sim/TrackGenerator {
public static final field $stable I
public fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;FF)V
public synthetic fun <init> (Lorg/apache/commons/math3/random/RandomGenerator;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun generate ()Lorg/apache/commons/math3/geometry/euclidean/threed/Line;

View File

@ -44,8 +44,10 @@ kscience {
//implementation(devNpm("webpack-bundle-analyzer", "4.4.0"))
}
}
kotlin{
explicitApi = null
}
kotlin.explicitApi = null
application {
mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt")

View File

@ -5,11 +5,11 @@ import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.Document
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.compose.VisionForgeStyles
import space.kscience.visionforge.html.Application
import space.kscience.visionforge.html.VisionForgeStyles
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication
private class MMDemoApp : Application {

View File

@ -53,6 +53,11 @@ public final class space/kscience/visionforge/examples/GenerateSchemaKt {
public static synthetic fun main ([Ljava/lang/String;)V
}
public final class space/kscience/visionforge/examples/MarkdownVisionKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
}
public final class space/kscience/visionforge/examples/PlotlyVisionKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V

View File

@ -1,8 +1,8 @@
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.html.runVisionClient
import space.kscience.visionforge.jupyter.VFNotebookClient
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.runVisionClient
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.tables.TableVisionJsPlugin

View File

@ -4,10 +4,10 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.w3c.dom.Document
import space.kscience.visionforge.Application
import space.kscience.visionforge.html.Application
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.solid.x
import space.kscience.visionforge.solid.y
import space.kscience.visionforge.startApplication
import kotlin.random.Random
private class ThreeDemoApp : Application {

View File

@ -40,13 +40,9 @@ dependencyResolutionManagement {
}
include(
// ":ui",
// ":ui:react",
// ":ui:ring",
// ":ui:material",
// ":ui:bootstrap",
":visionforge-compose-html",
":visionforge-core",
":visionforge-compose-html",
":visionforge-compose-multiplatform",
":visionforge-solid",
// ":visionforge-fx",
":visionforge-threejs",
@ -64,6 +60,7 @@ include(
":demo:playground",
// ":demo:plotly-fx",
":demo:js-playground",
":demo:compose-desktop-demo",
":visionforge-jupyter",
":visionforge-jupyter:visionforge-jupyter-common"
)

View File

@ -1,4 +0,0 @@
# Module ui

View File

@ -1,4 +0,0 @@
# Module bootstrap

View File

@ -1,19 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
}
val dataforgeVersion: String by rootProject.extra
kscience{
js()
jsMain{
dependencies {
api(project(":visionforge-solid"))
api(project(":ui:react"))
implementation(npm("file-saver", "2.0.2"))
implementation(npm("bootstrap","4.6.0"))
implementation(npm("jquery","3.5.1"))
implementation(npm("popper.js","1.16.1"))
}
}
}

View File

@ -1,121 +0,0 @@
package space.kscience.visionforge.bootstrap
public fun useBootstrap(){
kotlinext.js.require<dynamic>("bootstrap/dist/css/bootstrap.min.css")
kotlinext.js.require<dynamic>("bootstrap")
}
//public inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
// div("card w-100") {
// div("card-body") {
// h3(classes = "card-title") { +title }
// block()
// }
// }
//}
//public typealias SectionsBuilder = MutableList<Pair<String, DIV.() -> Unit>>
//
//public fun SectionsBuilder.entry(title: String, builder: DIV.() -> Unit) {
// add(title to builder)
//}
//public fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
// div("container-fluid") {
// div("accordion") {
// this.id = id
// elements.forEachIndexed { index, (title, builder) ->
// val headerID = "${id}-${index}-heading"
// val collapseID = "${id}-${index}-collapse"
// div("card") {
// div("card-header") {
// this.id = headerID
// h5("mb-0") {
// button(classes = "btn btn-link collapsed", type = ButtonType.button) {
// attributes["data-toggle"] = "collapse"
// attributes["data-target"] = "#$collapseID"
// attributes["aria-expanded"] = "false"
// attributes["aria-controls"] = collapseID
// +title
// }
// }
// }
// div("collapse") {
// this.id = collapseID
// attributes["aria-labelledby"] = headerID
// attributes["data-parent"] = "#$id"
// div("card-body", block = builder)
// }
// }
// }
// }
// }
//}
//public fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
// val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
// accordion(id, list)
//}
//public fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
// clear()
// append {
// accordion("controls") {
// entry("Settings") {
// div("row") {
// div("col-2") {
// label("checkbox-inline") {
// input(type = InputType.checkBox) {
// checked = canvas.axes.visible
// onChangeFunction = {
// canvas.axes.visible = checked
// }
// }
// +"Axes"
// }
// }
// div("col-1") {
// button {
// +"Export"
// onClickFunction = {
// val json = (canvas.content as? SolidGroup)?.let { group ->
// val visionManager = canvas.context.plugins.fetch(SolidManager).visionManager
// visionManager.encodeToString(group)
// }
// if (json != null) {
// saveData(it, "object.json", "text/json") {
// json
// }
// }
// }
// }
// }
// }
// }
// entry("Layers") {
// div("row") {
// (0..11).forEach { layer ->
// div("col-1") {
// label { +layer.toString() }
// input(type = InputType.checkBox) {
// if (layer == 0) {
// checked = true
// }
// onChangeFunction = {
// if (checked) {
// canvas.camera.layers.enable(layer)
// } else {
// canvas.camera.layers.disable(layer)
// }
// }
// }
// }
// }
// }
// }
// }
// block()
// }
//}

View File

@ -1,77 +0,0 @@
package space.kscience.visionforge.bootstrap
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import react.FC
import react.Props
import react.RBuilder
import react.dom.attrs
import react.dom.button
import react.fc
import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
event.stopPropagation();
event.preventDefault();
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8"))
fileSaver.saveAs(blob, fileName)
}
public fun RBuilder.canvasControls(canvasOptions: Canvas3DOptions, vision: Vision?) {
child(CanvasControls) {
attrs {
this.canvasOptions = canvasOptions
this.vision = vision
}
}
}
public external interface CanvasControlsProps : Props {
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
}
public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn {
flexRow {
css {
border = Border(1.px, BorderStyle.solid, Color.blue)
padding = Padding(4.px)
}
props.vision?.let { vision ->
button {
+"Export"
attrs {
onClickFunction = {
val json = vision.encodeToString()
saveData(it, "object.json", "text/json") {
json
}
}
}
}
}
}
@OptIn(DelicateCoroutinesApi::class)
propertyEditor(
scope = props.vision?.manager?.context ?: GlobalScope,
properties = props.canvasOptions.meta,
descriptor = Canvas3DOptions.descriptor,
expanded = false
)
}
}

View File

@ -1,181 +0,0 @@
package space.kscience.visionforge.bootstrap
import kotlinx.html.ButtonType
import kotlinx.html.DIV
import kotlinx.html.id
import kotlinx.html.js.onClickFunction
import react.RBuilder
import react.dom.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.length
import styled.StyledDOMBuilder
import styled.css
import styled.styledDiv
import styled.styledNav
public inline fun RBuilder.card(title: String, crossinline block: StyledDOMBuilder<DIV>.() -> Unit): Unit =
styledDiv {
css {
+"card"
+"w-100"
}
styledDiv {
css {
+"card-body"
}
h3(classes = "card-title") {
+title
}
block()
}
}
public fun RBuilder.accordion(
id: String,
elements: List<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>,
): Unit = styledDiv {
css {
+"accordion"
//+"p-1"
}
attrs {
this.id = id
}
elements.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse"
div("card p-0 m-0") {
div("card-header") {
attrs {
this.id = headerID
}
h5("mb-0") {
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
attrs {
attributes["data-toggle"] = "collapse"
attributes["data-target"] = "#$collapseID"
attributes["aria-expanded"] = "false"
attributes["aria-controls"] = collapseID
}
+title
}
}
}
div("collapse") {
attrs {
this.id = collapseID
attributes["aria-labelledby"] = headerID
attributes["data-parent"] = "#$id"
}
styledDiv {
css {
+"card-body"
}
builder()
}
}
}
}
}
public fun RBuilder.nameCrumbs(name: Name?, rootTitle: String, link: (Name) -> Unit): Unit = styledNav {
css {
+"p-0"
}
attrs {
attributes["aria-label"] = "breadcrumb"
}
ol("breadcrumb") {
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+rootTitle
attrs {
onClickFunction = {
link(Name.EMPTY)
}
}
}
}
if (name != null) {
val tokens = ArrayList<NameToken>(name.length)
name.tokens.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+token.toString()
attrs {
onClickFunction = {
console.log("Selected = $fullName")
link(fullName)
}
}
}
}
}
}
}
}
public typealias RSectionsBuilder = MutableList<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>
public fun RSectionsBuilder.entry(title: String, builder: StyledDOMBuilder<DIV>.() -> Unit) {
add(title to builder)
}
public fun RBuilder.accordion(id: String, builder: RSectionsBuilder.() -> Unit): Unit {
val list = ArrayList<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>().apply(builder)
accordion(id, list)
}
public enum class ContainerSize(public val suffix: String) {
DEFAULT(""),
SM("-sm"),
MD("-md"),
LG("-lg"),
XL("-xl"),
FLUID("-fluid")
}
public inline fun RBuilder.container(
size: ContainerSize = ContainerSize.FLUID,
block: StyledDOMBuilder<DIV>.() -> Unit,
): Unit = styledDiv {
css {
classes.add("container${size.suffix}")
}
block()
}
public enum class GridMaxSize(public val suffix: String) {
NONE(""),
SM("-sm"),
MD("-md"),
LG("-lg"),
XL("-xl")
}
public inline fun RBuilder.gridColumn(
weight: Int? = null,
maxSize: GridMaxSize = GridMaxSize.NONE,
block: StyledDOMBuilder<DIV>.() -> Unit,
): Unit = styledDiv {
val weightSuffix = weight?.let { "-$it" } ?: ""
css {
classes.add("col${maxSize.suffix}$weightSuffix")
}
block()
}
public inline fun RBuilder.gridRow(
block: StyledDOMBuilder<DIV>.() -> Unit,
): Unit = styledDiv {
css {
classes.add("row")
}
block()
}

View File

@ -1,86 +0,0 @@
package space.kscience.visionforge.bootstrap
import kotlinx.html.DIV
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import react.*
import react.dom.attrs
import react.dom.button
import react.dom.li
import react.dom.ul
import space.kscience.visionforge.react.flexColumn
import styled.StyledDOMBuilder
import styled.styledDiv
public external interface TabProps : PropsWithChildren {
public var id: String
public var title: String?
}
@JsExport
public val Tab: FC<TabProps> = fc { props ->
props.children()
}
public external interface TabPaneProps : PropsWithChildren {
public var activeTab: String?
}
@JsExport
public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
var activeTab: String? by useState(props.activeTab)
val children: Array<out ReactElement<*>?> = Children.map(props.children) {
it.asElementOrNull()
} ?: emptyArray()
val childrenProps = children.mapNotNull {
it?.props?.unsafeCast<TabProps>()
}
flexColumn {
ul("nav nav-tabs") {
childrenProps.forEach { cp ->
li("nav-item") {
button(classes = "nav-link") {
+(cp.title ?: cp.id)
attrs {
if (cp.id == activeTab) {
classes = classes + "active"
}
onClickFunction = {
activeTab = cp.id
}
}
}
}
}
}
children.find { (it?.props?.unsafeCast<TabProps>())?.id == activeTab }?.let {
child(it)
}
}
}
public class TabBuilder(internal val parentBuilder: RBuilder) {
public fun tab(id: String, title: String? = null, builder: StyledDOMBuilder<DIV>.() -> Unit) {
parentBuilder.child(Tab) {
attrs {
this.id = id
this.title = title
}
styledDiv {
builder()
}
}
}
}
public inline fun RBuilder.tabPane(activeTab: String? = null, crossinline builder: TabBuilder.() -> Unit) {
child(TabPane) {
attrs {
this.activeTab = activeTab
}
TabBuilder(this).builder()
}
}

View File

@ -1,80 +0,0 @@
package space.kscience.visionforge.bootstrap
import kotlinx.css.*
import react.FC
import react.PropsWithChildren
import react.RBuilder
import react.dom.h2
import react.fc
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.visionTree
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
public external interface ThreeControlsProps : PropsWithChildren {
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
public var selected: Name?
public var onSelect: (Name) -> Unit
}
@JsExport
public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
tabPane(if (props.selected != null) "Properties" else null) {
tab("Canvas") {
card("Canvas configuration") {
canvasControls(props.canvasOptions, props.vision)
}
}
tab("Tree") {
css {
border = Border(1.px, BorderStyle.solid, Color.lightGray)
padding = Padding(10.px)
}
h2 { +"Object tree" }
styledDiv {
css {
flex = Flex(1.0, 1.0, FlexBasis.inherit)
}
props.vision?.let {
visionTree(it, props.selected, props.onSelect)
}
}
}
tab("Properties") {
props.selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> props.vision
else -> (props.vision as? SolidGroup)?.get(selected)
}
if (selectedObject != null) {
visionPropertyEditor(selectedObject, key = selected)
}
}
}
this.parentBuilder.run {
props.children()
}
}
}
public fun RBuilder.threeControls(
canvasOptions: Canvas3DOptions,
vision: Vision?,
selected: Name?,
onSelect: (Name) -> Unit = {},
builder: TabBuilder.() -> Unit = {},
): Unit = child(ThreeControls) {
attrs {
this.canvasOptions = canvasOptions
this.vision = vision
this.selected = selected
this.onSelect = onSelect
}
TabBuilder(this).builder()
}

View File

@ -1,71 +0,0 @@
package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element
import react.RBuilder
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.EditorPropertyState
import space.kscience.visionforge.react.PropertyEditor
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.render
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.visionPropertyEditor(
vision: Vision,
descriptor: MetaDescriptor? = vision.descriptor,
key: Any? = null,
) {
card("Properties") {
child(PropertyEditor) {
attrs {
this.key = key?.toString()
this.meta = vision.properties.root()
this.updates = vision.properties.changes
this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = { name ->
val ownMeta = vision.properties.own?.get(name)
if (ownMeta != null && !ownMeta.isEmpty()) {
EditorPropertyState.Defined
} else if (vision.properties.root().getValue(name) != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
}
val styles = if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct()
} else {
vision.styles
}
if (styles.isNotEmpty()) {
card("Styles") {
accordion("styles") {
styles.forEach { styleName ->
val style = vision.getStyle(styleName)
if (style != null) {
entry(styleName) {
metaViewer(style)
}
}
}
}
}
}
}
public fun Element.visionPropertyEditor(
item: Vision,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = space.kscience.visionforge.react.createRoot(this).render {
visionPropertyEditor(item, descriptor = descriptor)
}

View File

@ -1,56 +0,0 @@
/*full height*/
html, body {
height: 100%;
width: 100%;
}
.container-fluid { height: inherit; }
/* Remove default bullets */
ul, .tree {
list-style-type: none;
}
/* Style the caret/arrow */
.tree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.tree-caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
}
.tree-leaf{
user-select: none;
display: inline-block;
}
.tree-leaf::before {
content: "\25C6";
color: black;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-caret-down::before {
transform: rotate(90deg);
}
.tree-label-inactive {
color: lightgrey;
}
.tree-label-selected{
background-color: lightblue;
}
.no-padding{
padding: 0;
}

View File

@ -1,4 +0,0 @@
# Module react

View File

@ -1,16 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
}
kscience {
js()
jsMain {
dependencies {
api(projects.visionforgeSolid)
api("org.jetbrains.kotlin-wrappers:kotlin-styled")
api("org.jetbrains.kotlin-wrappers:kotlin-react-dom")
// implementation(npm("react-select","4.3.0"))
api(projects.visionforgeThreejs)
}
}
}

View File

@ -1,149 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.Align
import kotlinx.css.alignItems
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import react.*
import react.dom.a
import react.dom.attrs
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
import styled.css
import styled.styledDiv
import styled.styledSpan
public external interface MetaViewerProps : Props {
/**
* Root meta
*/
public var root: Meta
/**
* The title of root node
*/
public var rootName: String?
/**
* Full path to the displayed node in [root]. Could be empty
*/
public var name: Name
/**
* Root descriptor
*/
public var descriptor: MetaDescriptor?
}
private val MetaViewerItem: FC<MetaViewerProps> = fc("MetaViewerItem") { props ->
metaViewerItem(props)
}
private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
var expanded: Boolean by useState { true }
val item = props.root[props.name]
val descriptorItem: MetaDescriptor? = props.descriptor?.get(props.name)
val actualValue = item?.value ?: descriptorItem?.defaultValue
val actualMeta = item ?: descriptorItem?.defaultNode
val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: ""
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
flexRow {
css {
alignItems = Align.center
}
if (actualMeta?.isLeaf == false) {
styledSpan {
css {
+TreeStyles.treeCaret
if (expanded) {
+TreeStyles.treeCaredDown
}
}
attrs {
onClickFunction = expanderClick
}
}
}
styledSpan {
css {
+TreeStyles.treeLabel
if (item == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
styledDiv {
a {
+actualValue.toString()
}
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
val keys = buildSet {
descriptorItem?.children?.keys?.forEach {
add(NameToken(it))
}
actualMeta!!.items.keys.let { addAll(it) }
}
keys.filter { !it.body.startsWith("@") }.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(MetaViewerItem) {
attrs {
this.key = props.name.toString()
this.root = props.root
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
}
@JsExport
public val MetaViewer: FC<MetaViewerProps> = fc("MetaViewer") { props ->
child(MetaViewerItem) {
attrs {
this.key = ""
this.root = props.root
this.name = Name.EMPTY
this.descriptor = props.descriptor
}
}
}
public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) {
child(MetaViewer) {
attrs {
this.key = key?.toString() ?: ""
this.root = meta
this.descriptor = descriptor
}
}
}

View File

@ -1,38 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.asList
import org.w3c.dom.events.Event
import react.FC
import react.dom.attrs
import react.dom.option
import react.dom.select
import react.fc
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.string
@JsExport
public val MultiSelectChooser: FC<ValueChooserProps> = fc("MultiSelectChooser") { props ->
val onChange: (Event) -> Unit = { event: Event ->
val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList()
.map { (it as HTMLOptionElement).value.asValue() }
props.onValueChange(newSelected.asValue())
}
select {
attrs {
multiple = true
values = (props.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
onChangeFunction = onChange
}
props.descriptor?.allowedValues?.forEach { optionValue ->
option {
+optionValue.string
}
}
}
}

View File

@ -1,277 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.css.*
import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import react.*
import react.dom.attrs
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRestriction
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.remove
import space.kscience.dataforge.names.*
import space.kscience.visionforge.hidden
import styled.css
import styled.styledButton
import styled.styledDiv
import styled.styledSpan
/**
* The display state of a property
*/
public sealed class EditorPropertyState {
public object Defined : EditorPropertyState()
public class Default(public val source: String = "unknown") : EditorPropertyState()
public object Undefined : EditorPropertyState()
}
public external interface PropertyEditorProps : Props {
/**
* Root config object - always non-null
*/
public var meta: MutableMeta
public var getPropertyState: (Name) -> EditorPropertyState
public var scope: CoroutineScope
public var updates: Flow<Name>
/**
* Full path to the displayed node in [meta]. Could be empty
*/
public var name: Name
/**
* Root descriptor
*/
public var descriptor: MetaDescriptor?
/**
* Initial expanded state
*/
public var expanded: Boolean?
}
private val PropertyEditorItem: FC<PropertyEditorProps> = fc("PropertyEditorItem") { props ->
propertyEditorItem(props)
}
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true }
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var property: MutableMeta by useState { props.meta.getOrCreate(props.name) }
var editorPropertyState: EditorPropertyState by useState { props.getPropertyState(props.name) }
val keys = useMemo(descriptor) {
buildSet {
descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden
}?.forEach {
add(NameToken(it.key))
}
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
}
}
val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() {
property = props.meta.getOrCreate(props.name)
editorPropertyState = props.getPropertyState(props.name)
}
useEffect(props.meta) {
val job = props.updates.onEach { updatedName ->
if (updatedName == props.name) {
update()
}
}.launchIn(props.scope)
cleanup {
job.cancel()
}
}
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
val removeClick: (Event) -> Unit = {
props.meta.remove(props.name)
update()
}
flexRow {
css {
alignItems = Align.center
}
if (keys.isNotEmpty()) {
styledSpan {
css {
+TreeStyles.treeCaret
if (expanded) {
+TreeStyles.treeCaredDown
}
}
attrs {
onClickFunction = expanderClick
}
}
}
styledSpan {
css {
+TreeStyles.treeLabel
if (editorPropertyState != EditorPropertyState.Defined) {
+TreeStyles.treeLabelInactive
}
}
+token
}
if (!props.name.isEmpty() && descriptor?.valueRestriction != ValueRestriction.ABSENT) {
styledDiv {
css {
//+TreeStyles.resizeableInput
width = 160.px
margin = Margin(1.px, 5.px)
}
ValueChooser {
attrs {
this.descriptor = descriptor
this.state = editorPropertyState
this.value = property.value
this.onValueChange = {
property.value = it
editorPropertyState = props.getPropertyState(props.name)
}
}
}
}
styledButton {
css {
width = 24.px
alignSelf = Align.stretch
margin = Margin(1.px, 5.px)
backgroundColor = Color.white
borderStyle = BorderStyle.solid
borderRadius = 2.px
textAlign = TextAlign.center
textDecoration = TextDecoration.none
cursor = Cursor.pointer
disabled {
cursor = Cursor.auto
borderStyle = BorderStyle.dashed
color = Color.lightGray
}
}
+"\u00D7"
attrs {
if (editorPropertyState != EditorPropertyState.Defined) {
disabled = true
} else {
onClickFunction = removeClick
}
}
}
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.meta = props.meta
this.name = props.name + token
this.descriptor = props.descriptor
this.scope = props.scope
this.getPropertyState = { props.getPropertyState(props.name + token) }
this.updates = props.updates
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
}
@JsExport
public val PropertyEditor: FC<PropertyEditorProps> = fc("PropertyEditor") { props ->
child(PropertyEditorItem) {
attrs {
this.key = ""
this.meta = props.meta
this.name = Name.EMPTY
this.descriptor = props.descriptor
this.expanded = props.expanded
this.scope = props.scope
this.getPropertyState = props.getPropertyState
this.updates = props.updates
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
public fun RBuilder.propertyEditor(
scope: CoroutineScope,
properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null,
key: Any? = null,
expanded: Boolean? = null,
) {
child(PropertyEditor) {
attrs {
this.meta = properties
this.descriptor = descriptor
this.key = key?.toString() ?: ""
this.expanded = expanded
this.scope = scope
this.getPropertyState = { name ->
if (properties[name] != null) {
EditorPropertyState.Defined
} else if (descriptor?.get(name)?.defaultValue != null) {
EditorPropertyState.Default("descriptor")
} else {
EditorPropertyState.Undefined
}
}
this.updates = callbackFlow {
properties.onChange(scope) { name ->
scope.launch {
send(name)
}
}
invokeOnClose {
properties.removeListener(scope)
}
}
}
}
}

View File

@ -1,78 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.pct
import kotlinx.css.width
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onInputFunction
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import react.FC
import react.dom.attrs
import react.fc
import react.useState
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.descriptors.ValueRestriction
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import styled.css
import styled.styledInput
@JsExport
public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props ->
var innerValue by useState(props.value?.double)
var rangeDisabled: Boolean by useState(props.state != EditorPropertyState.Defined)
val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue
props.onValueChange(
if (!checkBoxValue) {
null
} else {
innerValue?.asValue()
}
)
}
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value
props.onValueChange(newValue.toDoubleOrNull()?.asValue())
innerValue = newValue.toDoubleOrNull()
}
flexRow {
if (props.descriptor?.valueRestriction != ValueRestriction.REQUIRED) {
styledInput(type = InputType.checkBox) {
attrs {
defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable
}
}
}
styledInput(type = InputType.range) {
css {
width = 100.pct
}
attrs {
disabled = rangeDisabled
value = innerValue?.toString() ?: ""
// onChangeFunction = handleChange
onInputFunction = handleChange
val minValue = props.descriptor?.attributes?.get("min").string
minValue?.let {
min = it
}
val maxValue = props.descriptor?.attributes?.get("max").string
maxValue?.let {
max = it
}
props.descriptor?.attributes?.get("step").string?.let {
step = it
}
}
}
}
}

View File

@ -1,55 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.*
import org.w3c.dom.Element
import react.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import styled.css
import styled.styledDiv
public external interface ThreeCanvasProps : Props {
public var context: Context
public var options: Canvas3DOptions?
public var solid: Solid?
public var selected: Name?
}
public val ThreeCanvasComponent: FC<ThreeCanvasProps> = fc("ThreeCanvasComponent") { props ->
val elementRef = useRef<Element>(null)
var canvas by useState<ThreeCanvas?>(null)
val three: ThreePlugin = useMemo(props.context) { props.context.request(ThreePlugin) }
useEffect(props.solid, props.options, elementRef) {
if (canvas == null) {
val element = elementRef.current ?: error("Canvas element not found")
canvas = ThreeCanvas(three, element, props.options ?: Canvas3DOptions())
}
}
useEffect(canvas, props.solid) {
props.solid?.let { obj ->
canvas?.render(obj)
}
}
useEffect(canvas, props.selected) {
canvas?.select(props.selected)
}
styledDiv {
css {
maxWidth = 100.vw
maxHeight = 100.vh
width = 100.pct
height = 100.pct
}
ref = elementRef
}
}

View File

@ -1,69 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.*
import kotlinx.css.properties.deg
import kotlinx.css.properties.rotate
import styled.StyleSheet
public object TreeStyles : StyleSheet("treeStyles", true) {
/**
* Remove default bullets
*/
public val tree: RuleSet by css {
paddingLeft = 5.px
marginLeft = 0.px
listStyleType = ListStyleType.none
}
/**
* Style the caret/arrow
*/
public val treeCaret: RuleSet by css {
cursor = Cursor.pointer
userSelect = UserSelect.none
/* Create the caret/arrow with a unicode, and style it */
before {
content = "\u25B6".quoted
color = Color.black
display = Display.inlineBlock
marginRight = 6.px
}
}
/**
* Rotate the caret/arrow icon when clicked on (using JavaScript)
*/
public val treeCaredDown:RuleSet by css {
before {
content = "\u25B6".quoted
color = Color.black
display = Display.inlineBlock
marginRight = 6.px
transform.rotate(90.deg)
}
}
public val treeItem:RuleSet by css {
alignItems = Align.center
paddingLeft = 10.px
borderLeftStyle = BorderStyle.dashed
borderLeftWidth = 1.px
borderLeftColor = Color.lightGray
}
public val treeLabel:RuleSet by css {
border = Border.none
padding = Padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
textAlign = TextAlign.left
flex = Flex(1.0)
}
public val treeLabelInactive: RuleSet by css {
color = Color.lightGray
}
public val treeLabelSelected:RuleSet by css {
backgroundColor = Color.lightBlue
}
}

View File

@ -1,126 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.*
import kotlinx.css.properties.TextDecoration
import kotlinx.css.properties.TextDecorationLine
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import react.*
import react.dom.attrs
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence
import space.kscience.visionforge.isEmpty
import styled.css
import styled.styledDiv
import styled.styledSpan
public external interface ObjectTreeProps : Props {
public var name: Name
public var selected: Name?
public var obj: Vision
public var clickCallback: (Name) -> Unit
}
private val TreeLabel = fc<ObjectTreeProps> { props ->
val token = useMemo(props.name) { props.name.lastOrNull()?.toString() ?: "World" }
styledSpan {
css {
+TreeStyles.treeLabel
color = Color("#069")
cursor = Cursor.pointer
hover {
textDecoration = TextDecoration(setOf(TextDecorationLine.underline))
}
if (props.name == props.selected) {
+TreeStyles.treeLabelSelected
}
}
+token
attrs {
onClickFunction = { props.clickCallback(props.name) }
}
}
}
private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by useState { props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = {
expanded = !expanded
}
val obj = props.obj
//display as node if any child is visible
if (obj is VisionGroup) {
flexRow {
if (obj.children.keys.any { !it.body.startsWith("@") }) {
styledSpan {
css {
+TreeStyles.treeCaret
if (expanded) {
+TreeStyles.treeCaredDown
}
}
attrs {
onClickFunction = onClick
}
}
}
child(TreeLabel, props = props)
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
obj.children.asSequence()
.filter { !it.first.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.second as? VisionGroup)?.children?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(ObjectTree) {
attrs {
this.name = props.name + childToken
this.obj = child
this.selected = props.selected
this.clickCallback = props.clickCallback
}
}
}
}
}
}
} else {
child(TreeLabel, props = props)
}
}
@JsExport
public val ObjectTree: FC<ObjectTreeProps> = fc("ObjectTree") { props ->
visionTree(props)
}
public fun RBuilder.visionTree(
vision: Vision,
selected: Name? = null,
clickCallback: (Name) -> Unit = {},
) {
child(ObjectTree) {
attrs {
this.name = Name.EMPTY
this.obj = vision
this.selected = selected
this.clickCallback = clickCallback
}
}
}

View File

@ -1,17 +0,0 @@
@file:JsModule("react-dom/client")
@file:JsNonModule
package space.kscience.visionforge.react
import org.w3c.dom.Element
import react.dom.client.Root
import react.dom.client.RootOptions
/**
* Compatibility method to work with old browser API
*/
public external fun createRoot(
container: Element,
options: RootOptions = definedExternally,
): Root

View File

@ -1,10 +0,0 @@
package space.kscience.visionforge.react
import react.Props
import react.RBuilder
import react.createElement
import react.dom.client.Root
public fun Root.render(block: RBuilder.() -> Unit) {
render(createElement<Props>(block))
}

View File

@ -1,31 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.Display
import kotlinx.css.FlexDirection
import kotlinx.css.display
import kotlinx.css.flexDirection
import kotlinx.html.DIV
import react.RBuilder
import styled.StyledDOMBuilder
import styled.css
import styled.styledDiv
public inline fun RBuilder.flexColumn(
block: StyledDOMBuilder<DIV>.() -> Unit
): Unit = styledDiv {
css {
display = Display.flex
flexDirection = FlexDirection.column
}
block()
}
public inline fun RBuilder.flexRow(
block: StyledDOMBuilder<DIV>.() -> Unit
): Unit = styledDiv {
css {
display = Display.flex
flexDirection = FlexDirection.row
}
block()
}

View File

@ -1,175 +0,0 @@
package space.kscience.visionforge.react
import kotlinx.css.*
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onKeyDownFunction
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event
import react.FC
import react.Props
import react.dom.attrs
import react.dom.option
import react.fc
import react.useState
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType
import styled.css
import styled.styledInput
import styled.styledSelect
import three.math.Color
public external interface ValueChooserProps : Props {
public var descriptor: MetaDescriptor?
public var state: EditorPropertyState
public var value: Value?
public var onValueChange: (Value?) -> Unit
}
@JsExport
public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser") { props ->
var value by useState(props.value?.string ?: "")
val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value
props.onValueChange(value.asValue())
}
}
val handleChange: (Event) -> Unit = {
value = (it.target as HTMLInputElement).value
}
styledInput(type = InputType.text) {
css {
width = 100.pct
}
attrs {
this.value = value
onKeyDownFunction = keyDown
onChangeFunction = handleChange
}
}
}
@JsExport
public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked
props.onValueChange(newValue.asValue())
}
styledInput(type = InputType.checkBox) {
css {
width = 100.pct
}
attrs {
//this.attributes["indeterminate"] = (props.item == null).toString()
checked = props.value?.boolean ?: false
onChangeFunction = handleChange
}
}
}
@JsExport
public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser") { props ->
var innerValue by useState(props.value?.string ?: "")
val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value
val number = innerValue.toDoubleOrNull()
if (number == null) {
console.error("The input value $innerValue is not a number")
} else {
props.onValueChange(number.asValue())
}
}
}
val handleChange: (Event) -> Unit = {
innerValue = (it.target as HTMLInputElement).value
}
styledInput(type = InputType.number) {
css {
width = 100.pct
}
attrs {
value = innerValue
onKeyDownFunction = keyDown
onChangeFunction = handleChange
props.descriptor?.attributes?.get("step").string?.let {
step = it
}
props.descriptor?.attributes?.get("min").string?.let {
min = it
}
props.descriptor?.attributes?.get("max").string?.let {
max = it
}
}
}
}
@JsExport
public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") { props ->
var selected by useState(props.value?.string ?: "")
val handleChange: (Event) -> Unit = {
selected = (it.target as HTMLSelectElement).value
props.onValueChange(selected.asValue())
}
styledSelect {
css {
width = 100.pct
}
props.descriptor?.allowedValues?.forEach {
option {
+it.string
}
}
attrs {
this.value = props.value?.string ?: ""
multiple = false
onChangeFunction = handleChange
}
}
}
@JsExport
public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") { props ->
val handleChange: (Event) -> Unit = {
props.onValueChange((it.target as HTMLInputElement).value.asValue())
}
styledInput(type = InputType.color) {
css {
width = 100.pct
margin = Margin(0.px)
}
attrs {
this.value = props.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else "#" + Color(value.string).getHexString()
} ?: "#000000"
onChangeFunction = handleChange
}
}
}
@JsExport
public val ValueChooser: FC<ValueChooserProps> = fc("ValueChooser") { props ->
val rawInput by useState(false)
val descriptor = props.descriptor
val type = descriptor?.valueTypes?.firstOrNull()
when {
rawInput -> child(StringValueChooser, props)
descriptor?.widgetType == "color" -> child(ColorValueChooser, props)
descriptor?.widgetType == "multiSelect" -> child(MultiSelectChooser, props)
descriptor?.widgetType == "range" -> child(RangeValueChooser, props)
type == ValueType.BOOLEAN -> child(BooleanValueChooser, props)
type == ValueType.NUMBER -> child(NumberValueChooser, props)
descriptor?.allowedValues?.isNotEmpty() ?: false -> child(ComboValueChooser, props)
//TODO handle lists
else -> child(StringValueChooser, props)
}
}

View File

@ -1,4 +0,0 @@
# Module ring

View File

@ -1,25 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
}
val dataforgeVersion: String by rootProject.extra
kscience{
js{
useCommonJs()
browser {
commonWebpackConfig {
cssSupport{
enabled.set(false)
}
}
}
}
jsMain{
api(projects.ui.react)
api("org.jetbrains.kotlin-wrappers:kotlin-ring-ui")
implementation(npm("core-js","3.12.1"))
implementation(npm("file-saver", "2.0.2"))
}
}

View File

@ -1,20 +0,0 @@
@file:JsModule("@jetbrains/ring-ui/components/loader/loader")
@file:JsNonModule
package ringui
import react.ComponentClass
import react.PropsWithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/loader/loader.js
public external interface LoaderProps : PropsWithClassName {
public var size: Number
public var colors: Array<String>
public var message: String
public var stop: Boolean
public var deterministic: Boolean
}
@JsName("default")
public external val Loader: ComponentClass<LoaderProps>

View File

@ -1,16 +0,0 @@
@file:JsModule("@jetbrains/ring-ui/components/loader-screen/loader-screen")
@file:JsNonModule
package ringui
import react.ComponentClass
import react.PropsWithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/loader-screen/loader-screen.js
public external interface LoaderScreenProps : PropsWithClassName {
public var containerClassName: String
public var message: String
}
@JsName("default")
public external val LoaderScreen: ComponentClass<LoaderScreenProps>

View File

@ -1,212 +0,0 @@
package space.kscience.visionforge.ring
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.css.*
import react.*
import react.dom.b
import react.dom.div
import react.dom.p
import react.dom.span
import ringui.*
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length
import space.kscience.visionforge.*
import space.kscience.visionforge.react.*
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.solidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
public external interface ThreeCanvasWithControlsProps : Props {
public var solids: Solids
public var builderOfSolid: Deferred<Solid?>
public var selected: Name?
public var options: Canvas3DOptions?
public var additionalTabs: Map<String, RBuilder.() -> Unit>?
}
private val ThreeCanvasWithControlsProps.context get() = solids.context
public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
builderOfSolid = context.async {
solids.solidGroup(null, block)
}
}
public fun ThreeCanvasWithControlsProps.options(block: Canvas3DOptions.() -> Unit) {
options = Canvas3DOptions(block)
}
public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
}
public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styledDiv {
div {
Link {
attrs {
onClick = {
link(Name.EMPTY)
}
}
+"\u2302"
}
if (name != null) {
val tokens = ArrayList<NameToken>(name.length)
name.tokens.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
span { +"." }
Link {
+token.toString()
attrs {
onClick = {
console.log("Selected = $fullName")
link(fullName)
}
}
}
}
}
}
}
@JsExport
public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props ->
var selected: Name? by useState { props.selected }
var solid: Solid? by useState(null)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await()
//ensure that the solid is properly rooted
if (solid?.parent == null) {
solid?.setAsRoot(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? SolidGroup)?.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 = 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 {
child(PropertyEditor) {
attrs {
this.key = selected.toString()
this.meta = vision.properties.root()
this.updates = vision.properties.changes
this.descriptor = vision.descriptor
this.scope = props.context
this.getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
p {
b { +"Styles: " }
+styles.joinToString(separator = ", ")
}
}
}
}
}
}
}
flexColumn {
css {
padding = Padding(4.px)
minWidth = 400.px
height = 100.pct
overflowY = Overflow.auto
flex = Flex(1.0, 10.0, FlexBasis("300px"))
}
ringThreeControls(options, solid, selected, onSelect, additionalTabs = props.additionalTabs)
}
}
}

View File

@ -1,52 +0,0 @@
package space.kscience.visionforge.ring
import kotlinx.coroutines.async
import org.w3c.dom.Element
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionClient
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreePlugin
public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
public val three: ThreePlugin by require(ThreePlugin)
override val tag: PluginTag get() = Companion.tag
override fun rateVision(vision: Vision): Int =
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING * 2 else ElementVisionRenderer.ZERO_RATING
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
space.kscience.visionforge.react.createRoot(element).render {
child(ThreeCanvasWithControls) {
attrs {
this.solids = three.solids
this.options = Canvas3DOptions.read(meta)
this.builderOfSolid = context.async { vision as Solid }
}
}
}
}
override fun content(target: String): Map<Name, Any> {
return when (target) {
ElementVisionRenderer.TYPE -> mapOf("three.withControls".asName() to this)
else -> super.content(target)
}
}
public companion object : PluginFactory<ThreeWithControlsPlugin> {
override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP)
override fun build(context: Context, meta: Meta): ThreeWithControlsPlugin = ThreeWithControlsPlugin()
}
}

View File

@ -1,88 +0,0 @@
package space.kscience.visionforge.ring
import org.w3c.dom.Element
import react.RBuilder
import react.dom.p
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.*
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.ringPropertyEditor(
vision: Vision,
descriptor: MetaDescriptor? = vision.descriptor,
key: Any? = null,
) {
val styles = if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct()
} else {
vision.styles
}
flexColumn {
Island("Properties") {
child(PropertyEditor) {
attrs {
this.key = key?.toString()
this.meta = vision.properties.root()
this.updates = vision.properties.changes
this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = {name->
if(vision.properties.own?.get(name)!= null){
EditorPropertyState.Defined
} else if(vision.properties.root()[name] != null){
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
}
if (styles.isNotEmpty()) {
Island("Styles") {
if (styles.size == 1) {
val styleName = styles.first()
p {
+styleName
}
val style = vision.getStyle(styleName)
if (style != null) {
Tab(styleName, id = styleName) {
metaViewer(style)
}
}
} else {
SmartTabs {
styles.forEach { styleName ->
val style = vision.getStyle(styleName)
if (style != null) {
Tab(styleName, id = styleName) {
metaViewer(style)
}
}
}
}
}
}
}
}
}
public fun Element.ringPropertyEditor(
item: Vision,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = createRoot(this).render {
ringPropertyEditor(item, descriptor = descriptor)
}

View File

@ -1,131 +0,0 @@
package space.kscience.visionforge.ring
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import react.FC
import react.Props
import react.RBuilder
import react.dom.attrs
import react.dom.button
import react.fc
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.visionTree
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
internal fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
event.stopPropagation();
event.preventDefault();
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8"))
fileSaver.saveAs(blob, fileName)
}
internal fun RBuilder.canvasControls(options: Canvas3DOptions, vision: Vision?): Unit {
child(CanvasControls) {
attrs {
this.options = options
this.vision = vision
}
}
}
internal external interface CanvasControlsProps : Props {
public var options: Canvas3DOptions
public var vision: Vision?
}
@OptIn(DelicateCoroutinesApi::class)
internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn {
flexRow {
css {
border = Border(1.px, BorderStyle.solid, Color.blue)
padding = Padding(4.px)
}
props.vision?.let { vision ->
button {
+"Export"
attrs {
onClickFunction = {
val json = vision.encodeToString()
saveData(it, "object.json", "text/json") {
json
}
}
}
}
}
}
propertyEditor(
scope = props.vision?.manager?.context ?: GlobalScope,
properties = props.options.meta,
descriptor = Canvas3DOptions.descriptor,
expanded = false
)
}
}
public external interface ThreeControlsProps : Props {
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
public var selected: Name?
public var onSelect: (Name?) -> Unit
public var additionalTabs: Map<String, RBuilder.() -> Unit>
}
@JsExport
public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
SmartTabs("Tree") {
props.vision?.let {
Tab("Tree") {
Island("Vision tree") {
visionTree(it, props.selected, props.onSelect)
}
}
}
Tab("Settings") {
Island("Canvas configuration") {
canvasControls(props.canvasOptions, props.vision)
}
}
props.additionalTabs.forEach { (name, handler) ->
Tab(name) {
handler()
}
}
}
}
public fun RBuilder.ringThreeControls(
canvasOptions: Canvas3DOptions,
vision: Vision?,
selected: Name?,
onSelect: (Name?) -> Unit = {},
additionalTabs: Map<String, RBuilder.() -> Unit>? = null
): Unit = child(ThreeControls) {
attrs {
this.canvasOptions = canvasOptions
this.vision = vision
this.selected = selected
this.onSelect = onSelect
this.additionalTabs = additionalTabs ?: emptyMap()
}
}

View File

@ -1,3 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -4,9 +4,8 @@ plugins {
}
kscience {
jvm()
js()
// wasm()
jvm()
}
kotlin {
@ -15,23 +14,17 @@ kotlin {
commonMain {
dependencies {
api(projects.visionforgeCore)
//need this to placate compose compiler in MPP applications
api(compose.runtime)
}
}
val jvmMain by getting {
jsMain{
dependencies {
api(compose.foundation)
api(compose.material)
api(compose.preview)
}
}
val jsMain by getting {
dependencies {
api(compose.html.core)
api("app.softwork:bootstrap-compose:0.1.15")
api("app.softwork:bootstrap-compose-icons:0.1.15")
api(compose.html.core)
}
}
}

View File

@ -1,7 +0,0 @@
package space.kscience.visionforge.compose
import org.jetbrains.compose.web.css.StyleSheet
public object VisionForgeStyles: StyleSheet() {
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.css.Style
@ -7,22 +7,20 @@ import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.Element
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionClient
/**
* An [ElementVisionRenderer] that could be used directly in Compose-html as well as a stand-alone renderer
*/
public interface ComposeVisionRenderer: ElementVisionRenderer {
public interface ComposeHtmlVisionRenderer : ElementVisionRenderer {
@Composable
public fun DOMScope<Element>.render(client: VisionClient, name: Name, vision: Vision, meta: Meta)
public fun DOMScope<Element>.render(name: Name, vision: Vision, meta: Meta)
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
override fun render(element: Element, name: Name, vision: Vision, meta: Meta) {
renderComposable(element) {
Style(VisionForgeStyles)
render(client, name, vision, meta)
render(name, vision, meta)
}
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.*
import org.jetbrains.compose.web.css.AlignItems

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.dom.*

View File

@ -1,8 +1,7 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.CloseButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
@ -39,7 +38,6 @@ public sealed class EditorPropertyState {
*/
@Composable
public fun PropertyEditor(
scope: CoroutineScope,
rootMeta: MutableMeta,
getPropertyState: (Name) -> EditorPropertyState,
updates: Flow<Name>,
@ -136,7 +134,7 @@ public fun PropertyEditor(
Div({
classes(TreeStyles.treeItem)
}) {
PropertyEditor(scope, rootMeta, getPropertyState, updates, name + token, rootDescriptor, expanded)
PropertyEditor(rootMeta, getPropertyState, updates, name + token, rootDescriptor, expanded)
}
}
}
@ -145,13 +143,12 @@ public fun PropertyEditor(
@Composable
public fun PropertyEditor(
scope: CoroutineScope,
properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null,
expanded: Boolean? = null,
) {
val scope = rememberCoroutineScope()
PropertyEditor(
scope = scope,
rootMeta = properties,
getPropertyState = { name ->
if (properties[name] != null) {

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Card

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import org.jetbrains.compose.web.ExperimentalComposeWebApi
import org.jetbrains.compose.web.css.*
@ -67,26 +67,4 @@ public object TreeStyles : StyleSheet(VisionForgeStyles) {
public val treeLabelSelected: String by style {
backgroundColor(Color.lightblue)
}
public val propertyEditorButton: String by style {
width(24.px)
alignSelf(AlignSelf.Stretch)
marginAll(1.px, 5.px)
backgroundColor(Color.white)
border {
style(LineStyle.Solid)
}
borderRadius(2.px)
textAlign("center")
textDecoration("none")
cursor("pointer")
(self + disabled) {
cursor("auto")
border {
style(LineStyle.Dashed)
}
color(Color.lightgray)
}
}
}

View File

@ -0,0 +1,28 @@
package space.kscience.visionforge.html
import org.jetbrains.compose.web.css.*
public object VisionForgeStyles: StyleSheet() {
public val propertyEditorButton: String by style {
width(24.px)
alignSelf(AlignSelf.Stretch)
marginAll(1.px, 5.px)
backgroundColor(Color.white)
border {
style(LineStyle.Solid)
}
borderRadius(2.px)
textAlign("center")
textDecoration("none")
cursor("pointer")
(self + disabled) {
cursor("auto")
border {
style(LineStyle.Dashed)
}
color(Color.lightgray)
}
}
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.*
import org.jetbrains.compose.web.css.Color

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.dom.H5

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -48,7 +48,7 @@ public fun Vision(
}
DisposableEffect(vision, name, renderer, meta) {
renderer.render(scopeElement, client, name, vision, meta)
renderer.render(scopeElement, name, vision, meta)
onDispose {
scopeElement.clear()
}

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.css.keywords.CSSAutoKeyword

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.css.DisplayStyle

View File

@ -1,6 +1,6 @@
@file:Suppress("UNUSED_PARAMETER")
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.*
import kotlinx.uuid.UUID

View File

@ -0,0 +1,21 @@
# Module visionforge-compose-multiplatform
## Usage
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-compose-multiplatform:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:visionforge-compose-multiplatform:0.4.0-dev-3")
}
```

View File

@ -0,0 +1,35 @@
import space.kscience.gradle.Maturity
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose)
}
kscience {
jvm()
// wasm()
}
kotlin {
// android()
sourceSets {
commonMain {
dependencies {
api(projects.visionforgeCore)
api(compose.foundation)
api(compose.runtime)
api(compose.material)
api(compose.materialIconsExtended)
}
}
jvmMain {
dependencies {
implementation("com.eygraber:compose-color-picker:0.0.17")
}
}
}
}
readme {
maturity = Maturity.EXPERIMENTAL
}

View File

@ -0,0 +1,134 @@
package space.kscience.visionforge.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.html.VisionTagConsumer
/**
* A Kotlin-browser plugin that renders visions based on provided renderers and governs communication with the server.
*/
public class ComposeVisionClient : AbstractPlugin(), VisionClient {
override val tag: PluginTag get() = Companion.tag
override val visionManager: VisionManager by require(VisionManager)
private val renderers by lazy { context.gather<ComposeVisionRenderer>(ComposeVisionRenderer.TYPE).values }
private fun findRendererFor(vision: Vision): ComposeVisionRenderer? = renderers.mapNotNull {
val rating = it.rateVision(vision)
if (rating > 0) {
rating to it
} else {
null
}
}.maxByOrNull { it.first }?.second
private val mutex = Mutex()
private val rootChangeCollector = VisionChangeBuilder()
/**
* Communicate vision property changed from rendering engine to model
*/
override fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
context.launch {
mutex.withLock {
rootChangeCollector.propertyChanged(visionName, propertyName, item)
}
}
}
private val eventCollector = MutableSharedFlow<Pair<Name, VisionEvent>>(meta["feedback.eventCache"].int ?: 100)
/**
* Send a custom feedback event
*/
override suspend fun sendEvent(targetName: Name, event: VisionEvent) {
eventCollector.emit(targetName to event)
}
@Composable
public fun renderVision(name: Name, vision: Vision, outputMeta: Meta) {
val renderer: ComposeVisionRenderer = remember(vision) {
findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}")
}
key(vision) {
vision.setAsRoot(visionManager)
}
//subscribe to a backwards events propagation for control visions
if (vision is ControlVision) {
LaunchedEffect(vision) {
vision.controlEventFlow.collect {
sendEvent(name, it)
}
}
}
renderer.render(name, vision, outputMeta)
}
// override fun content(target: String): Map<Name, Any> = if (target == ComposeVisionRenderer.TYPE) {
// listOf(
// htmlVisionRenderer,
// inputVisionRenderer,
// checkboxVisionRenderer,
// numberVisionRenderer,
// textVisionRenderer,
// rangeVisionRenderer,
// formVisionRenderer,
// buttonVisionRenderer
// ).associateBy { it.toString().asName() }
// } else super<AbstractPlugin>.content(target)
public companion object : PluginFactory<ComposeVisionClient> {
override fun build(context: Context, meta: Meta): ComposeVisionClient = ComposeVisionClient()
override val tag: PluginTag = PluginTag(name = "vision.client.compose", group = PluginTag.DATAFORGE_GROUP)
}
}
/**
* Render an Element vision via injected vision renderer inside compose-html
*/
@Composable
public fun Vision(
context: Context,
vision: Vision,
name: Name? = null,
meta: Meta = Meta.EMPTY,
) {
val actualName = name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
context.request(ComposeVisionClient).renderVision(actualName, vision, meta)
}
@Composable
public fun Vision(
context: Context,
name: Name? = null,
meta: Meta = Meta.EMPTY,
buildOutput: VisionOutput.() -> Vision,
) {
val actualName = name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, buildOutput.hashCode().toUInt().toString()).asName()
val output = VisionOutput(context, actualName)
val vision = output.buildOutput()
context.request(ComposeVisionClient).renderVision(actualName, vision, meta)
}

View File

@ -0,0 +1,47 @@
package space.kscience.visionforge.compose
import androidx.compose.runtime.Composable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DfType
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import kotlin.reflect.KClass
import kotlin.reflect.cast
@DfType(ComposeVisionRenderer.TYPE)
public interface ComposeVisionRenderer {
public fun rateVision(vision: Vision): Int
@Composable
public fun render(name: Name, vision: Vision, meta: Meta)
public companion object {
public const val TYPE: String = "composeVisionRenderer"
public const val ZERO_RATING: Int = 0
public const val DEFAULT_RATING: Int = 10
}
}
public class SingleTypeComposeRenderer<T : Vision>(
public val kClass: KClass<T>,
private val acceptRating: Int = ComposeVisionRenderer.DEFAULT_RATING,
private val renderFunction: @Composable (name: Name, vision: T, meta: Meta) -> Unit,
) : ComposeVisionRenderer {
override fun rateVision(vision: Vision): Int =
if (vision::class == kClass) acceptRating else ComposeVisionRenderer.ZERO_RATING
@Composable
override fun render(
name: Name,
vision: Vision,
meta: Meta,
) {
renderFunction(name, kClass.cast(vision), meta)
}
}
public inline fun <reified T : Vision> ComposeVisionRenderer(
acceptRating: Int = ComposeVisionRenderer.DEFAULT_RATING,
noinline renderFunction: @Composable (name: Name, vision: T, meta: Meta) -> Unit,
): ComposeVisionRenderer = SingleTypeComposeRenderer(T::class, acceptRating, renderFunction)

View File

@ -0,0 +1,69 @@
package space.kscience.visionforge.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
@Composable
private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescriptor? = null) {
var expanded: Boolean by remember { mutableStateOf(true) }
val item: Meta? = root[name]
val descriptorItem: MetaDescriptor? = rootDescriptor?.get(name)
val actualValue = item?.value ?: descriptorItem?.defaultValue
val actualMeta = item ?: descriptorItem?.defaultNode
val token = name.lastOrNull()?.toString() ?: ""
Row(modifier = Modifier.fillMaxWidth()) {
if (actualMeta?.isLeaf == false) {
TextButton({ expanded = !expanded }) {
if (expanded) {
Icon(Icons.Filled.ExpandLess, "collapse")
} else {
Icon(Icons.Filled.ExpandMore, "expand")
}
}
}
Text(token, color = if (item == null) Color.Gray else Color.Unspecified)
Spacer(Modifier.weight(1f))
Text(actualValue.toString())
}
if (expanded) {
Column {
val keys = buildSet {
descriptorItem?.nodes?.keys?.forEach {
add(NameToken(it))
}
actualMeta!!.items.keys.let { addAll(it) }
}
keys.filter { !it.body.startsWith("@") }.forEach { token ->
MetaViewerItem(root, name + token, rootDescriptor)
}
}
}
}
@Composable
public fun MetaViewer(meta: Meta, descriptor: MetaDescriptor? = null) {
MetaViewerItem(meta, Name.EMPTY, descriptor)
}

View File

@ -0,0 +1,174 @@
package space.kscience.visionforge.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRestriction
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.remove
import space.kscience.dataforge.names.*
import space.kscience.visionforge.hidden
/**
* The display state of a property
*/
public sealed class EditorPropertyState {
public data object Defined : EditorPropertyState()
public data class Default(public val source: String = "unknown") : EditorPropertyState()
public data object Undefined : EditorPropertyState()
}
/**
* @param rootMeta Root config object - always non-null
* @param rootDescriptor Full path to the displayed node in [rootMeta]. Could be empty
*/
@Composable
public fun PropertyEditor(
rootMeta: MutableMeta,
getPropertyState: (Name) -> EditorPropertyState,
updates: Flow<Name>,
name: Name,
rootDescriptor: MetaDescriptor?,
initialExpanded: Boolean? = null,
) {
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
val descriptor: MetaDescriptor? by derivedStateOf { rootDescriptor?.get(name) }
var displayedValue by remember { mutableStateOf(rootMeta.getValue(name)) }
var editorPropertyState: EditorPropertyState by remember { mutableStateOf(getPropertyState(name)) }
fun buildKeys() = buildSet {
descriptor?.nodes?.filterNot {
it.key.startsWith("@") || it.value.hidden
}?.forEach {
add(NameToken(it.key))
}
rootMeta[name]?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
}
var keys by remember { mutableStateOf(buildKeys()) }
val token = name.lastOrNull()?.toString() ?: "Properties"
fun update() {
displayedValue = rootMeta.getValue(name)
editorPropertyState = getPropertyState(name)
keys = buildKeys()
}
LaunchedEffect(rootMeta) {
updates.collect { updatedName ->
if (name.startsWith(updatedName)) {
update()
}
}
}
Column(modifier = Modifier.fillMaxWidth().padding(start = 20.dp)) {
Row(modifier = Modifier.fillMaxWidth().padding(start = 20.dp)) {
//if node has children
if (keys.isNotEmpty()) {
TextButton(
{ expanded = !expanded },
modifier = Modifier.align(Alignment.CenterVertically).width(40.dp)
) {
if (expanded) {
Icon(Icons.Filled.ExpandLess, "collapse")
} else {
Icon(Icons.Filled.ExpandMore, "expand")
}
}
}
Text(
token,
color = when (editorPropertyState) {
is EditorPropertyState.Default, EditorPropertyState.Undefined -> Color.Gray
else -> Color.Unspecified
},
modifier = Modifier.align(Alignment.CenterVertically)
)
Spacer(modifier = Modifier.weight(1f))
if (!name.isEmpty() && descriptor?.valueRestriction != ValueRestriction.ABSENT) {
Box(modifier = Modifier.padding(1.dp, 5.dp).width(160.dp)) {
ValueChooser(descriptor, editorPropertyState, displayedValue) {
rootMeta.setValue(name, it)
update()
}
}
}
if (!name.isEmpty()) {
TextButton(
onClick = {
rootMeta.remove(name)
update()
},
enabled = editorPropertyState == EditorPropertyState.Defined,
modifier = Modifier.align(Alignment.CenterVertically).width(50.dp)
) {
if (editorPropertyState == EditorPropertyState.Defined) {
Icon(Icons.Filled.Clear, "Reset")
}
}
}
}
if (expanded) {
Column(modifier = Modifier.fillMaxWidth()) {
keys.forEach { token ->
PropertyEditor(rootMeta, getPropertyState, updates, name + token, rootDescriptor, expanded)
}
}
}
}
}
@Composable
public fun PropertyEditor(
properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null,
expanded: Boolean? = null,
) {
val scope = rememberCoroutineScope()
PropertyEditor(
rootMeta = properties,
getPropertyState = { name ->
if (properties[name] != null) {
EditorPropertyState.Defined
} else if (descriptor?.get(name)?.defaultValue != null) {
EditorPropertyState.Default("descriptor")
} else {
EditorPropertyState.Undefined
}
},
updates = callbackFlow {
properties.onChange(scope) { name ->
scope.launch {
send(name)
}
}
awaitClose { properties.removeListener(scope) }
},
name = Name.EMPTY,
rootDescriptor = descriptor,
initialExpanded = expanded,
)
}

View File

@ -0,0 +1,270 @@
@file:Suppress("UNUSED_PARAMETER")
package space.kscience.visionforge.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckBox
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.input.KeyboardType
import com.eygraber.compose.colorpicker.ColorPicker
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.visionforge.widgetType
import kotlin.math.roundToInt
@Composable
public fun StringValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var stringValue by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
TextField(
value = stringValue,
onValueChange = {
stringValue = it
onValueChange(it.asValue())
},
modifier = Modifier.fillMaxWidth()
)
}
@Composable
public fun BooleanValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var innerValue by remember(value, descriptor) {
mutableStateOf(
value?.boolean ?: descriptor?.defaultValue?.boolean ?: false
)
}
Button(
onClick = {
innerValue = !innerValue
onValueChange(innerValue.asValue())
},
colors = if (innerValue) ButtonDefaults.buttonColors(Color.Green) else ButtonDefaults.buttonColors(Color.Gray)
) {
if (innerValue) {
Text("On")
} else {
Text("Off")
}
}
}
@Composable
public fun NumberValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var stringValue by remember(value, descriptor) { mutableStateOf(value?.string ?: descriptor?.defaultValue?.string) }
TextField(
value = stringValue ?: "",
onValueChange = { newValue ->
stringValue = newValue
newValue.toDoubleOrNull()?.let { onValueChange(it.asValue()) }
},
isError = stringValue?.toDoubleOrNull() != null,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
public fun ComboValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
var selected by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
modifier = Modifier.fillMaxWidth()
) {
TextField(
value = selected,
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
descriptor?.allowedValues?.forEach { item: Value ->
DropdownMenuItem(
onClick = {
selected = item.string
expanded = false
onValueChange(selected.asValue())
}
) {
Text(item.string)
}
}
}
}
}
@Composable
public fun ColorValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
//var innerValue by remember { mutableStateOf(value ?: descriptor?.defaultValue) }
Box(Modifier.fillMaxWidth()) {
ColorPicker(Modifier.fillMaxWidth()) {
if (it.isSpecified) {
onValueChange(it.toArgb().asValue())
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
public fun MultiSelectChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
var selected: Set<Value> by remember(value) {
mutableStateOf(value?.list?.toSet() ?: emptySet<Value>())
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
modifier = Modifier.fillMaxWidth()
) {
TextField(
value = selected.joinToString(prefix = "[", postfix = "]"),
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
descriptor?.allowedValues?.forEach { item: Value ->
val currentlySelected = item in selected
DropdownMenuItem(
onClick = {
selected = if (currentlySelected) {
selected - item
} else {
selected + item
}
onValueChange(selected.asValue())
}
) {
if (currentlySelected) {
Icon(Icons.Default.CheckBox, "checked")
} else {
Icon(Icons.Default.CheckBoxOutlineBlank, "checked")
}
Text(item.string)
}
}
}
}
}
@Composable
public fun RangeValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) }
val min by derivedStateOf {
descriptor?.attributes?.get("min").float ?: 0f
}
val max by derivedStateOf {
descriptor?.attributes?.get("max").float ?: 0f
}
val step by derivedStateOf {
descriptor?.attributes?.get("step").float ?: 0.1f
}
Slider(
value = innerValue?.toFloat() ?: 0f,
onValueChange = {
innerValue = it
onValueChange(it.asValue())
},
valueRange = min..max,
steps = ((max - min) / step).roundToInt(),
modifier = Modifier.fillMaxWidth()
)
}
@Composable
public fun ValueChooser(
descriptor: MetaDescriptor?,
state: EditorPropertyState,
value: Value?,
onValueChange: (Value?) -> Unit,
) {
val rawInput by remember { mutableStateOf(false) }
val type = descriptor?.valueTypes?.firstOrNull()
when {
rawInput -> StringValueChooser(descriptor, state, value, onValueChange)
descriptor?.widgetType == "color" -> ColorValueChooser(descriptor, state, value, onValueChange)
descriptor?.widgetType == "multiSelect" -> MultiSelectChooser(descriptor, state, value, onValueChange)
descriptor?.widgetType == "range" -> RangeValueChooser(descriptor, state, value, onValueChange)
type == ValueType.BOOLEAN -> BooleanValueChooser(descriptor, state, value, onValueChange)
type == ValueType.NUMBER -> NumberValueChooser(descriptor, state, value, onValueChange)
descriptor?.allowedValues?.isNotEmpty() ?: false -> ComboValueChooser(descriptor, state, value, onValueChange)
//TODO handle lists
else -> StringValueChooser(descriptor, state, value, onValueChange)
}
}

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-core:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-core:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-core:0.3.0-rc")
implementation("space.kscience:visionforge-core:0.4.0-dev-3")
}
```

View File

@ -1,3 +1,16 @@
public abstract class space/kscience/visionforge/AbstractControlVision : space/kscience/visionforge/AbstractVision, space/kscience/visionforge/ControlVision {
public static final field Companion Lspace/kscience/visionforge/AbstractControlVision$Companion;
public fun <init> ()V
public synthetic fun <init> (ILspace/kscience/dataforge/meta/MutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun dispatchControlEvent (Lspace/kscience/visionforge/VisionControlEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun getControlEventFlow ()Lkotlinx/coroutines/flow/SharedFlow;
public static final synthetic fun write$Self (Lspace/kscience/visionforge/AbstractControlVision;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class space/kscience/visionforge/AbstractControlVision$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract class space/kscience/visionforge/AbstractVision : space/kscience/visionforge/Vision {
public static final field Companion Lspace/kscience/visionforge/AbstractVision$Companion;
public fun <init> ()V
@ -34,29 +47,20 @@ public final class space/kscience/visionforge/AbstractVisionGroup$Companion {
public static synthetic fun updateProperties$default (Lspace/kscience/visionforge/AbstractVisionGroup$Companion;Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)V
}
public abstract class space/kscience/visionforge/AbstractVisionProperties : space/kscience/visionforge/MutableVisionProperties {
public fun <init> (Lspace/kscience/visionforge/Vision;)V
public synthetic fun getChanges ()Lkotlinx/coroutines/flow/Flow;
public fun getChanges ()Lkotlinx/coroutines/flow/SharedFlow;
public class space/kscience/visionforge/AbstractVisionProperties : space/kscience/visionforge/MutableVisionProperties {
public fun <init> (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/MutableMeta;)V
public fun flowChanges ()Lkotlinx/coroutines/flow/Flow;
protected final fun getChangesInternal ()Lkotlinx/coroutines/flow/MutableSharedFlow;
public fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;
protected final fun getOrCreateProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getOwn ()Lspace/kscience/dataforge/meta/Meta;
protected abstract fun getProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public synthetic fun getOwn ()Lspace/kscience/dataforge/meta/Meta;
public final fun getOwn ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getValue (Lspace/kscience/dataforge/names/Name;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lspace/kscience/dataforge/meta/Value;
public final fun getVision ()Lspace/kscience/visionforge/Vision;
public fun invalidate (Lspace/kscience/dataforge/names/Name;)V
public fun set (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/meta/Meta;Z)V
protected abstract fun setProperties (Lspace/kscience/dataforge/meta/MutableMeta;)V
public fun setValue (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/meta/Value;Z)V
}
public abstract interface class space/kscience/visionforge/ClickControl : space/kscience/visionforge/ControlVision {
public fun click (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun click$default (Lspace/kscience/visionforge/ClickControl;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun click$suspendImpl (Lspace/kscience/visionforge/ClickControl;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class space/kscience/visionforge/Colors {
public static final field BLUE_KEY Ljava/lang/String;
public static final field GREEN_KEY Ljava/lang/String;
@ -223,13 +227,21 @@ public abstract interface class space/kscience/visionforge/ControlVision : space
}
public final class space/kscience/visionforge/ControlVisionKt {
public static final fun VisionClickEvent (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionClickEvent;
public static synthetic fun VisionClickEvent$default (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionClickEvent;
public static final fun VisionInputEvent (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionInputEvent;
public static synthetic fun VisionInputEvent$default (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionInputEvent;
public static final fun VisionSubmitEvent (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionSubmitEvent;
public static synthetic fun VisionSubmitEvent$default (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionSubmitEvent;
public static final fun VisionValueChangeEvent (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionValueChangeEvent;
public static synthetic fun VisionValueChangeEvent$default (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionValueChangeEvent;
public static final fun onClick (Lspace/kscience/visionforge/ClickControl;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
public static final fun asyncControlEvent (Lspace/kscience/visionforge/ControlVision;Lspace/kscience/visionforge/VisionControlEvent;Lkotlinx/coroutines/CoroutineScope;)V
public static synthetic fun asyncControlEvent$default (Lspace/kscience/visionforge/ControlVision;Lspace/kscience/visionforge/VisionControlEvent;Lkotlinx/coroutines/CoroutineScope;ILjava/lang/Object;)V
public static final fun onSubmit (Lspace/kscience/visionforge/DataControl;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
}
public abstract interface class space/kscience/visionforge/DataControl : space/kscience/visionforge/ControlVision {
public fun submit (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun submit$default (Lspace/kscience/visionforge/DataControl;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun submit$suspendImpl (Lspace/kscience/visionforge/DataControl;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class space/kscience/visionforge/FlowPropertyKt {
@ -320,9 +332,9 @@ public final class space/kscience/visionforge/StyleReference {
public final class space/kscience/visionforge/StyleReferenceKt {
public static final fun style (Lspace/kscience/visionforge/Vision;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/ReadOnlyProperty;
public static final fun style (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Specification;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/ReadOnlyProperty;
public static final fun style (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/SchemeSpec;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/ReadOnlyProperty;
public static synthetic fun style$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty;
public static synthetic fun style$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Specification;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty;
public static synthetic fun style$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/SchemeSpec;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty;
public static final fun useStyle (Lspace/kscience/visionforge/Vision;Lspace/kscience/visionforge/StyleReference;Z)V
public static synthetic fun useStyle$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/visionforge/StyleReference;ZILjava/lang/Object;)V
}
@ -364,7 +376,9 @@ public final class space/kscience/visionforge/StyleSheetKt {
public final class space/kscience/visionforge/UsePropertyKt {
public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlin/reflect/KProperty1;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/Job;
public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
public static synthetic fun onPropertyChange$default (Lspace/kscience/visionforge/Vision;Lkotlin/reflect/KProperty1;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
public static synthetic fun onPropertyChange$default (Lspace/kscience/visionforge/Vision;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
public static final fun useProperty (Lspace/kscience/visionforge/Vision;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/Job;
public static final fun useProperty (Lspace/kscience/visionforge/Vision;Lkotlin/reflect/KProperty1;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
public static final fun useProperty (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/Job;
@ -462,30 +476,6 @@ public final class space/kscience/visionforge/VisionChildren$Companion {
public final fun empty (Lspace/kscience/visionforge/Vision;)Lspace/kscience/visionforge/VisionChildren;
}
public final class space/kscience/visionforge/VisionClickEvent : space/kscience/visionforge/VisionControlEvent {
public static final field Companion Lspace/kscience/visionforge/VisionClickEvent$Companion;
public fun <init> (Lspace/kscience/dataforge/meta/Meta;)V
public fun getMeta ()Lspace/kscience/dataforge/meta/Meta;
public final fun getName ()Lspace/kscience/dataforge/names/Name;
public final fun getPayload ()Lspace/kscience/dataforge/meta/Meta;
public fun toString ()Ljava/lang/String;
}
public final class space/kscience/visionforge/VisionClickEvent$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lspace/kscience/visionforge/VisionClickEvent$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/visionforge/VisionClickEvent;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/visionforge/VisionClickEvent;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/VisionClickEvent$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface class space/kscience/visionforge/VisionClient : space/kscience/dataforge/context/Plugin {
public abstract fun getVisionManager ()Lspace/kscience/visionforge/VisionManager;
public abstract fun notifyPropertyChanged (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/meta/Meta;)V
@ -589,8 +579,6 @@ public final class space/kscience/visionforge/VisionInputEvent$Companion {
public final class space/kscience/visionforge/VisionKt {
public static final fun getVisible (Lspace/kscience/visionforge/Vision;)Ljava/lang/Boolean;
public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
public static synthetic fun onPropertyChange$default (Lspace/kscience/visionforge/Vision;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
public static final fun setVisible (Lspace/kscience/visionforge/Vision;Ljava/lang/Boolean;)V
}
@ -659,10 +647,10 @@ public abstract class space/kscience/visionforge/VisionPlugin : space/kscience/d
}
public abstract interface class space/kscience/visionforge/VisionProperties : space/kscience/dataforge/meta/MetaProvider {
public abstract fun flowChanges ()Lkotlinx/coroutines/flow/Flow;
public fun get (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/meta/Meta;
public abstract fun get (Lspace/kscience/dataforge/names/Name;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lspace/kscience/dataforge/meta/Meta;
public static synthetic fun get$default (Lspace/kscience/visionforge/VisionProperties;Lspace/kscience/dataforge/names/Name;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/Meta;
public abstract fun getChanges ()Lkotlinx/coroutines/flow/Flow;
public abstract fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;
public abstract fun getOwn ()Lspace/kscience/dataforge/meta/Meta;
public fun getValue (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/meta/Value;
@ -685,6 +673,30 @@ public final class space/kscience/visionforge/VisionPropertiesKt {
public static synthetic fun root$default (Lspace/kscience/visionforge/MutableVisionProperties;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/MutableMeta;
}
public final class space/kscience/visionforge/VisionSubmitEvent : space/kscience/visionforge/VisionControlEvent {
public static final field Companion Lspace/kscience/visionforge/VisionSubmitEvent$Companion;
public fun <init> (Lspace/kscience/dataforge/meta/Meta;)V
public fun getMeta ()Lspace/kscience/dataforge/meta/Meta;
public final fun getName ()Lspace/kscience/dataforge/names/Name;
public final fun getPayload ()Lspace/kscience/dataforge/meta/Meta;
public fun toString ()Ljava/lang/String;
}
public final class space/kscience/visionforge/VisionSubmitEvent$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lspace/kscience/visionforge/VisionSubmitEvent$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/visionforge/VisionSubmitEvent;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/visionforge/VisionSubmitEvent;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/VisionSubmitEvent$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/VisionValueChangeEvent : space/kscience/visionforge/VisionControlEvent {
public static final field Companion Lspace/kscience/visionforge/VisionValueChangeEvent$Companion;
public fun <init> (Lspace/kscience/dataforge/meta/Meta;)V
@ -750,10 +762,10 @@ public abstract interface class space/kscience/visionforge/html/HtmlVisionFragme
public final class space/kscience/visionforge/html/HtmlVisionRendererKt {
public static final fun appendTo (Lspace/kscience/visionforge/html/HtmlVisionFragment;Lspace/kscience/visionforge/html/VisionTagConsumer;)V
public static final fun visionFragment (Lkotlinx/html/FlowContent;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Lspace/kscience/visionforge/html/HtmlVisionFragment;)V
public static final fun visionFragment (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lspace/kscience/visionforge/html/HtmlVisionFragment;)V
public static synthetic fun visionFragment$default (Lkotlinx/html/FlowContent;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Lspace/kscience/visionforge/html/HtmlVisionFragment;ILjava/lang/Object;)V
public static synthetic fun visionFragment$default (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lspace/kscience/visionforge/html/HtmlVisionFragment;ILjava/lang/Object;)V
public static final fun visionFragment (Lkotlinx/html/FlowContent;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lspace/kscience/visionforge/html/HtmlVisionFragment;)V
public static final fun visionFragment (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lspace/kscience/visionforge/html/HtmlVisionFragment;)V
public static synthetic fun visionFragment$default (Lkotlinx/html/FlowContent;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lspace/kscience/visionforge/html/HtmlVisionFragment;ILjava/lang/Object;)V
public static synthetic fun visionFragment$default (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/VisionManager;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lspace/kscience/visionforge/html/HtmlVisionFragment;ILjava/lang/Object;)V
}
public final class space/kscience/visionforge/html/InputFeedbackMode : java/lang/Enum {
@ -782,6 +794,21 @@ public final class space/kscience/visionforge/html/ResourceLocation : java/lang/
public abstract interface annotation class space/kscience/visionforge/html/VisionDSL : java/lang/annotation/Annotation {
}
public final class space/kscience/visionforge/html/VisionDisplay {
public fun <init> (Lspace/kscience/visionforge/VisionManager;Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Meta;)V
public final fun component1 ()Lspace/kscience/visionforge/VisionManager;
public final fun component2 ()Lspace/kscience/visionforge/Vision;
public final fun component3 ()Lspace/kscience/dataforge/meta/Meta;
public final fun copy (Lspace/kscience/visionforge/VisionManager;Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/visionforge/html/VisionDisplay;
public static synthetic fun copy$default (Lspace/kscience/visionforge/html/VisionDisplay;Lspace/kscience/visionforge/VisionManager;Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/meta/Meta;ILjava/lang/Object;)Lspace/kscience/visionforge/html/VisionDisplay;
public fun equals (Ljava/lang/Object;)Z
public final fun getMeta ()Lspace/kscience/dataforge/meta/Meta;
public final fun getVision ()Lspace/kscience/visionforge/Vision;
public final fun getVisionManager ()Lspace/kscience/visionforge/VisionManager;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class space/kscience/visionforge/html/VisionOfCheckbox : space/kscience/visionforge/html/VisionOfHtmlInput {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfCheckbox$Companion;
public fun <init> ()V
@ -804,20 +831,14 @@ public final class space/kscience/visionforge/html/VisionOfCheckbox$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract class space/kscience/visionforge/html/VisionOfHtml : space/kscience/visionforge/AbstractVision {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtml$Companion;
public fun <init> ()V
public synthetic fun <init> (ILspace/kscience/dataforge/meta/MutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public final fun getClasses ()Ljava/util/Set;
public final fun setClasses (Ljava/util/Set;)V
public static final synthetic fun write$Self (Lspace/kscience/visionforge/html/VisionOfHtml;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
public abstract interface class space/kscience/visionforge/html/VisionOfHtml : space/kscience/visionforge/Vision {
public fun getClasses ()Ljava/util/Set;
public fun getStyleString ()Ljava/lang/String;
public fun setClasses (Ljava/util/Set;)V
public fun setStyleString (Ljava/lang/String;)V
}
public final class space/kscience/visionforge/html/VisionOfHtml$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/html/VisionOfHtmlButton : space/kscience/visionforge/html/VisionOfHtmlControl, space/kscience/visionforge/ClickControl {
public final class space/kscience/visionforge/html/VisionOfHtmlButton : space/kscience/visionforge/AbstractControlVision, space/kscience/visionforge/DataControl, space/kscience/visionforge/html/VisionOfHtml {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlButton$Companion;
public fun <init> ()V
public final fun getLabel ()Ljava/lang/String;
@ -839,20 +860,7 @@ public final class space/kscience/visionforge/html/VisionOfHtmlButton$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract class space/kscience/visionforge/html/VisionOfHtmlControl : space/kscience/visionforge/html/VisionOfHtml, space/kscience/visionforge/ControlVision {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlControl$Companion;
public fun <init> ()V
public synthetic fun <init> (ILspace/kscience/dataforge/meta/MutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun dispatchControlEvent (Lspace/kscience/visionforge/VisionControlEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun getControlEventFlow ()Lkotlinx/coroutines/flow/SharedFlow;
public static final synthetic fun write$Self (Lspace/kscience/visionforge/html/VisionOfHtmlControl;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class space/kscience/visionforge/html/VisionOfHtmlControl$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/html/VisionOfHtmlForm : space/kscience/visionforge/html/VisionOfHtmlControl {
public final class space/kscience/visionforge/html/VisionOfHtmlForm : space/kscience/visionforge/AbstractControlVision, space/kscience/visionforge/DataControl, space/kscience/visionforge/html/VisionOfHtml {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlForm$Companion;
public fun <init> (Ljava/lang/String;)V
public final fun getFormId ()Ljava/lang/String;
@ -876,12 +884,14 @@ public final class space/kscience/visionforge/html/VisionOfHtmlForm$Companion {
}
public final class space/kscience/visionforge/html/VisionOfHtmlFormKt {
public static final fun bindForm (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/html/VisionOfHtmlForm;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun button (Lspace/kscience/visionforge/html/VisionOutput;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lspace/kscience/visionforge/html/VisionOfHtmlButton;
public static synthetic fun button$default (Lspace/kscience/visionforge/html/VisionOutput;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/visionforge/html/VisionOfHtmlButton;
public static final fun onFormSubmit (Lspace/kscience/visionforge/html/VisionOfHtmlForm;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/Job;
public static final fun visionOfForm (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/html/VisionOfHtmlForm;Ljava/lang/String;Lkotlinx/html/FormEncType;Lkotlinx/html/FormMethod;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static synthetic fun visionOfForm$default (Lkotlinx/html/TagConsumer;Lspace/kscience/visionforge/html/VisionOfHtmlForm;Ljava/lang/String;Lkotlinx/html/FormEncType;Lkotlinx/html/FormMethod;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
}
public class space/kscience/visionforge/html/VisionOfHtmlInput : space/kscience/visionforge/html/VisionOfHtmlControl {
public class space/kscience/visionforge/html/VisionOfHtmlInput : space/kscience/visionforge/AbstractControlVision, space/kscience/visionforge/html/VisionOfHtml {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlInput$Companion;
public synthetic fun <init> (ILspace/kscience/dataforge/meta/MutableMeta;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;)V
@ -951,7 +961,7 @@ public final class space/kscience/visionforge/html/VisionOfNumberField$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class space/kscience/visionforge/html/VisionOfPlainHtml : space/kscience/visionforge/html/VisionOfHtml {
public final class space/kscience/visionforge/html/VisionOfPlainHtml : space/kscience/visionforge/AbstractVision, space/kscience/visionforge/html/VisionOfHtml {
public static final field Companion Lspace/kscience/visionforge/html/VisionOfPlainHtml$Companion;
public fun <init> ()V
public final fun getContent ()Ljava/lang/String;
@ -1027,7 +1037,6 @@ public final class space/kscience/visionforge/html/VisionOutput : space/kscience
public final fun getMeta ()Lspace/kscience/dataforge/meta/Meta;
public final fun getName ()Lspace/kscience/dataforge/names/Name;
public final fun getVisionManager ()Lspace/kscience/visionforge/VisionManager;
public final fun meta (Lkotlin/jvm/functions/Function1;)V
public final fun requirePlugin (Lspace/kscience/dataforge/context/PluginFactory;)V
public final fun setMeta (Lspace/kscience/dataforge/meta/Meta;)V
}
@ -1098,6 +1107,11 @@ public abstract class space/kscience/visionforge/html/VisionTagConsumer : kotlin
public final class space/kscience/visionforge/html/VisionTagConsumer$Companion {
}
public final class space/kscience/visionforge/html/VisionTagConsumerKt {
public static final fun meta (Lspace/kscience/visionforge/html/VisionOutput;Lkotlin/jvm/functions/Function1;)V
public static final fun meta (Lspace/kscience/visionforge/html/VisionOutput;Lspace/kscience/dataforge/meta/MetaRepr;)V
}
public final class space/kscience/visionforge/visitor/CountDistinctKt {
public static final fun countDistinct (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun countDistinctBy (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

View File

@ -8,7 +8,7 @@ kscience {
jvm()
js()
native()
// wasm()
wasm()
useCoroutines()
commonMain {
api("space.kscience:dataforge-context:$dataforgeVersion")

View File

@ -2,12 +2,11 @@ package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
@ -23,6 +22,9 @@ public abstract class VisionControlEvent : VisionEvent, MetaRepr {
public interface ControlVision : Vision {
public val controlEventFlow: SharedFlow<VisionControlEvent>
/**
* Fire a [VisionControlEvent] on this [ControlVision]
*/
public suspend fun dispatchControlEvent(event: VisionControlEvent)
override suspend fun receiveEvent(event: VisionEvent) {
@ -32,6 +34,28 @@ public interface ControlVision : Vision {
}
}
public fun ControlVision.asyncControlEvent(
event: VisionControlEvent,
scope: CoroutineScope = manager?.context ?: error("Can't fire asynchronous event for an orphan vision. Provide a scope."),
) {
scope.launch { dispatchControlEvent(event) }
}
@Serializable
public abstract class AbstractControlVision : AbstractVision(), ControlVision {
@Transient
private val mutableControlEventFlow = MutableSharedFlow<VisionControlEvent>()
override val controlEventFlow: SharedFlow<VisionControlEvent>
get() = mutableControlEventFlow
override suspend fun dispatchControlEvent(event: VisionControlEvent) {
mutableControlEventFlow.emit(event)
}
}
/**
* An event for submitting changes

View File

@ -64,7 +64,7 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
}
/**
* List of names of styles applied to this object. Order matters. Not inherited.
* List of style names applied to this object. Order matters. Not inherited.
*/
public var Vision.styles: List<String>
get() = properties.own[Vision.STYLE_KEY]?.stringList ?: emptyList()

View File

@ -132,6 +132,8 @@ public class RootVision(override val manager: VisionManager) : AbstractVisionGro
* Designate this [Vision] as a root and assign a [VisionManager] as its parent
*/
public fun Vision.setAsRoot(manager: VisionManager) {
//do nothing if vision is already rooted
if(this.manager == manager) return
if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVision(manager)
}

View File

@ -2,31 +2,44 @@ package space.kscience.visionforge.html
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.html.DIV
import kotlinx.html.InputType
import kotlinx.html.div
import kotlinx.html.stream.createHTML
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.*
@Serializable
public abstract class VisionOfHtml : AbstractVision() {
public interface VisionOfHtml : Vision {
/**
* Html class strings for this instance. Does not use vision inheritance, but uses styles
*/
public var classes: Set<String>
get() = properties[::classes.name, false].stringList?.toSet() ?: emptySet()
get() = properties[::classes.name, false, true].stringList?.toSet() ?: emptySet()
set(value) {
properties[::classes.name] = value.map { it.asValue() }
}
/**
* A custom style string
*/
public var styleString: String?
get() = properties[::styleString.name,false,true].string
set(value){
properties[::styleString.name] = value?.asValue()
}
}
@Serializable
@SerialName("html.plain")
public class VisionOfPlainHtml : VisionOfHtml() {
public class VisionOfPlainHtml : AbstractVision(), VisionOfHtml {
public var content: String? by properties.string()
}
@ -59,30 +72,14 @@ public enum class InputFeedbackMode {
NONE
}
@Serializable
public abstract class VisionOfHtmlControl: VisionOfHtml(), ControlVision{
@Transient
private val mutableControlEventFlow = MutableSharedFlow<VisionControlEvent>()
override val controlEventFlow: SharedFlow<VisionControlEvent>
get() = mutableControlEventFlow
override suspend fun dispatchControlEvent(event: VisionControlEvent) {
mutableControlEventFlow.emit(event)
}
}
@Serializable
@SerialName("html.input")
public open class VisionOfHtmlInput(
public val inputType: String,
) : VisionOfHtmlControl() {
) : AbstractControlVision(), VisionOfHtml {
public var value: Value? by properties.value()
public var disabled: Boolean by properties.boolean { false }
public var fieldName: String? by properties.string()
}
/**

View File

@ -8,6 +8,7 @@ import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.string
import space.kscience.visionforge.AbstractControlVision
import space.kscience.visionforge.DataControl
import space.kscience.visionforge.onSubmit
@ -18,7 +19,7 @@ import space.kscience.visionforge.onSubmit
@SerialName("html.form")
public class VisionOfHtmlForm(
public val formId: String,
) : VisionOfHtmlControl(), DataControl {
) : AbstractControlVision(), DataControl, VisionOfHtml {
public var values: Meta? by properties.node()
}
@ -45,7 +46,7 @@ public fun VisionOfHtmlForm.onFormSubmit(scope: CoroutineScope, block: (Meta?) -
@Serializable
@SerialName("html.button")
public class VisionOfHtmlButton : VisionOfHtmlControl(), DataControl {
public class VisionOfHtmlButton : AbstractControlVision(), DataControl, VisionOfHtml {
public var label: String? by properties.string()
}

View File

@ -23,7 +23,7 @@ public annotation class VisionDSL
* A placeholder object to attach inline vision builders.
*/
@VisionDSL
public class VisionOutput @PublishedApi internal constructor(override val context: Context, public val name: Name): ContextAware {
public class VisionOutput(override val context: Context, public val name: Name): ContextAware {
public var meta: Meta = Meta.EMPTY
private val requirements: MutableSet<PluginFactory<*>> = HashSet()

View File

@ -10,6 +10,7 @@ import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.*
import space.kscience.visionforge.*
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@ -91,6 +92,7 @@ internal class VisionPropertyTest {
}
@Test
@Ignore
fun testChildrenPropertyFlow() = runTest(timeout = 500.milliseconds) {
val group = Global.request(VisionManager).group {

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge
package space.kscience.visionforge.html
import kotlinx.browser.document
import kotlinx.coroutines.CoroutineScope
@ -46,7 +46,7 @@ public interface Application: CoroutineScope {
}
public fun startApplication(builder: () -> Application) {
fun start(document: Document, state: dynamic): Application{
fun start(document: Document, state: dynamic): Application {
val application = builder()
@Suppress("UnsafeCastFromDynamic")

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge
package space.kscience.visionforge.html
import kotlinx.dom.clear
import kotlinx.html.TagConsumer
@ -8,6 +8,7 @@ import org.w3c.dom.HTMLElement
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DfType
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import kotlin.reflect.KClass
import kotlin.reflect.cast
@ -30,7 +31,6 @@ public interface ElementVisionRenderer {
*/
public fun render(
element: Element,
client: VisionClient,
name: Name,
vision: Vision,
meta: Meta = Meta.EMPTY,
@ -49,7 +49,7 @@ public interface ElementVisionRenderer {
public class SingleTypeVisionRenderer<T : Vision>(
public val kClass: KClass<T>,
private val acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
) : ElementVisionRenderer {
override fun rateVision(vision: Vision): Int =
@ -57,19 +57,18 @@ public class SingleTypeVisionRenderer<T : Vision>(
override fun render(
element: Element,
client: VisionClient,
name: Name,
vision: Vision,
meta: Meta,
) {
element.clear()
element.append {
renderFunction(name, client, kClass.cast(vision), meta)
renderFunction(name, kClass.cast(vision), meta)
}
}
}
public inline fun <reified T : Vision> ElementVisionRenderer(
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
noinline renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
noinline renderFunction: TagConsumer<HTMLElement>.(name: Name, vision: T, meta: Meta) -> Unit,
): ElementVisionRenderer = SingleTypeVisionRenderer(T::class, acceptRating, renderFunction)

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge
package space.kscience.visionforge.html
import kotlinx.browser.document
import kotlinx.browser.window
@ -23,7 +23,7 @@ import space.kscience.dataforge.meta.int
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.html.VisionTagConsumer
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE
@ -92,13 +92,6 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
eventCollector.emit(targetName to event)
}
private fun renderVision(element: Element, name: Name, vision: Vision, outputMeta: Meta) {
vision.setAsRoot(visionManager)
val renderer: ElementVisionRenderer =
findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}")
renderer.render(element, this, name, vision, outputMeta)
}
private fun startVisionUpdate(element: Element, visionName: Name, vision: Vision, outputMeta: Meta) {
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
@ -181,6 +174,24 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
}
}
private fun renderVision(element: Element, name: Name, vision: Vision, outputMeta: Meta) {
vision.setAsRoot(visionManager)
val renderer: ElementVisionRenderer =
findRendererFor(vision) ?: error("Could not find renderer for ${vision::class}")
//render vision
renderer.render(element, name, vision, outputMeta)
//start vision update from backend model
startVisionUpdate(element, name, vision, outputMeta)
//subscribe to a backwards events propagation for control visions
if(vision is ControlVision){
vision.controlEventFlow.onEach {
sendEvent(name,it)
}.launchIn(context)
}
}
/**
* Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class.
*/
@ -224,7 +235,6 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
response.text().then { text ->
val vision = visionManager.decodeFromString(text)
renderVision(element, name, vision, outputMeta)
startVisionUpdate(element, name, vision, outputMeta)
}
} else {
logger.error { "Failed to fetch initial vision state from $fetchUrl" }
@ -240,7 +250,6 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
}
logger.info { "Found embedded vision for output with name $name" }
renderVision(element, name, embeddedVision, outputMeta)
startVisionUpdate(element, name, embeddedVision, outputMeta)
}
//Try to load vision via websocket

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge
package space.kscience.visionforge.html
import kotlinx.browser.document
import kotlinx.coroutines.launch
@ -12,8 +12,7 @@ import space.kscience.dataforge.context.debug
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.html.VisionOfHtmlButton
import space.kscience.visionforge.html.VisionOfHtmlForm
import space.kscience.visionforge.*
/**
* Convert form data to Meta
@ -49,7 +48,7 @@ public fun VisionClient.sendMetaEvent(targetName: Name, payload: MetaRepr): Unit
}
internal val formVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfHtmlForm> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfHtmlForm> { name, vision, _ ->
val form = document.getElementById(vision.formId) as? HTMLFormElement
?: error("An element with id = '${vision.formId} is not a form")
@ -69,22 +68,18 @@ internal val formVisionRenderer: ElementVisionRenderer =
form.onsubmit = { event ->
event.preventDefault()
val formData = FormData(form).toMeta()
client.context.launch {
client.sendEvent(name, VisionSubmitEvent(name = name, payload = formData))
}
vision.asyncControlEvent(VisionSubmitEvent(name = name, payload = formData))
console.info("Sent form data: ${formData.toMap()}")
false
}
}
internal val buttonVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfHtmlButton> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfHtmlButton> { name, vision, _ ->
button(type = ButtonType.button).also { button ->
button.subscribeToVision(vision)
button.onclick = {
client.context.launch {
client.sendEvent(name, VisionSubmitEvent(name = name))
}
vision.asyncControlEvent(VisionSubmitEvent(name = name))
}
vision.useProperty(VisionOfHtmlButton::label) {
button.innerHTML = it ?: ""

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge
package space.kscience.visionforge.html
import kotlinx.dom.clear
import kotlinx.html.InputType
@ -9,7 +9,10 @@ import org.w3c.dom.HTMLInputElement
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.string
import space.kscience.visionforge.html.*
import space.kscience.visionforge.VisionInputEvent
import space.kscience.visionforge.VisionValueChangeEvent
import space.kscience.visionforge.asyncControlEvent
import space.kscience.visionforge.useProperty
/**
* Subscribes the HTML element to a given vision.
@ -20,6 +23,10 @@ internal fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
vision.useProperty(VisionOfHtml::classes) {
classList.value = classes.joinToString(separator = " ")
}
vision.useProperty(VisionOfHtml::styleString) {
style.cssText = it ?: ""
}
}
/**
@ -35,7 +42,7 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
}
internal val htmlVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfPlainHtml> { _, _, vision, _ ->
ElementVisionRenderer<VisionOfPlainHtml> { _, vision, _ ->
div().also { div ->
div.subscribeToVision(vision)
vision.useProperty(VisionOfPlainHtml::content) {
@ -47,17 +54,18 @@ internal val htmlVisionRenderer: ElementVisionRenderer =
internal val inputVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlInput>(
acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1
) { name, client, vision, _ ->
) { name, vision, _ ->
input {
type = InputType.text
}.also { htmlInputElement ->
htmlInputElement.onchange = {
client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.subscribeToInput(vision)
@ -68,17 +76,17 @@ internal val inputVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<
}
internal val checkboxVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfCheckbox> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfCheckbox> { name, vision, _ ->
input {
type = InputType.checkBox
}.also { htmlInputElement ->
htmlInputElement.onchange = {
client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
}
@ -90,17 +98,17 @@ internal val checkboxVisionRenderer: ElementVisionRenderer =
}
internal val textVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfTextField> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfTextField> { name, vision, _ ->
input {
type = InputType.text
}.also { htmlInputElement ->
htmlInputElement.onchange = {
client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionValueChangeEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.oninput = {
client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name))
vision.asyncControlEvent(VisionInputEvent(htmlInputElement.value.asValue(), name))
}
htmlInputElement.subscribeToInput(vision)
@ -111,20 +119,20 @@ internal val textVisionRenderer: ElementVisionRenderer =
}
internal val numberVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfNumberField> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfNumberField> { name, vision, _ ->
input {
type = InputType.number
}.also { htmlInputElement ->
htmlInputElement.onchange = {
htmlInputElement.value.toDoubleOrNull()?.let {
client.sendEventAsync(name, VisionValueChangeEvent(it.asValue(), name))
vision.asyncControlEvent(VisionValueChangeEvent(it.asValue(), name))
}
}
htmlInputElement.oninput = {
htmlInputElement.value.toDoubleOrNull()?.let {
client.sendEventAsync(name, VisionInputEvent(it.asValue(), name))
vision.asyncControlEvent(VisionInputEvent(it.asValue(), name))
}
}
@ -137,7 +145,7 @@ internal val numberVisionRenderer: ElementVisionRenderer =
}
internal val rangeVisionRenderer: ElementVisionRenderer =
ElementVisionRenderer<VisionOfRangeField> { name, client, vision, _ ->
ElementVisionRenderer<VisionOfRangeField> { name, vision, _ ->
input {
type = InputType.range
min = vision.min.toString()
@ -147,13 +155,13 @@ internal val rangeVisionRenderer: ElementVisionRenderer =
htmlInputElement.onchange = {
htmlInputElement.value.toDoubleOrNull()?.let {
client.sendEventAsync(name, VisionValueChangeEvent(it.asValue(), name))
vision.asyncControlEvent(VisionValueChangeEvent(it.asValue(), name))
}
}
htmlInputElement.oninput = {
htmlInputElement.value.toDoubleOrNull()?.let {
client.sendEventAsync(name, VisionInputEvent(it.asValue(), name))
vision.asyncControlEvent(VisionInputEvent(it.asValue(), name))
}
}

View File

@ -4,6 +4,7 @@ import org.w3c.xhr.FormData
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.stringList
import space.kscience.visionforge.html.toMeta
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -0,0 +1,65 @@
package space.kscience.visionforge.meta
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Timeout
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.*
import space.kscience.visionforge.*
import kotlin.test.Test
import kotlin.test.assertEquals
internal class PropertyFlowTest {
private val manager = Global.request(VisionManager)
@Test
@Timeout(200)
fun testChildrenPropertyFlow() = runBlocking{
val group = Global.request(VisionManager).group {
properties {
"test" put 11
}
group("child") {
properties {
"test" put 22
}
}
}
val child = group.children["child"]!!
val changesFlow = child.flowPropertyValue("test", inherit = true).map {
it!!.int
}
val collectedValues = ArrayList<Int>(5)
val collectorJob = changesFlow.onEach {
collectedValues.add(it)
}.launchIn(this)
delay(2)
assertEquals(22, child.properties["test", true].int)
child.properties.remove("test")
delay(2)
assertEquals(11, child.properties["test", true].int)
group.properties["test"] = 33
delay(2)
assertEquals(33, child.properties["test", true].int)
collectorJob.cancel()
assertEquals(listOf(22, 11, 33), collectedValues)
}
}

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-gdml:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-gdml:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-gdml:0.3.0-rc")
implementation("space.kscience:visionforge-gdml:0.4.0-dev-3")
}
```

View File

@ -6,7 +6,7 @@ Common visionforge jupyter module
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-jupyter:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-jupyter:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-jupyter:0.3.0-rc")
implementation("space.kscience:visionforge-jupyter:0.4.0-dev-3")
}
```

View File

@ -8,10 +8,10 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta
import space.kscience.visionforge.JsVisionClient
import space.kscience.visionforge.renderAllVisions
import space.kscience.visionforge.renderAllVisionsById
import space.kscience.visionforge.renderAllVisionsIn
import space.kscience.visionforge.html.JsVisionClient
import space.kscience.visionforge.html.renderAllVisions
import space.kscience.visionforge.html.renderAllVisionsById
import space.kscience.visionforge.html.renderAllVisionsIn
@JsExport
public class VFNotebookClient : AbstractPlugin() {

View File

@ -1,9 +1,9 @@
package space.kscience.visionforge.gdml.jupyter
import space.kscience.visionforge.html.runVisionClient
import space.kscience.visionforge.jupyter.VFNotebookClient
import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.runVisionClient
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.tables.TableVisionJsPlugin

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-markdown:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-markdown:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-markdown:0.3.0-rc")
implementation("space.kscience:visionforge-markdown:0.4.0-dev-3")
}
```

View File

@ -13,9 +13,13 @@ import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionPlugin
import space.kscience.visionforge.html.ElementVisionRenderer
import space.kscience.visionforge.html.JsVisionClient
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
import space.kscience.visionforge.useProperty
public actual class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
public val visionClient: JsVisionClient by require(JsVisionClient)
@ -27,7 +31,7 @@ public actual class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
else -> ElementVisionRenderer.ZERO_RATING
}
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
override fun render(element: Element,name: Name, vision: Vision, meta: Meta) {
require(vision is VisionOfMarkup) { "The vision is not a markup vision" }
val div = document.createElement("div")
val flavour = when (vision.format) {

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-plotly:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-plotly:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-plotly:0.3.0-rc")
implementation("space.kscience:visionforge-plotly:0.4.0-dev-3")
}
```

View File

@ -10,7 +10,10 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.plotly.PlotlyConfig
import space.kscience.plotly.plot
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionPlugin
import space.kscience.visionforge.html.ElementVisionRenderer
import space.kscience.visionforge.html.JsVisionClient
public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
public val visionClient: JsVisionClient by require(JsVisionClient)
@ -24,7 +27,7 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
else -> ElementVisionRenderer.ZERO_RATING
}
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
override fun render(element: Element, name: Name, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta)
element.plot(config, plot)

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-server:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-server:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-server:0.3.0-rc")
implementation("space.kscience:visionforge-server:0.4.0-dev-3")
}
```

View File

@ -6,7 +6,7 @@
## Artifact:
The Maven coordinates of this project are `space.kscience:visionforge-solid:0.3.0-rc`.
The Maven coordinates of this project are `space.kscience:visionforge-solid:0.4.0-dev-3`.
**Gradle Kotlin DSL:**
```kotlin
@ -16,6 +16,6 @@ repositories {
}
dependencies {
implementation("space.kscience:visionforge-solid:0.3.0-rc")
implementation("space.kscience:visionforge-solid:0.4.0-dev-3")
}
```

View File

@ -747,6 +747,7 @@ public final class space/kscience/visionforge/solid/SolidKt {
public static final fun getY (Lspace/kscience/visionforge/solid/Solid;)Ljava/lang/Number;
public static final fun getZ (Lspace/kscience/visionforge/solid/Solid;)Ljava/lang/Number;
public static final fun rotate (Lspace/kscience/visionforge/solid/Solid;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Vector3D;)V
public static final fun scale (Lspace/kscience/visionforge/solid/Solid;Ljava/lang/Number;)V
public static final fun setDetail (Lspace/kscience/visionforge/solid/Solid;Ljava/lang/Integer;)V
public static final fun setIgnore (Lspace/kscience/visionforge/Vision;Ljava/lang/Boolean;)V
public static final fun setLayer (Lspace/kscience/visionforge/solid/Solid;I)V
@ -802,8 +803,10 @@ public final class space/kscience/visionforge/solid/SolidMaterial : space/kscien
public final fun getEmissiveColor ()Lspace/kscience/visionforge/solid/ColorAccessor;
public final fun getOpacity ()F
public final fun getSpecularColor ()Lspace/kscience/visionforge/solid/ColorAccessor;
public final fun getType ()Ljava/lang/String;
public final fun getWireframe ()Z
public final fun setOpacity (F)V
public final fun setType (Ljava/lang/String;)V
public final fun setWireframe (Z)V
}
@ -1087,23 +1090,6 @@ public final class space/kscience/visionforge/solid/SurfaceKt {
public static synthetic fun surface$default (Lspace/kscience/visionforge/MutableVisionContainer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/visionforge/solid/Surface;
}
public final class space/kscience/visionforge/solid/specifications/AxesScheme : space/kscience/dataforge/meta/Scheme {
public static final field AXIS_SIZE D
public static final field AXIS_WIDTH D
public static final field Companion Lspace/kscience/visionforge/solid/specifications/AxesScheme$Companion;
public fun <init> ()V
public final fun getSize ()D
public final fun getVisible ()Z
public final fun getWidth ()D
public final fun setSize (D)V
public final fun setVisible (Z)V
public final fun setWidth (D)V
}
public final class space/kscience/visionforge/solid/specifications/AxesScheme$Companion : space/kscience/dataforge/meta/SchemeSpec {
public fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;
}
public final class space/kscience/visionforge/solid/specifications/CameraScheme : space/kscience/dataforge/meta/Scheme {
public static final field Companion Lspace/kscience/visionforge/solid/specifications/CameraScheme$Companion;
public static final field FAR_CLIP D
@ -1138,15 +1124,15 @@ public final class space/kscience/visionforge/solid/specifications/CameraSchemeK
public final class space/kscience/visionforge/solid/specifications/Canvas3DOptions : space/kscience/dataforge/meta/Scheme {
public static final field Companion Lspace/kscience/visionforge/solid/specifications/Canvas3DOptions$Companion;
public fun <init> ()V
public final fun getAxes ()Lspace/kscience/visionforge/solid/specifications/AxesScheme;
public final fun getCamera ()Lspace/kscience/visionforge/solid/specifications/CameraScheme;
public final fun getCanvasName ()Ljava/lang/String;
public final fun getClipping ()Lspace/kscience/visionforge/solid/specifications/PointScheme;
public final fun getControls ()Lspace/kscience/visionforge/solid/specifications/Canvas3DUIScheme;
public final fun getLayers ()Ljava/util/List;
public final fun getOnSelect ()Lkotlin/jvm/functions/Function1;
public final fun getSize ()Lspace/kscience/visionforge/solid/specifications/CanvasSize;
public final fun setAxes (Lspace/kscience/visionforge/solid/specifications/AxesScheme;)V
public final fun setCamera (Lspace/kscience/visionforge/solid/specifications/CameraScheme;)V
public final fun setCanvasName (Ljava/lang/String;)V
public final fun setClipping (Lspace/kscience/visionforge/solid/specifications/PointScheme;)V
public final fun setControls (Lspace/kscience/visionforge/solid/specifications/Canvas3DUIScheme;)V
public final fun setLayers (Ljava/util/List;)V
@ -1171,6 +1157,7 @@ public final class space/kscience/visionforge/solid/specifications/Canvas3DUISch
}
public final class space/kscience/visionforge/solid/specifications/Canvas3DUIScheme$Companion : space/kscience/dataforge/meta/SchemeSpec {
public fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;
}
public final class space/kscience/visionforge/solid/specifications/CanvasSize : space/kscience/dataforge/meta/Scheme {

View File

@ -8,6 +8,7 @@ kscience {
jvm()
js()
native()
// wasm()
useSerialization {
json()
}

Some files were not shown because too many files have changed in this diff Show More