rename modules

This commit is contained in:
Alexander Nozik 2024-02-12 16:44:15 +03:00
parent cbf3f4941a
commit 38d6a9c419
54 changed files with 48 additions and 2323 deletions

View File

@ -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

View File

@ -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.*

View File

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

View File

@ -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

View File

@ -40,13 +40,9 @@ dependencyResolutionManagement {
}
include(
// ":ui",
// ":ui:react",
// ":ui:ring",
// ":ui:material",
// ":ui:bootstrap",
":visionforge-compose-html",
":visionforge-core",
":visionforge-compose-html",
":visionforge-compose-mpp",
":visionforge-solid",
// ":visionforge-fx",
":visionforge-threejs",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package space.kscience.visionforge.compose
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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)
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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