rename modules
This commit is contained in:
parent
cbf3f4941a
commit
38d6a9c419
@ -8,8 +8,8 @@ import space.kscience.dataforge.context.Context
|
|||||||
import space.kscience.gdml.GdmlShowCase
|
import space.kscience.gdml.GdmlShowCase
|
||||||
import space.kscience.visionforge.Application
|
import space.kscience.visionforge.Application
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.compose.TreeStyles
|
|
||||||
import space.kscience.visionforge.gdml.toVision
|
import space.kscience.visionforge.gdml.toVision
|
||||||
|
import space.kscience.visionforge.html.TreeStyles
|
||||||
import space.kscience.visionforge.solid.ambientLight
|
import space.kscience.visionforge.solid.ambientLight
|
||||||
import space.kscience.visionforge.solid.invoke
|
import space.kscience.visionforge.solid.invoke
|
||||||
import space.kscience.visionforge.solid.three.ThreePlugin
|
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||||
|
@ -7,8 +7,8 @@ import space.kscience.plotly.models.Trace
|
|||||||
import space.kscience.plotly.scatter
|
import space.kscience.plotly.scatter
|
||||||
import space.kscience.visionforge.Application
|
import space.kscience.visionforge.Application
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.compose.Tabs
|
import space.kscience.visionforge.html.Tabs
|
||||||
import space.kscience.visionforge.compose.TreeStyles
|
import space.kscience.visionforge.html.TreeStyles
|
||||||
import space.kscience.visionforge.markup.MarkupPlugin
|
import space.kscience.visionforge.markup.MarkupPlugin
|
||||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
|
@ -15,8 +15,8 @@ import space.kscience.plotly.Plot
|
|||||||
import space.kscience.plotly.layout
|
import space.kscience.plotly.layout
|
||||||
import space.kscience.plotly.models.Trace
|
import space.kscience.plotly.models.Trace
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.compose.Vision
|
import space.kscience.visionforge.html.Vision
|
||||||
import space.kscience.visionforge.compose.zIndex
|
import space.kscience.visionforge.html.zIndex
|
||||||
import space.kscience.visionforge.markup.VisionOfMarkup
|
import space.kscience.visionforge.markup.VisionOfMarkup
|
||||||
import space.kscience.visionforge.plotly.asVision
|
import space.kscience.visionforge.plotly.asVision
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
|
@ -7,7 +7,7 @@ import space.kscience.dataforge.context.Context
|
|||||||
import space.kscience.dataforge.context.request
|
import space.kscience.dataforge.context.request
|
||||||
import space.kscience.visionforge.Application
|
import space.kscience.visionforge.Application
|
||||||
import space.kscience.visionforge.VisionManager
|
import space.kscience.visionforge.VisionManager
|
||||||
import space.kscience.visionforge.compose.VisionForgeStyles
|
import space.kscience.visionforge.html.VisionForgeStyles
|
||||||
import space.kscience.visionforge.solid.three.ThreePlugin
|
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||||
import space.kscience.visionforge.startApplication
|
import space.kscience.visionforge.startApplication
|
||||||
|
|
||||||
|
@ -40,13 +40,9 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
include(
|
include(
|
||||||
// ":ui",
|
|
||||||
// ":ui:react",
|
|
||||||
// ":ui:ring",
|
|
||||||
// ":ui:material",
|
|
||||||
// ":ui:bootstrap",
|
|
||||||
":visionforge-compose-html",
|
|
||||||
":visionforge-core",
|
":visionforge-core",
|
||||||
|
":visionforge-compose-html",
|
||||||
|
":visionforge-compose-mpp",
|
||||||
":visionforge-solid",
|
":visionforge-solid",
|
||||||
// ":visionforge-fx",
|
// ":visionforge-fx",
|
||||||
":visionforge-threejs",
|
":visionforge-threejs",
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# Module ui
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
# Module bootstrap
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
# Module react
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
@ -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))
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
# Module ring
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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"))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
3
ui/ring/webpack.config.d/01.ring.js
vendored
3
ui/ring/webpack.config.d/01.ring.js
vendored
@ -1,3 +0,0 @@
|
|||||||
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
|
|
||||||
|
|
||||||
config.module.rules.push(...ringConfig.module.rules)
|
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.jetbrains.compose.web.css.Style
|
import org.jetbrains.compose.web.css.Style
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import org.jetbrains.compose.web.css.AlignItems
|
import org.jetbrains.compose.web.css.AlignItems
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.jetbrains.compose.web.dom.*
|
import org.jetbrains.compose.web.dom.*
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import app.softwork.bootstrapcompose.CloseButton
|
import app.softwork.bootstrapcompose.CloseButton
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import app.softwork.bootstrapcompose.Card
|
import app.softwork.bootstrapcompose.Card
|
@ -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.ExperimentalComposeWebApi
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import org.jetbrains.compose.web.css.StyleSheet
|
import org.jetbrains.compose.web.css.StyleSheet
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import org.jetbrains.compose.web.css.Color
|
import org.jetbrains.compose.web.css.Color
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.jetbrains.compose.web.dom.H5
|
import org.jetbrains.compose.web.dom.H5
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
@ -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.*
|
||||||
import org.jetbrains.compose.web.css.keywords.CSSAutoKeyword
|
import org.jetbrains.compose.web.css.keywords.CSSAutoKeyword
|
@ -1,4 +1,4 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.jetbrains.compose.web.css.DisplayStyle
|
import org.jetbrains.compose.web.css.DisplayStyle
|
@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("UNUSED_PARAMETER")
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import kotlinx.uuid.UUID
|
import kotlinx.uuid.UUID
|
24
visionforge-compose-mpp/build.gradle.kts
Normal file
24
visionforge-compose-mpp/build.gradle.kts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
plugins {
|
||||||
|
id("space.kscience.gradle.mpp")
|
||||||
|
alias(spclibs.plugins.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
kscience {
|
||||||
|
jvm()
|
||||||
|
wasm()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
// android()
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api(projects.visionforgeCore)
|
||||||
|
api(compose.runtime)
|
||||||
|
api(compose.foundation)
|
||||||
|
api(compose.material)
|
||||||
|
api(compose.preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import space.kscience.dataforge.context.*
|
|||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.visionforge.*
|
import space.kscience.visionforge.*
|
||||||
import space.kscience.visionforge.compose.ComposeVisionRenderer
|
import space.kscience.visionforge.html.ComposeVisionRenderer
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
import space.kscience.visionforge.solid.three.compose.ThreeView
|
import space.kscience.visionforge.solid.three.compose.ThreeView
|
||||||
|
@ -12,8 +12,8 @@ import org.w3c.files.BlobPropertyBag
|
|||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.compose.*
|
|
||||||
import space.kscience.visionforge.encodeToString
|
import space.kscience.visionforge.encodeToString
|
||||||
|
import space.kscience.visionforge.html.*
|
||||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -13,7 +13,7 @@ import space.kscience.dataforge.context.Context
|
|||||||
import space.kscience.dataforge.context.request
|
import space.kscience.dataforge.context.request
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.isEmpty
|
import space.kscience.dataforge.names.isEmpty
|
||||||
import space.kscience.visionforge.compose.*
|
import space.kscience.visionforge.html.*
|
||||||
import space.kscience.visionforge.root
|
import space.kscience.visionforge.root
|
||||||
import space.kscience.visionforge.solid.Solid
|
import space.kscience.visionforge.solid.Solid
|
||||||
import space.kscience.visionforge.solid.SolidGroup
|
import space.kscience.visionforge.solid.SolidGroup
|
||||||
|
Loading…
Reference in New Issue
Block a user