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 4f6560b6..0c52257d 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 @@ -1,6 +1,9 @@ package space.kscience.visionforge.gdml.demo import kotlinx.browser.window +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import org.w3c.files.File import org.w3c.files.FileReader import org.w3c.files.get import react.* @@ -27,32 +30,43 @@ external interface GDMLAppProps : RProps { @JsExport val GDMLApp = functionalComponent("GDMLApp") { props -> val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager } - var vision: Solid? by useState { props.vision?.apply { root(visionManager) } } + var deferredVision: Deferred by useState { + CompletableDeferred(props.vision) + } - fun loadData(name: String, data: String) { - val parsedVision = when { - name.endsWith(".gdml") || name.endsWith(".xml") -> { - val gdml = Gdml.decodeFromString(data) - gdml.toVision().apply { - root(visionManager) - console.info("Marking layers for file $name") - markLayers() + fun readFileAsync(file: File): Deferred { + val deferred = CompletableDeferred() + FileReader().apply { + onload = { + val data = result as String + val name = file.name + val parsedVision = when { + name.endsWith(".gdml") || name.endsWith(".xml") -> { + val gdml = Gdml.decodeFromString(data) + gdml.toVision().apply { + root(visionManager) + console.info("Marking layers for file $name") + markLayers() + } + } + name.endsWith(".json") -> visionManager.decodeFromString(data) + else -> { + window.alert("File extension is not recognized: $name") + error("File extension is not recognized: $name") + } } + deferred.complete(parsedVision as? Solid ?: error("Parsed vision is not a solid")) } - name.endsWith(".json") -> visionManager.decodeFromString(data) - else -> { - window.alert("File extension is not recognized: $name") - error("File extension is not recognized: $name") - } + readAsText(file) } - vision = parsedVision as? Solid ?: error("Parsed vision is not a solid") + return deferred } child(ThreeCanvasWithControls) { attrs { this.context = props.context - this.solid = vision + this.builderOfSolid = deferredVision this.selected = props.selected tab("Load") { h2 { @@ -61,13 +75,7 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> fileDrop("(drag file here)") { files -> val file = files?.get(0) if (file != null) { - FileReader().apply { - onload = { - val string = result as String - loadData(file.name, string) - } - readAsText(file) - } + deferredVision = readFileAsync(file) } } } diff --git a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt index a54e48c9..55407e4f 100644 --- a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt @@ -1,5 +1,4 @@ import kotlinx.browser.document -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -17,6 +16,7 @@ import space.kscience.visionforge.VisionClient import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeWithControlsPlugin +import space.kscience.visionforge.ring.solid import space.kscience.visionforge.solid.* import space.kscience.visionforge.startApplication import styled.css @@ -38,51 +38,6 @@ private class JsPlaygroundApp : Application { val bouncingSphereTrace = Trace() - val bouncingSphere = SolidGroup { - sphere(5.0, "ball") { - detail = 16 - color("red") - val h = 100.0 - y = h - GlobalScope.launch { - val g = 10.0 - val dt = 0.1 - var time = 0.0 - var velocity = 0.0 - while (isActive) { - delay(20) - time += dt - velocity -= g * dt - y = y.toDouble() + velocity * dt - bouncingSphereTrace.appendXY(time, y) - if (y.toDouble() <= 2.5) { - //conservation of energy - velocity = sqrt(2 * g * h) - } - } - } - } - - box(200, 5, 200, name = "floor") { - y = -2.5 - } - } - val random = Random(112233) - - val visionOfSpheres = SolidGroup { - repeat(100) { - sphere(5, name = "sphere[$it]") { - x = random.nextDouble(-300.0, 300.0) - y = random.nextDouble(-300.0, 300.0) - z = random.nextDouble(-300.0, 300.0) - material { - color(random.nextInt()) - } - detail = 16 - } - } - } - render(element) { styledDiv { css { @@ -94,18 +49,46 @@ private class JsPlaygroundApp : Application { SmartTabs("gravity") { Tab("gravity") { styledDiv { - css{ + css { height = 50.vh } child(ThreeCanvasWithControls) { attrs { context = playgroundContext - solid = bouncingSphere + solid { + sphere(5.0, "ball") { + detail = 16 + color("red") + val h = 100.0 + y = h + launch { + val g = 10.0 + val dt = 0.1 + var time = 0.0 + var velocity = 0.0 + while (isActive) { + delay(20) + time += dt + velocity -= g * dt + y = y.toDouble() + velocity * dt + bouncingSphereTrace.appendXY(time, y) + if (y.toDouble() <= 2.5) { + //conservation of energy + velocity = sqrt(2 * g * h) + } + } + } + } + + box(200, 5, 200, name = "floor") { + y = -2.5 + } + } } } } styledDiv { - css{ + css { height = 40.vh } @@ -129,10 +112,28 @@ private class JsPlaygroundApp : Application { // } // } Tab("spheres") { - child(ThreeCanvasWithControls) { - attrs { - context = playgroundContext - solid = visionOfSpheres + styledDiv { + css { + height = 90.vh + } + child(ThreeCanvasWithControls) { + val random = Random(112233) + attrs { + context = playgroundContext + solid { + repeat(100) { + sphere(5, name = "sphere[$it]") { + x = random.nextDouble(-300.0, 300.0) + y = random.nextDouble(-300.0, 300.0) + z = random.nextDouble(-300.0, 300.0) + material { + color(random.nextInt()) + } + detail = 16 + } + } + } + } } } } diff --git a/ui/ring/src/main/kotlin/ringui/Loader.kt b/ui/ring/src/main/kotlin/ringui/Loader.kt new file mode 100644 index 00000000..c58d51c1 --- /dev/null +++ b/ui/ring/src/main/kotlin/ringui/Loader.kt @@ -0,0 +1,19 @@ +@file:JsModule("@jetbrains/ring-ui/components/loader/loader") +@file:JsNonModule + +package ringui + +import react.ComponentClass +import react.dom.WithClassName + +// https://github.com/JetBrains/ring-ui/blob/master/components/loader/loader.js +public external interface LoaderProps : WithClassName { + public var size: Number + public var colors: Array + public var message: String + public var stop: Boolean + public var deterministic: Boolean +} + +@JsName("default") +public external val Loader: ComponentClass \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt b/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt new file mode 100644 index 00000000..8d0bf578 --- /dev/null +++ b/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt @@ -0,0 +1,16 @@ +@file:JsModule("@jetbrains/ring-ui/components/loader-screen/loader-screen") +@file:JsNonModule + +package ringui + +import react.ComponentClass +import react.dom.WithClassName + +// https://github.com/JetBrains/ring-ui/blob/master/components/loader-screen/loader-screen.js +public external interface LoaderScreenProps : WithClassName { + public var containerClassName: String + public var message: String +} + +@JsName("default") +public external val LoaderScreen: ComponentClass \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt index a14d23ca..06cffdd0 100644 --- a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt @@ -1,18 +1,19 @@ 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.div import react.dom.span -import ringui.Island -import ringui.IslandContent -import ringui.IslandHeader -import ringui.Link +import ringui.* import space.kscience.dataforge.context.Context 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.Vision import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.computeProperties import space.kscience.visionforge.react.ThreeCanvasComponent @@ -20,17 +21,24 @@ import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.solid.Solid +import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.specifications.Canvas3DOptions import styled.css import styled.styledDiv public external interface ThreeCanvasWithControlsProps : RProps { public var context: Context - public var solid: Solid? + public var builderOfSolid: Deferred public var selected: Name? public var additionalTabs: Map Unit>? } +public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) { + builderOfSolid = context.async { + SolidGroup(block) + } +} + public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) { additionalTabs = (additionalTabs ?: emptyMap()) + (title to block) } @@ -72,6 +80,13 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement public val ThreeCanvasWithControls: FunctionComponent = functionalComponent("ThreeViewWithControls") { props -> var selected by useState { props.selected } + var solid: Solid? by useState(null) + + useEffect { + props.context.launch { + solid = props.builderOfSolid.await() + } + } val onSelect: (Name?) -> Unit = { selected = it @@ -83,15 +98,16 @@ public val ThreeCanvasWithControls: FunctionComponent props.solid - else -> (props.solid as? VisionGroup)?.get(it) + it.isEmpty() -> solid + else -> (solid as? VisionGroup)?.get(it) } } } + flexRow { css { height = 100.pct @@ -108,12 +124,20 @@ public val ThreeCanvasWithControls: FunctionComponent