Add compose-html
This commit is contained in:
parent
ed71ba9ccb
commit
e6bdb67262
@ -0,0 +1,44 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import org.jetbrains.compose.web.dom.Li
|
||||||
|
import org.jetbrains.compose.web.dom.Nav
|
||||||
|
import org.jetbrains.compose.web.dom.Ol
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.NameToken
|
||||||
|
import space.kscience.dataforge.names.length
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
||||||
|
attr("aria-label","breadcrumb")
|
||||||
|
}) {
|
||||||
|
Ol({classes("breadcrumb")}) {
|
||||||
|
Li({
|
||||||
|
classes("breadcrumb-item")
|
||||||
|
onClick {
|
||||||
|
link(Name.EMPTY)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("\u2302")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
val tokens = ArrayList<NameToken>(name.length)
|
||||||
|
name.tokens.forEach { token ->
|
||||||
|
tokens.add(token)
|
||||||
|
val fullName = Name(tokens.toList())
|
||||||
|
Text(".")
|
||||||
|
Li({
|
||||||
|
classes("breadcrumb-item")
|
||||||
|
if(tokens.size == name.length) classes("active")
|
||||||
|
onClick {
|
||||||
|
link(fullName)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(token.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,10 +21,7 @@ import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
|||||||
import space.kscience.dataforge.meta.descriptors.get
|
import space.kscience.dataforge.meta.descriptors.get
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.remove
|
import space.kscience.dataforge.meta.remove
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.dataforge.names.NameToken
|
|
||||||
import space.kscience.dataforge.names.isEmpty
|
|
||||||
import space.kscience.dataforge.names.lastOrNull
|
|
||||||
import space.kscience.visionforge.hidden
|
import space.kscience.visionforge.hidden
|
||||||
|
|
||||||
|
|
||||||
@ -39,19 +36,17 @@ public sealed class EditorPropertyState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param meta Root config object - always non-null
|
||||||
* @param rootDescriptor Full path to the displayed node in [meta]. Could be empty
|
* @param rootDescriptor Full path to the displayed node in [meta]. Could be empty
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun PropertyEditorItem(
|
public fun PropertyEditor(
|
||||||
/**
|
scope: CoroutineScope,
|
||||||
* Root config object - always non-null
|
|
||||||
*/
|
|
||||||
meta: MutableMeta,
|
meta: MutableMeta,
|
||||||
getPropertyState: (Name) -> EditorPropertyState,
|
getPropertyState: (Name) -> EditorPropertyState,
|
||||||
scope: CoroutineScope,
|
|
||||||
updates: Flow<Name>,
|
updates: Flow<Name>,
|
||||||
name: Name,
|
name: Name = Name.EMPTY,
|
||||||
rootDescriptor: MetaDescriptor?,
|
rootDescriptor: MetaDescriptor? = null,
|
||||||
initialExpanded: Boolean? = null,
|
initialExpanded: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
|
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
|
||||||
@ -145,7 +140,7 @@ private fun PropertyEditorItem(
|
|||||||
Div({
|
Div({
|
||||||
classes(TreeStyles.treeItem)
|
classes(TreeStyles.treeItem)
|
||||||
}) {
|
}) {
|
||||||
PropertyEditorItem(meta, getPropertyState, scope, updates, name, descriptor, expanded)
|
PropertyEditor(scope, meta, getPropertyState, updates, name + token, descriptor, expanded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +154,8 @@ public fun PropertyEditor(
|
|||||||
descriptor: MetaDescriptor? = null,
|
descriptor: MetaDescriptor? = null,
|
||||||
expanded: Boolean? = null,
|
expanded: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
PropertyEditorItem(
|
PropertyEditor(
|
||||||
|
scope = scope,
|
||||||
meta = properties,
|
meta = properties,
|
||||||
getPropertyState = { name ->
|
getPropertyState = { name ->
|
||||||
if (properties[name] != null) {
|
if (properties[name] != null) {
|
||||||
@ -170,7 +166,6 @@ public fun PropertyEditor(
|
|||||||
EditorPropertyState.Undefined
|
EditorPropertyState.Undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scope = scope,
|
|
||||||
updates = callbackFlow {
|
updates = callbackFlow {
|
||||||
properties.onChange(scope) { name ->
|
properties.onChange(scope) { name ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import org.jetbrains.compose.web.dom.*
|
||||||
|
import org.w3c.dom.HTMLDivElement
|
||||||
|
import org.w3c.dom.HTMLLIElement
|
||||||
|
|
||||||
|
|
||||||
|
public class ComposeTab(
|
||||||
|
public val key: String,
|
||||||
|
public val title: String,
|
||||||
|
public val content: ContentBuilder<HTMLDivElement>,
|
||||||
|
public val disabled: Boolean,
|
||||||
|
public val titleExt: ContentBuilder<HTMLLIElement>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Tabs(tabs: List<ComposeTab>, activeKey: String) {
|
||||||
|
var active by remember(activeKey) { mutableStateOf(activeKey) }
|
||||||
|
|
||||||
|
Div({ classes("card", "text-center") }) {
|
||||||
|
Div({ classes("card-header") }) {
|
||||||
|
|
||||||
|
Ul({ classes("nav", "nav-tabs", "card-header-tabs") }) {
|
||||||
|
tabs.forEach { tab ->
|
||||||
|
Li({
|
||||||
|
classes("nav-item")
|
||||||
|
}) {
|
||||||
|
A(attrs = {
|
||||||
|
classes("nav-link")
|
||||||
|
if (active == tab.key) {
|
||||||
|
classes("active")
|
||||||
|
}
|
||||||
|
if (tab.disabled) {
|
||||||
|
classes("disabled")
|
||||||
|
}
|
||||||
|
onClick {
|
||||||
|
active = tab.key
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(tab.title)
|
||||||
|
}
|
||||||
|
tab.titleExt.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tabs.find { it.key == active }?.let { tab ->
|
||||||
|
Div({ classes("card-body") }) {
|
||||||
|
tab.content.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TabBuilder internal constructor(public val key: String) {
|
||||||
|
private var title: String = key
|
||||||
|
public var disabled: Boolean = false
|
||||||
|
private var content: ContentBuilder<HTMLDivElement> = {}
|
||||||
|
private var titleExt: ContentBuilder<HTMLLIElement> = {}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Content(content: ContentBuilder<HTMLDivElement>) {
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Title(title: String, titleExt: ContentBuilder<HTMLLIElement> = {}) {
|
||||||
|
this.title = title
|
||||||
|
this.titleExt = titleExt
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun build(): ComposeTab = ComposeTab(
|
||||||
|
key,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
disabled,
|
||||||
|
titleExt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TabsBuilder {
|
||||||
|
public var active: String = ""
|
||||||
|
internal val tabs: MutableList<ComposeTab> = mutableListOf()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Tab(key: String, builder: @Composable TabBuilder.() -> Unit) {
|
||||||
|
tabs.add(TabBuilder(key).apply { builder() }.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun addTab(tab: ComposeTab) {
|
||||||
|
tabs.add(tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Tabs(builder: @Composable TabsBuilder.() -> Unit) {
|
||||||
|
val result = TabsBuilder().apply { builder() }
|
||||||
|
Tabs(result.tabs, result.active)
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import org.jetbrains.compose.web.css.*
|
||||||
|
import org.jetbrains.compose.web.dom.Button
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
import org.w3c.files.Blob
|
||||||
|
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.encodeToString
|
||||||
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun CanvasControls(
|
||||||
|
vision: Vision?,
|
||||||
|
options: Canvas3DOptions,
|
||||||
|
) {
|
||||||
|
FlexColumn {
|
||||||
|
FlexRow({
|
||||||
|
style {
|
||||||
|
border {
|
||||||
|
width(1.px)
|
||||||
|
style(LineStyle.Solid)
|
||||||
|
color(Color("blue"))
|
||||||
|
}
|
||||||
|
padding(4.px)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
vision?.let { vision ->
|
||||||
|
Button({
|
||||||
|
onClick { event ->
|
||||||
|
val json = vision.encodeToString()
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
|
||||||
|
val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
|
||||||
|
fileSaver.saveAs(blob, "object.json") as Unit
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Export")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyEditor(
|
||||||
|
scope = vision?.manager?.context ?: Global,
|
||||||
|
properties = options.meta,
|
||||||
|
descriptor = Canvas3DOptions.descriptor,
|
||||||
|
expanded = false
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ThreeControls(
|
||||||
|
vision: Vision?,
|
||||||
|
canvasOptions: Canvas3DOptions,
|
||||||
|
selected: Name?,
|
||||||
|
onSelect: (Name?) -> Unit,
|
||||||
|
tabBuilder: @Composable TabsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
Tabs {
|
||||||
|
active = "Tree"
|
||||||
|
vision?.let { vision ->
|
||||||
|
Tab("Tree") {
|
||||||
|
CardTitle("Vision tree")
|
||||||
|
VisionTree(vision, Name.EMPTY, selected, onSelect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab("Settings") {
|
||||||
|
CardTitle("Canvas configuration")
|
||||||
|
CanvasControls(vision, canvasOptions)
|
||||||
|
}
|
||||||
|
tabBuilder()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
@file:OptIn(ExperimentalComposeWebApi::class)
|
||||||
|
|
||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import app.softwork.bootstrapcompose.Card
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.jetbrains.compose.web.ExperimentalComposeWebApi
|
||||||
|
import org.jetbrains.compose.web.css.*
|
||||||
|
import org.jetbrains.compose.web.dom.*
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.isEmpty
|
||||||
|
import space.kscience.visionforge.*
|
||||||
|
import space.kscience.visionforge.solid.Solid
|
||||||
|
import space.kscience.visionforge.solid.SolidGroup
|
||||||
|
import space.kscience.visionforge.solid.Solids
|
||||||
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ThreeCanvasWithControls(
|
||||||
|
solids: Solids,
|
||||||
|
builderOfSolid: Deferred<Solid?>,
|
||||||
|
initialSelected: Name?,
|
||||||
|
options: Canvas3DOptions?,
|
||||||
|
tabBuilder: @Composable TabsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
var selected: Name? by remember { mutableStateOf(initialSelected) }
|
||||||
|
var solid: Solid? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(builderOfSolid) {
|
||||||
|
solids.context.launch {
|
||||||
|
solid = builderOfSolid.await()
|
||||||
|
//ensure that the solid is properly rooted
|
||||||
|
if (solid?.parent == null) {
|
||||||
|
solid?.setAsRoot(solids.context.visionManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val optionsWithSelector = remember(options) {
|
||||||
|
(options ?: Canvas3DOptions()).apply {
|
||||||
|
this.onSelect = {
|
||||||
|
selected = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedVision: Vision? = remember(builderOfSolid, selected) {
|
||||||
|
selected?.let {
|
||||||
|
when {
|
||||||
|
it.isEmpty() -> solid
|
||||||
|
else -> (solid as? SolidGroup)?.get(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FlexRow({
|
||||||
|
style {
|
||||||
|
height(100.percent)
|
||||||
|
width(100.percent)
|
||||||
|
flexWrap(FlexWrap.Wrap)
|
||||||
|
alignItems(AlignItems.Stretch)
|
||||||
|
alignContent(AlignContent.Stretch)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
FlexColumn({
|
||||||
|
style {
|
||||||
|
height(100.percent)
|
||||||
|
minWidth(600.px)
|
||||||
|
flex(10, 1, 600.px)
|
||||||
|
position(Position.Relative)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
if (solid == null) {
|
||||||
|
Div({
|
||||||
|
style {
|
||||||
|
position(Position.Fixed)
|
||||||
|
width(100.percent)
|
||||||
|
height(100.percent)
|
||||||
|
zIndex(1000)
|
||||||
|
top(40.percent)
|
||||||
|
left(0.px)
|
||||||
|
opacity(0.5)
|
||||||
|
filter {
|
||||||
|
opacity(50.percent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Div({ classes("d-flex", " justify-content-center") }) {
|
||||||
|
Div({
|
||||||
|
classes("spinner-grow", "text-primary")
|
||||||
|
style {
|
||||||
|
width(3.cssRem)
|
||||||
|
height(3.cssRem)
|
||||||
|
zIndex(20)
|
||||||
|
}
|
||||||
|
attr("role", "status")
|
||||||
|
}) {
|
||||||
|
Span({ classes("sr-only") }) { Text("Loading 3D vision") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ThreeCanvas(solids.context, optionsWithSelector, solid, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedVision?.let { vision ->
|
||||||
|
Div({
|
||||||
|
style {
|
||||||
|
position(Position.Absolute)
|
||||||
|
top(5.px)
|
||||||
|
right(5.px)
|
||||||
|
width(450.px)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Card(
|
||||||
|
headerAttrs = {
|
||||||
|
// border = true
|
||||||
|
},
|
||||||
|
header = {
|
||||||
|
NameCrumbs(selected) { selected = it }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
PropertyEditor(
|
||||||
|
scope = solids.context,
|
||||||
|
meta = vision.properties.root(),
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updates = vision.properties.changes,
|
||||||
|
rootDescriptor = vision.descriptor
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
|
||||||
|
P {
|
||||||
|
B { Text("Styles: ") }
|
||||||
|
Text(styles.joinToString(separator = ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlexColumn({
|
||||||
|
style {
|
||||||
|
paddingAll(4.px)
|
||||||
|
minWidth(400.px)
|
||||||
|
height(100.percent)
|
||||||
|
overflowY("auto")
|
||||||
|
flex(1, 10, 300.px)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
ThreeControls(solid, optionsWithSelector, selected, onSelect = { selected = it }, tabBuilder = tabBuilder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import org.jetbrains.compose.web.css.Color
|
||||||
|
import org.jetbrains.compose.web.css.color
|
||||||
|
import org.jetbrains.compose.web.css.cursor
|
||||||
|
import org.jetbrains.compose.web.css.textDecorationLine
|
||||||
|
import org.jetbrains.compose.web.dom.Div
|
||||||
|
import org.jetbrains.compose.web.dom.Span
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
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.compose.TreeStyles.hover
|
||||||
|
import space.kscience.visionforge.compose.TreeStyles.invoke
|
||||||
|
import space.kscience.visionforge.isEmpty
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TreeLabel(
|
||||||
|
vision: Vision,
|
||||||
|
name: Name,
|
||||||
|
selected: Name?,
|
||||||
|
clickCallback: (Name) -> Unit,
|
||||||
|
) {
|
||||||
|
Span({
|
||||||
|
classes(TreeStyles.treeLabel)
|
||||||
|
if (name == selected) {
|
||||||
|
classes(TreeStyles.treeLabelSelected)
|
||||||
|
}
|
||||||
|
style {
|
||||||
|
color(Color("#069"))
|
||||||
|
cursor("pointer")
|
||||||
|
hover.invoke {
|
||||||
|
textDecorationLine("underline")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
onClick { clickCallback(name) }
|
||||||
|
}) {
|
||||||
|
Text(name.lastOrNull()?.toString() ?: "World")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun VisionTree(
|
||||||
|
vision: Vision,
|
||||||
|
name: Name = Name.EMPTY,
|
||||||
|
selected: Name? = null,
|
||||||
|
clickCallback: (Name) -> Unit,
|
||||||
|
): Unit {
|
||||||
|
var expanded: Boolean by remember { mutableStateOf(selected?.startsWith(name) ?: false) }
|
||||||
|
|
||||||
|
//display as node if any child is visible
|
||||||
|
if (vision is VisionGroup) {
|
||||||
|
FlexRow {
|
||||||
|
if (vision.children.keys.any { !it.body.startsWith("@") }) {
|
||||||
|
Span({
|
||||||
|
classes(TreeStyles.treeCaret)
|
||||||
|
if (expanded) {
|
||||||
|
classes(TreeStyles.treeCaretDown)
|
||||||
|
}
|
||||||
|
onClick {
|
||||||
|
expanded = !expanded
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
TreeLabel(vision, name, selected, clickCallback)
|
||||||
|
}
|
||||||
|
if (expanded) {
|
||||||
|
FlexColumn({
|
||||||
|
classes(TreeStyles.tree)
|
||||||
|
}) {
|
||||||
|
vision.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) ->
|
||||||
|
Div({ classes(TreeStyles.treeItem) }) {
|
||||||
|
VisionTree(
|
||||||
|
child,
|
||||||
|
name + childToken,
|
||||||
|
selected,
|
||||||
|
clickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TreeLabel(vision, name, selected, clickCallback)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import org.jetbrains.compose.web.dom.H5
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun CardTitle(title: String): Unit = H5({ classes("card-title") }) { Text(title) }
|
@ -1,6 +1,7 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
||||||
|
import org.jetbrains.compose.web.css.keywords.CSSAutoKeyword
|
||||||
|
|
||||||
public enum class UserSelect {
|
public enum class UserSelect {
|
||||||
inherit, initial, revert, revertLayer, unset,
|
inherit, initial, revert, revertLayer, unset,
|
||||||
@ -33,3 +34,11 @@ public fun StyleScope.marginAll(
|
|||||||
) {
|
) {
|
||||||
margin(top, right, bottom, left)
|
margin(top, right, bottom, left)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.zIndex(value: Int) {
|
||||||
|
property("z-index", "$value")
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.zIndex(value: CSSAutoKeyword) {
|
||||||
|
property("z-index", value)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user