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.visionforge.Application
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.compose.TreeStyles
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.TreeStyles
|
||||
import space.kscience.visionforge.solid.ambientLight
|
||||
import space.kscience.visionforge.solid.invoke
|
||||
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.visionforge.Application
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.compose.Tabs
|
||||
import space.kscience.visionforge.compose.TreeStyles
|
||||
import space.kscience.visionforge.html.Tabs
|
||||
import space.kscience.visionforge.html.TreeStyles
|
||||
import space.kscience.visionforge.markup.MarkupPlugin
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.solid.*
|
||||
|
@ -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.*
|
||||
|
@ -7,7 +7,7 @@ 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.VisionForgeStyles
|
||||
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||
import space.kscience.visionforge.startApplication
|
||||
|
||||
|
@ -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-mpp",
|
||||
":visionforge-solid",
|
||||
// ":visionforge-fx",
|
||||
":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 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 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 org.jetbrains.compose.web.dom.*
|
@ -1,4 +1,4 @@
|
||||
package space.kscience.visionforge.compose
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import app.softwork.bootstrapcompose.CloseButton
|
@ -1,4 +1,4 @@
|
||||
package space.kscience.visionforge.compose
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
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.css.*
|
@ -1,4 +1,4 @@
|
||||
package space.kscience.visionforge.compose
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
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 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 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.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.keywords.CSSAutoKeyword
|
@ -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
|
@ -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
|
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.names.*
|
||||
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.specifications.Canvas3DOptions
|
||||
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.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.compose.*
|
||||
import space.kscience.visionforge.encodeToString
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||
|
||||
@Composable
|
||||
|
@ -13,7 +13,7 @@ import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.names.Name
|
||||
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.solid.Solid
|
||||
import space.kscience.visionforge.solid.SolidGroup
|
||||
|
Loading…
Reference in New Issue
Block a user