From 399be206be9e2c455db4895aacc770efd0ff7a86 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 13 Nov 2023 21:45:37 +0300 Subject: [PATCH] Non-nullable accessor for colors --- CHANGELOG.md | 2 +- build.gradle.kts | 2 +- demo/muon-monitor/build.gradle.kts | 4 - gradle.properties | 2 +- ui/compose/build.gradle.kts | 41 ++++++ .../visionforge/compose/MetaViewer.kt | 130 ++++++++++++++++++ .../visionforge/compose/TreeStyles.kt | 71 ++++++++++ .../kscience/visionforge/compose/layouts.kt | 41 ++++++ .../visionforge/solid/ColorAccessor.kt | 22 +-- .../visionforge/solid/SolidPropertyTest.kt | 2 +- .../visionforge/solid/SolidReferenceTest.kt | 2 +- visionforge-tables/build.gradle.kts | 6 +- 12 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 ui/compose/build.gradle.kts create mode 100644 ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt create mode 100644 ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt create mode 100644 ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fe2343..bf2c3478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Custom client-side events and thier processing in VisionServer ### Changed -- Color accessor property is now `colorProperty`. Color uses `invoke` instead of `set` +- Color accessor property is now `colorProperty`. Color uses non-nullable `invoke` instead of `set`. - API update for server and pages - Edges moved to solids module for easier construction - Visions **must** be rooted in order to subscribe to updates. diff --git a/build.gradle.kts b/build.gradle.kts index 4e97dbe6..9f982d94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val fxVersion by extra("11") allprojects { group = "space.kscience" - version = "0.3.0-dev-14" + version = "0.3.0-dev-15" } subprojects { diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index e453492b..d01b9dc9 100644 --- a/demo/muon-monitor/build.gradle.kts +++ b/demo/muon-monitor/build.gradle.kts @@ -13,7 +13,6 @@ kscience { useKtor() fullStack( "muon-monitor.js", - development = true, jvmConfig = { withJava() }, jsConfig = { useCommonJs() } ) { @@ -47,9 +46,6 @@ application { mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt") } -//TODO ??? -tasks.getByName("jsBrowserProductionWebpack").dependsOn("jsDevelopmentExecutableCompileSync") - //distributions { // main { // contents { diff --git a/gradle.properties b/gradle.properties index c8070279..9413b93e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,6 @@ org.gradle.jvmargs=-Xmx4G org.jetbrains.compose.experimental.jscanvas.enabled=true -toolsVersion=0.15.0-kotlin-1.9.20-RC2 +toolsVersion=0.15.0-kotlin-1.9.20 #kotlin.experimental.tryK2=true #kscience.wasm.disabled=true diff --git a/ui/compose/build.gradle.kts b/ui/compose/build.gradle.kts new file mode 100644 index 00000000..a2f2c4a7 --- /dev/null +++ b/ui/compose/build.gradle.kts @@ -0,0 +1,41 @@ + +plugins { + id("space.kscience.gradle.mpp") + id("org.jetbrains.compose") version "1.5.10" +// id("com.android.library") +} + +kscience{ + jvm() + js() +// wasm() +} + +kotlin { +// android() + sourceSets { + val commonMain by getting { + dependencies { + + } + } + + val jvmMain by getting { + dependencies { + api(compose.runtime) + api(compose.foundation) + api(compose.material) + api(compose.preview) + } + } + + val jsMain by getting{ + dependencies { + api(compose.html.core) + api("app.softwork:bootstrap-compose:0.1.15") + api("app.softwork:bootstrap-compose-icons:0.1.15") + api(projects.visionforge.visionforgeThreejs) + } + } + } +} \ No newline at end of file diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt new file mode 100644 index 00000000..93cbddfc --- /dev/null +++ b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt @@ -0,0 +1,130 @@ +package space.kscience.visionforge.compose + +import androidx.compose.runtime.* +import kotlinx.html.js.onClickFunction +import org.jetbrains.compose.web.css.AlignItems +import org.jetbrains.compose.web.css.alignItems +import org.jetbrains.compose.web.dom.Span +import org.w3c.dom.events.Event +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 + + +private val MetaViewerItem: FC = fc("MetaViewerItem") { props -> + metaViewerItem(props) +} + +@Composable +private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescriptor? = null) { + var expanded: Boolean by remember { mutableStateOf(true) } + val item: Meta? = root[name] + val descriptorItem: MetaDescriptor? = rootDescriptor?.get(name) + val actualValue = item?.value ?: descriptorItem?.defaultValue + val actualMeta = item ?: descriptorItem?.defaultNode + + val token = name.lastOrNull()?.toString() ?: props.rootName ?: "" + + val expanderClick: (Event) -> Unit = { + expanded = !expanded + } + + FlexRow(attrs = { + classes("metaItem") + style { + alignItems(AlignItems.Center) + } + }) { + if (actualMeta?.isLeaf == false) { + Span(attrs = { + + }) + 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 = 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 + } + } +} diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt new file mode 100644 index 00000000..29074464 --- /dev/null +++ b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt @@ -0,0 +1,71 @@ +package space.kscience.visionforge.compose + +import kotlinx.css.* +import org.jetbrains.compose.web.css.* + +public object TreeStyles : StyleSheet() { + /** + * Remove default bullets + */ + public val tree: String by style { + paddingLeft(5.px) + marginLeft(0.px) + listStyleType("none") + } + + /** + * Style the caret/arrow + */ + public val treeCaret by style { + cursor("pointer") + userSelect = UserSelect.none + /* Create the caret/arrow with a unicode, and style it */ + before { + content = "\u25B6".quoted + color(Color.black) + display(DisplayStyle.InlineBlock) + marginRight(6.px) + } + } + + /** + * Rotate the caret/arrow icon when clicked on (using JavaScript) + */ + public val treeCaredDown by style { + before { + content = "\u25B6".quoted + color(Color.black) + display(DisplayStyle.InlineBlock) + marginRight(6.px) + transform { rotate(90.deg) } + } + } + + public val treeItem: String by style { + alignItems(AlignItems.Center) + paddingLeft(10.px) + border { + left{ + width(1.px) + color(Color.lightgray) + style = LineStyle.Dashed + } + } + } + + public val treeLabel by style { + border(style = LineStyle.None) + padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt) + textAlign("left") + flex(1) + } + + public val treeLabelInactive: RuleSet by css { + color = Color.lightGray + } + + public val treeLabelSelected: RuleSet by css { + backgroundColor = Color.lightBlue + } + +} \ No newline at end of file diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt new file mode 100644 index 00000000..882f1f1c --- /dev/null +++ b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt @@ -0,0 +1,41 @@ +package space.kscience.visionforge.compose + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.web.css.DisplayStyle +import org.jetbrains.compose.web.css.FlexDirection +import org.jetbrains.compose.web.css.display +import org.jetbrains.compose.web.css.flexDirection +import org.jetbrains.compose.web.dom.AttrBuilderContext +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.ElementScope +import org.w3c.dom.HTMLDivElement + +@Composable +public fun FlexColumn( + attrs: AttrBuilderContext? = null, + content: @Composable ElementScope.() -> Unit, +): Unit = Div( + attrs = { + style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + } + attrs?.invoke(this) + }, + content +) + +@Composable +public fun FlexRow( + attrs: AttrBuilderContext? = null, + content: @Composable ElementScope.() -> Unit, +): Unit = Div( + attrs = { + style { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + } + attrs?.invoke(this) + }, + content +) \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt index 60789027..14a22797 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt @@ -34,33 +34,33 @@ public fun Vision.colorProperty( ColorAccessor(properties.root(true), propertyName ?: property.name.asName()) } -public var ColorAccessor?.string: String? - get() = this?.value?.let { if (it == Null) null else it.string } +public var ColorAccessor.string: String? + get() = value?.let { if (it == Null) null else it.string } set(value) { - this?.value = value?.asValue() + this.value = value?.asValue() } /** * Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string */ -public operator fun ColorAccessor?.invoke(webColor: String) { - this?.value = webColor.asValue() +public operator fun ColorAccessor.invoke(webColor: String) { + value = webColor.asValue() } /** * Set color as RGB integer */ -public operator fun ColorAccessor?.invoke(rgb: Int) { - this?.value = Colors.rgbToString(rgb).asValue() +public operator fun ColorAccessor.invoke(rgb: Int) { + value = Colors.rgbToString(rgb).asValue() } /** * Set color as RGB */ -public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) { - this?.value = Colors.rgbToString(r, g, b).asValue() +public operator fun ColorAccessor.invoke(r: UByte, g: UByte, b: UByte) { + value = Colors.rgbToString(r, g, b).asValue() } -public fun ColorAccessor?.clear() { - this?.value = null +public fun ColorAccessor.clear() { + value = null } \ No newline at end of file diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt index e4b0619d..4991c12d 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidPropertyTest.kt @@ -94,7 +94,7 @@ class SolidPropertyTest { } } } - assertEquals("#555555", box?.color.string) + assertEquals("#555555", box?.color?.string) } @Test diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt index d8d971bb..512f3807 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SolidReferenceTest.kt @@ -31,7 +31,7 @@ class SolidReferenceTest { fun testReferenceSerialization(){ val serialized = Solids.jsonForSolids.encodeToJsonElement(groupWithReference) val deserialized = Solids.jsonForSolids.decodeFromJsonElement(SolidGroup.serializer(), serialized) - assertEquals(groupWithReference.items["test"]?.color.string, deserialized.items["test"]?.color.string) + assertEquals(groupWithReference.items["test"]?.color?.string, deserialized.items["test"]?.color?.string) assertEquals("blue", (deserialized.children.getChild("test") as Solid).color.string) } } \ No newline at end of file diff --git a/visionforge-tables/build.gradle.kts b/visionforge-tables/build.gradle.kts index cf813e6e..33988cf0 100644 --- a/visionforge-tables/build.gradle.kts +++ b/visionforge-tables/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("space.kscience.gradle.mpp") } -val tablesVersion = "0.2.0-dev-4" +val tablesVersion = "0.2.1" kscience { jvm() @@ -22,8 +22,8 @@ kscience { api("space.kscience:tables-kt:${tablesVersion}") } dependencies(jsMain) { - implementation(npm("tabulator-tables", "5.4.4")) - implementation(npm("@types/tabulator-tables", "5.4.8")) + implementation(npm("tabulator-tables", "5.5.2")) + implementation(npm("@types/tabulator-tables", "5.5.3")) } useSerialization() }