diff --git a/demo/gdml/build.gradle.kts b/demo/gdml/build.gradle.kts index e4fef51e..cf9d8eb3 100644 --- a/demo/gdml/build.gradle.kts +++ b/demo/gdml/build.gradle.kts @@ -16,6 +16,15 @@ kotlin { jvm { withJava() } + js{ + useCommonJs() + browser { + commonWebpackConfig { + sourceMaps = false + cssSupport.enabled = false + } + } + } sourceSets { commonMain { dependencies { @@ -31,6 +40,7 @@ kotlin { jsMain { dependencies { implementation(project(":ui:bootstrap")) + implementation(project(":ui:ring")) implementation(project(":visionforge-threejs")) implementation(npm("react-file-drop", "3.0.6")) } diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt index 60e676a0..2122e8bd 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt @@ -14,10 +14,10 @@ import space.kscience.gdml.decodeFromString import space.kscience.visionforge.Vision import space.kscience.visionforge.bootstrap.gridRow import space.kscience.visionforge.bootstrap.nameCrumbs -import space.kscience.visionforge.bootstrap.threeControls import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.flexColumn +import space.kscience.visionforge.ring.ringThreeControls import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.specifications.Canvas3DOptions @@ -31,14 +31,6 @@ external interface GDMLAppProps : RProps { var selected: Name? } -//private val canvasConfig = Canvas3DOptions { -// camera = Camera { -// distance = 2100.0 -// latitude = PI / 6 -// azimuth = PI + PI / 6 -// } -//} - @JsExport val GDMLApp = functionalComponent("GDMLApp") { props -> var selected by useState { props.selected } @@ -120,7 +112,7 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> } } canvas?.let { - threeControls(it, selected, onSelect) + ringThreeControls(it, selected, onSelect) } } } diff --git a/demo/gdml/webpack.config.d/01.ring.js b/demo/gdml/webpack.config.d/01.ring.js new file mode 100644 index 00000000..41da041c --- /dev/null +++ b/demo/gdml/webpack.config.d/01.ring.js @@ -0,0 +1,3 @@ +const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; + +config.module.rules.push(...ringConfig.module.rules) \ No newline at end of file diff --git a/demo/gdml/webpack.config.d/02.boostrap.js b/demo/gdml/webpack.config.d/02.boostrap.js new file mode 100644 index 00000000..e1d53b36 --- /dev/null +++ b/demo/gdml/webpack.config.d/02.boostrap.js @@ -0,0 +1,7 @@ +config.module.rules.push({ + test: /\.css$/, + include: [ + require.resolve('bootstrap/dist/css/bootstrap.min.css') + ], + use: ['style-loader', 'css-loader'] +}); \ No newline at end of file diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt index 34fcf78b..8052d576 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt @@ -49,7 +49,6 @@ public external interface PropertyEditorProps : RProps { */ public var descriptor: NodeDescriptor? - /** * A coroutine scope for updates */ diff --git a/ui/ring/build.gradle.kts b/ui/ring/build.gradle.kts index bb80da8f..5bd5817c 100644 --- a/ui/ring/build.gradle.kts +++ b/ui/ring/build.gradle.kts @@ -7,6 +7,11 @@ val dataforgeVersion: String by rootProject.extra kotlin{ js{ useCommonJs() + browser { + commonWebpackConfig { + cssSupport.enabled = false + } + } } } @@ -15,4 +20,8 @@ dependencies{ implementation(npm("@jetbrains/icons", "3.14.1")) implementation(npm("@jetbrains/ring-ui", "4.0.7")) + implementation(npm("core-js","3.12.1")) + compileOnly(npm("url-loader","4.1.1")) + compileOnly(npm("postcss-loader","5.2.0")) + compileOnly(npm("source-map-loader","2.0.1")) } \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/ringui/Alert.kt b/ui/ring/src/main/kotlin/ringui/Alert.kt index 2f5c4b8b..05cebfe0 100644 --- a/ui/ring/src/main/kotlin/ringui/Alert.kt +++ b/ui/ring/src/main/kotlin/ringui/Alert.kt @@ -20,11 +20,11 @@ public external interface AlertProps : WithClassName { public typealias AlertType = String public object AlertTypes { - public var ERROR = "error" - public var MESSAGE = "message" - public var SUCCESS = "success" - public var WARNING = "warning" - public var LOADING = "loading" + public var ERROR: String = "error" + public var MESSAGE: String = "message" + public var SUCCESS: String = "success" + public var WARNING: String = "warning" + public var LOADING: String = "loading" } public fun RBuilder.ringAlert(handler: RHandler) { diff --git a/ui/ring/src/main/kotlin/ringui/Link.kt b/ui/ring/src/main/kotlin/ringui/Link.kt index c38239d9..7688c5ce 100644 --- a/ui/ring/src/main/kotlin/ringui/Link.kt +++ b/ui/ring/src/main/kotlin/ringui/Link.kt @@ -9,15 +9,15 @@ import react.dom.WithClassName public external interface LinkProps : WithClassName { public var innerClassName: String public var active: Boolean - var inherit: Boolean - var pseudo: Boolean - var hover: Boolean - var href: String - var onPlainLeftClick: (MouseEvent) -> Unit - var onClick: (MouseEvent) -> Unit + public var inherit: Boolean + public var pseudo: Boolean + public var hover: Boolean + public var href: String + public var onPlainLeftClick: (MouseEvent) -> Unit + public var onClick: (MouseEvent) -> Unit } -fun RBuilder.ringLink(handler: RHandler) { +public fun RBuilder.ringLink(handler: RHandler) { RingUI.Link { handler() } diff --git a/ui/ring/src/main/kotlin/ringui/island/Island.kt b/ui/ring/src/main/kotlin/ringui/island/Island.kt index 7699f66b..300faefa 100644 --- a/ui/ring/src/main/kotlin/ringui/island/Island.kt +++ b/ui/ring/src/main/kotlin/ringui/island/Island.kt @@ -25,6 +25,16 @@ public fun RBuilder.ringIsland(handler: RHandler) { } } +public fun RBuilder.ringIsland(header: String, handler: RHandler) { + ringIsland { + ringIslandHeader { + +header + } + ringIslandContent(handler) + } +} + + public fun RBuilder.ringAdaptiveIsland(handler: RHandler) { IslandModule.AdaptiveIsland { handler() diff --git a/ui/ring/src/main/kotlin/ringui/tabs/SmartTabs.kt b/ui/ring/src/main/kotlin/ringui/tabs/SmartTabs.kt new file mode 100644 index 00000000..574224ef --- /dev/null +++ b/ui/ring/src/main/kotlin/ringui/tabs/SmartTabs.kt @@ -0,0 +1,21 @@ +package ringui.tabs + +import react.RBuilder +import react.RHandler +import react.dom.WithClassName + +public external interface SmartTabsProps: WithClassName { + public var initSelected: String +} + + +public fun RBuilder.ringSmartTabs(active: String? = null, handler: RHandler){ + TabsModule.SmartTabs{ + active?.let{ + attrs { + initSelected = active + } + } + handler() + } +} \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/ringui/tabs/Tabs.kt b/ui/ring/src/main/kotlin/ringui/tabs/Tabs.kt new file mode 100644 index 00000000..ec30223b --- /dev/null +++ b/ui/ring/src/main/kotlin/ringui/tabs/Tabs.kt @@ -0,0 +1,50 @@ +package ringui.tabs + +import react.RBuilder +import react.RClass +import react.RHandler +import react.dom.WithClassName + +@JsModule("@jetbrains/ring-ui/components/tabs/tabs") +internal external object TabsModule { + val Tabs: RClass + val Tab: RClass + val SmartTabs: RClass + //val CustomItem: RClass +} + +//https://github.com/JetBrains/ring-ui/blob/master/components/tabs/tabs.js +public external interface TabsProps : WithClassName { + public var theme: String + public var selected: String + public var onSelect: (String) -> Unit + public var href: String + public var autoCollapse: Boolean +} + +public external interface CustomItemProps : WithClassName + +public external interface TabProps : WithClassName { + public var title: dynamic // PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, + public var id: String +} + +public fun RBuilder.ringTabs(active: String? = null, handler: RHandler) { + TabsModule.Tabs { + active?.let{ + attrs { + selected = active + } + } + handler() + } +} + +public fun RBuilder.ringTab(title: dynamic, handler: RHandler) { + TabsModule.Tab { + attrs { + this.title = title + } + handler() + } +} \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringPropertyEditor.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringPropertyEditor.kt new file mode 100644 index 00000000..40101c5e --- /dev/null +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringPropertyEditor.kt @@ -0,0 +1,75 @@ +package space.kscience.visionforge.ring + +import org.w3c.dom.Element +import react.RBuilder +import react.dom.render +import ringui.island.ringIsland +import ringui.island.ringIslandContent +import ringui.island.ringIslandHeader +import ringui.tabs.ringSmartTabs +import ringui.tabs.ringTab +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.visionforge.* +import space.kscience.visionforge.react.metaViewer +import space.kscience.visionforge.react.propertyEditor +import space.kscience.visionforge.solid.SolidReference + +public fun RBuilder.ringPropertyEditor( + vision: Vision, + descriptor: NodeDescriptor? = vision.descriptor, + key: Any? = null, +) { + + ringIsland { + ringIslandHeader { + attrs { + border = true + } + +"Properties" + } + ringIslandContent { + propertyEditor( + ownProperties = vision.ownProperties, + allProperties = vision.allProperties(), + updateFlow = vision.propertyChanges, + descriptor = descriptor, + key = key + ) + } + } + val styles = if (vision is SolidReference) { + (vision.styles + vision.prototype.styles).distinct() + } else { + vision.styles + } + if (styles.isNotEmpty()) { + ringIsland { + ringIslandHeader { + attrs { + border = true + } + +"Styles" + } + ringIslandContent { + ringSmartTabs { + + } + styles.forEach { styleName -> + val style = vision.getStyle(styleName) + if (style != null) { + ringTab(styleName) { + metaViewer(style) + } + } + } + } + } + } +} + +public fun Element.ringPropertyEditor( + item: Vision, + descriptor: NodeDescriptor? = item.descriptor, +): Unit = render(this) { + ringPropertyEditor(item, descriptor = descriptor) +} \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringThreeControls.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringThreeControls.kt new file mode 100644 index 00000000..63722265 --- /dev/null +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ringThreeControls.kt @@ -0,0 +1,150 @@ +package space.kscience.visionforge.ring + +import kotlinx.css.* +import kotlinx.css.properties.border +import kotlinx.html.js.onClickFunction +import org.w3c.dom.events.Event +import org.w3c.files.Blob +import org.w3c.files.BlobPropertyBag +import react.* +import react.dom.button +import react.dom.h2 +import ringui.island.ringIsland +import ringui.tabs.ringSmartTabs +import ringui.tabs.ringTab +import space.kscience.dataforge.meta.descriptors.defaultMeta +import space.kscience.dataforge.meta.withDefault +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.isEmpty +import space.kscience.visionforge.Vision +import space.kscience.visionforge.VisionGroup +import space.kscience.visionforge.react.flexColumn +import space.kscience.visionforge.react.flexRow +import space.kscience.visionforge.react.objectTree +import space.kscience.visionforge.react.propertyEditor +import space.kscience.visionforge.solid.SolidGroup +import space.kscience.visionforge.solid.specifications.Canvas3DOptions +import space.kscience.visionforge.solid.three.ThreeCanvas +import styled.css +import styled.styledDiv + +internal fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) { + event.stopPropagation(); + event.preventDefault(); + + val fileSaver = kotlinext.js.require("file-saver") + val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8")) + fileSaver.saveAs(blob, fileName) +} + +internal fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement { + return child(CanvasControls) { + attrs { + this.canvas = canvas + } + } +} + +internal external interface CanvasControlsProps : RProps { + public var canvas: ThreeCanvas +} + +internal val CanvasControls: FunctionalComponent = functionalComponent("CanvasControls") { props -> + val visionManager = useMemo( + { props.canvas.three.solids.visionManager }, + arrayOf(props.canvas) + ) + flexColumn { + flexRow { + css { + border(1.px, BorderStyle.solid, Color.blue) + padding(4.px) + } + button { + +"Export" + attrs { + onClickFunction = { + val json = (props.canvas.content as? SolidGroup)?.let { group -> + visionManager.encodeToString(group) + } + if (json != null) { + saveData(it, "object.json", "text/json") { + json + } + } + } + } + } + } + propertyEditor( + ownProperties = props.canvas.options, + allProperties = props.canvas.options.withDefault(Canvas3DOptions.descriptor.defaultMeta()), + descriptor = Canvas3DOptions.descriptor, + expanded = false + ) + + } +} + + +public external interface ThreeControlsProps : RProps { + public var canvas: ThreeCanvas + public var selected: Name? + public var onSelect: (Name) -> Unit +} + +@JsExport +public val ThreeControls: FunctionalComponent = functionalComponent { props -> + val vision = props.canvas.content + ringSmartTabs(if (props.selected != null) "Properties" else null) { + ringTab("Canvas") { + ringIsland("Canvas configuration") { + canvasControls(props.canvas) + } + } + ringTab("Tree") { + styledDiv { + css { + border(1.px, BorderStyle.solid, Color.lightGray) + padding(10.px) + } + h2 { +"Object tree" } + styledDiv { + css { + flex(1.0, 1.0, FlexBasis.inherit) + } + props.canvas.content?.let { + objectTree(it, props.selected, props.onSelect) + } + } + } + } + ringTab("Properties") { + props.selected.let { selected -> + val selectedObject: Vision? = when { + selected == null -> null + selected.isEmpty() -> vision + else -> (vision as? VisionGroup)?.get(selected) + } + if (selectedObject != null) { + ringPropertyEditor(selectedObject, key = selected) + } + } + } + props.children() + } +} + +public fun RBuilder.ringThreeControls( + canvas: ThreeCanvas, + selected: Name?, + onSelect: (Name) -> Unit = {}, + builder: RBuilder.() -> Unit = {}, +): ReactElement = child(ThreeControls) { + attrs { + this.canvas = canvas + this.selected = selected + this.onSelect = onSelect + } + builder() +} \ No newline at end of file diff --git a/ui/ring/webpack.config.d/01.ring.js b/ui/ring/webpack.config.d/01.ring.js index 868fde03..41da041c 100644 --- a/ui/ring/webpack.config.d/01.ring.js +++ b/ui/ring/webpack.config.d/01.ring.js @@ -1,6 +1,3 @@ -// wrap is useful, because declaring variables in module can be already declared -// module creates own lexical environment -(function (config) { - const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; - config.module.rules.push(...ringConfig.module.rules); -})(config); \ No newline at end of file +const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; + +config.module.rules.push(...ringConfig.module.rules) \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt index 5ab8fa70..5045a375 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt @@ -1,8 +1,7 @@ package space.kscience.visionforge.solid import javafx.application.Platform -import javafx.beans.binding.Binding -import javafx.beans.binding.ObjectBinding +import javafx.beans.binding.* import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.startsWith @@ -46,18 +45,18 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi } public fun ObjectBinding.value(): Binding = objectBinding { it.value } -fun ObjectBinding.string() = stringBinding { it.string } -fun ObjectBinding.number() = objectBinding { it.number } -fun ObjectBinding.double() = objectBinding { it.double } -fun ObjectBinding.float() = objectBinding { it.float } -fun ObjectBinding.int() = objectBinding { it.int } -fun ObjectBinding.long() = objectBinding { it.long } -fun ObjectBinding.node() = objectBinding { it.node } +public fun ObjectBinding.string(): StringBinding = stringBinding { it.string } +public fun ObjectBinding.number(): Binding = objectBinding { it.number } +public fun ObjectBinding.double(): Binding = objectBinding { it.double } +public fun ObjectBinding.float(): Binding = objectBinding { it.float } +public fun ObjectBinding.int(): Binding = objectBinding { it.int } +public fun ObjectBinding.long(): Binding = objectBinding { it.long } +public fun ObjectBinding.node(): Binding = objectBinding { it.node } -fun ObjectBinding.string(default: String) = stringBinding { it.string ?: default } -fun ObjectBinding.double(default: Double) = doubleBinding { it.double ?: default } -fun ObjectBinding.float(default: Float) = floatBinding { it.float ?: default } -fun ObjectBinding.int(default: Int) = integerBinding { it.int ?: default } -fun ObjectBinding.long(default: Long) = longBinding { it.long ?: default } +public fun ObjectBinding.string(default: String): StringBinding = stringBinding { it.string ?: default } +public fun ObjectBinding.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default } +public fun ObjectBinding.float(default: Float): FloatBinding = floatBinding { it.float ?: default } +public fun ObjectBinding.int(default: Int): IntegerBinding = integerBinding { it.int ?: default } +public fun ObjectBinding.long(default: Long): LongBinding = longBinding { it.long ?: default } -fun ObjectBinding.transform(transform: (MetaItem) -> T) = objectBinding { it?.let(transform) } +public fun ObjectBinding.transform(transform: (MetaItem) -> T): Binding = objectBinding { it?.let(transform) }