diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2c3478..22ecb805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Context receivers flag - MeshLine for thick lines - Custom client-side events and thier processing in VisionServer +- Control/input visions ### Changed - Color accessor property is now `colorProperty`. Color uses non-nullable `invoke` instead of `set`. diff --git a/build.gradle.kts b/build.gradle.kts index 57e42d2b..3d1779e7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile import space.kscience.gradle.useApache2Licence import space.kscience.gradle.useSPCTeam @@ -11,7 +10,7 @@ val dataforgeVersion by extra("0.7.1") allprojects { group = "space.kscience" - version = "0.3.0-RC" + version = "0.4.0-dev-1" } subprojects { @@ -31,11 +30,11 @@ subprojects { } } - tasks.withType{ - kotlinOptions{ - useEsClasses = true - } - } +// tasks.withType{ +// kotlinOptions{ +// useEsClasses = true +// } +// } } ksciencePublish { diff --git a/demo/gdml/build.gradle.kts b/demo/gdml/build.gradle.kts index 4cccf686..f715262b 100644 --- a/demo/gdml/build.gradle.kts +++ b/demo/gdml/build.gradle.kts @@ -7,14 +7,8 @@ group = "demo" kscience { jvm() js { - useCommonJs() browser { binaries.executable() - commonWebpackConfig { - cssSupport { - enabled.set(false) - } - } } } dependencies { @@ -26,7 +20,6 @@ kscience { implementation(spclibs.logback.classic) } jsMain { - implementation(projects.ui.ring) implementation(projects.visionforgeThreejs) 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 37c178f4..7a7aa3cc 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,46 +1,33 @@ package space.kscience.visionforge.gdml.demo +import androidx.compose.runtime.* import kotlinx.browser.window -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred -import kotlinx.css.* +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.H2 +import org.jetbrains.compose.web.dom.Text import org.w3c.files.File import org.w3c.files.FileReader import org.w3c.files.get -import react.Props -import react.dom.h2 -import react.fc -import react.useState import space.kscience.dataforge.names.Name import space.kscience.gdml.Gdml import space.kscience.gdml.decodeFromString import space.kscience.visionforge.Colors import space.kscience.visionforge.gdml.markLayers import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.ring.ThreeCanvasWithControls -import space.kscience.visionforge.ring.tab import space.kscience.visionforge.setAsRoot import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.invoke -import styled.css -import styled.styledDiv +import space.kscience.visionforge.solid.three.compose.ThreeView -external interface GDMLAppProps : Props { - var solids: Solids - var vision: Solid? - var selected: Name? -} -@JsExport -val GDMLApp = fc("GDMLApp") { props -> - var deferredVision: Deferred by useState { - CompletableDeferred(props.vision) - } +@Composable +fun GDMLApp(solids: Solids, initialVision: Solid?, selected: Name? = null) { + var vision: Solid? by remember { mutableStateOf(initialVision) } - fun readFileAsync(file: File): Deferred { - val deferred = CompletableDeferred() + fun readFileAsync(file: File) { FileReader().apply { onload = { val data = result as String @@ -49,7 +36,7 @@ val GDMLApp = fc("GDMLApp") { props -> name.endsWith(".gdml") || name.endsWith(".xml") -> { val gdml = Gdml.decodeFromString(data) gdml.toVision().apply { - setAsRoot(props.solids.visionManager) + setAsRoot(solids.visionManager) console.info("Marking layers for file $name") markLayers() ambientLight { @@ -57,43 +44,38 @@ val GDMLApp = fc("GDMLApp") { props -> } } } - name.endsWith(".json") -> props.solids.visionManager.decodeFromString(data) + + name.endsWith(".json") -> solids.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")) + vision = parsedVision as? Solid ?: error("Parsed vision is not a solid") + Unit } readAsText(file) } - - return deferred } - styledDiv { - css { - height = 100.vh - 12.pt - width = 100.vw + Div({ + style { + height(100.vh - 12.pt) + width(100.vw) } - child(ThreeCanvasWithControls) { - attrs { - this.solids = props.solids - this.builderOfSolid = deferredVision - this.selected = props.selected - tab("Load") { - h2 { - +"Drag and drop .gdml or .json VisionForge files here" - } - fileDrop("(drag file here)") { files -> - val file = files?.get(0) - if (file != null) { - deferredVision = readFileAsync(file) - } + }) { + ThreeView(solids, vision, selected) { + Tab("Load") { + H2 { + Text("Drag and drop .gdml or .json VisionForge files here") + } + fileDrop("(drag file here)") { files -> + val file = files?.get(0) + if (file != null) { + readFileAsync(file) } } } - } } } diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt index 7c6514bf..350c9de9 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlJsDemoApp.kt @@ -1,63 +1,58 @@ package space.kscience.visionforge.gdml.demo -import kotlinx.css.* +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Style +import org.jetbrains.compose.web.renderComposable import org.w3c.dom.Document import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.request 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.react.createRoot -import space.kscience.visionforge.react.render import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.startApplication -import styled.injectGlobal private class GDMLDemoApp : Application { override fun start(document: Document, state: Map) { - val context = Context("gdml-demo"){ + val context = Context("gdml-demo") { plugin(ThreePlugin) } - injectGlobal { - html{ - height = 100.pct - } - - body{ - height = 100.pct - display = Display.flex - alignItems = Align.stretch - } - - "#application"{ - width = 100.pct - display = Display.flex - alignItems = Align.stretch - } - } - val element = document.getElementById("application") ?: error("Element with id 'application' not found on page") - createRoot(element).render { - child(GDMLApp) { - val vision = GdmlShowCase.cubes().toVision().apply { - ambientLight { - color(Colors.white) - } + val vision = GdmlShowCase.cubes().toVision().apply { + ambientLight { + color(Colors.white) + } + } + + renderComposable(element) { + Style(TreeStyles) + Style { + "html" { + height(100.percent) } - //println(context.plugins.fetch(VisionManager).encodeToString(vision)) - attrs { - this.solids = context.request(Solids) - this.vision = vision + + "body" { + height(100.percent) + display(DisplayStyle.Flex) + alignItems(AlignItems.Stretch) + } + + "#application" { + width(100.percent) + display(DisplayStyle.Flex) + alignItems(AlignItems.Stretch) } } + GDMLApp(context.request(Solids), vision) } } } diff --git a/demo/js-playground/build.gradle.kts b/demo/js-playground/build.gradle.kts index eca736e4..e1b39c62 100644 --- a/demo/js-playground/build.gradle.kts +++ b/demo/js-playground/build.gradle.kts @@ -9,14 +9,8 @@ kscience { kotlin { explicitApi = null js { - useCommonJs() browser { binaries.executable() - commonWebpackConfig { - cssSupport { - enabled.set(false) - } - } } } } @@ -29,7 +23,4 @@ kscience { implementation(projects.visionforge.visionforgeMarkdown) implementation(projects.visionforge.visionforgeThreejs) } - jsMain { - implementation(projects.ui.ring) - } } \ No newline at end of file diff --git a/demo/js-playground/src/jsMain/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/jsMain/kotlin/JsPlaygroundApp.kt index dd0d0b26..a612ab3e 100644 --- a/demo/js-playground/src/jsMain/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/jsMain/kotlin/JsPlaygroundApp.kt @@ -1,7 +1,7 @@ -import kotlinx.css.* +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.renderComposable import org.w3c.dom.Document -import ringui.SmartTabs -import ringui.Tab import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.request import space.kscience.plotly.models.Trace @@ -9,16 +9,12 @@ import space.kscience.plotly.scatter import space.kscience.visionforge.Application import space.kscience.visionforge.Colors import space.kscience.visionforge.JsVisionClient +import space.kscience.visionforge.compose.Tabs import space.kscience.visionforge.plotly.PlotlyPlugin -import space.kscience.visionforge.react.createRoot -import space.kscience.visionforge.react.render -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.solid.three.ThreePlugin +import space.kscience.visionforge.solid.three.compose.ThreeView import space.kscience.visionforge.startApplication -import styled.css -import styled.styledDiv import kotlin.random.Random fun Trace.appendXYLatest(x: Number, y: Number, history: Int = 400, xErr: Number? = null, yErr: Number? = null) { @@ -33,28 +29,28 @@ private class JsPlaygroundApp : Application { override fun start(document: Document, state: Map) { val playgroundContext = Context { - plugin(ThreeWithControlsPlugin) - plugin(JsVisionClient) + plugin(ThreePlugin) plugin(PlotlyPlugin) } + val solids = playgroundContext.request(Solids) + val client = playgroundContext.request(JsVisionClient) + val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page") - createRoot(element).render { - styledDiv { - css { - padding = Padding(0.pt) - margin = Margin(0.pt) - height = 100.vh - width = 100.vw + renderComposable(element) { + Div({ + style { + padding(0.pt) + margin(0.pt) + height(100.vh) + width(100.vw) } - SmartTabs("gravity") { + }) { + Tabs { + active = "gravity" Tab("gravity") { - GravityDemo { - attrs { - this.solids = playgroundContext.request(Solids) - } - } + GravityDemo(solids, client) } // Tab("D0") { @@ -66,43 +62,34 @@ private class JsPlaygroundApp : Application { // } // } Tab("spheres") { - styledDiv { - css { - height = 100.vh - 50.pt + Div({ + style { + height(100.vh - 50.pt) } - child(ThreeCanvasWithControls) { - val random = Random(112233) - attrs { - solids = playgroundContext.request(Solids) - solid { - ambientLight { - color(Colors.white) - } - 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 - } + }) { + ThreeView(solids, SolidGroup { + ambientLight { + color(Colors.white) + } + 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 } } - } + }) } } Tab("plotly") { - Plotly { - attrs { - plot = space.kscience.plotly.Plotly.plot { - scatter { - x(1, 2, 3) - y(5, 8, 7) - } - } + Plot(client) { + scatter { + x(1, 2, 3) + y(5, 8, 7) } } } @@ -110,6 +97,7 @@ private class JsPlaygroundApp : Application { } } } + } public fun main() { diff --git a/demo/js-playground/src/jsMain/kotlin/gravityDemo.kt b/demo/js-playground/src/jsMain/kotlin/gravityDemo.kt index a4bc9057..8e041513 100644 --- a/demo/js-playground/src/jsMain/kotlin/gravityDemo.kt +++ b/demo/js-playground/src/jsMain/kotlin/gravityDemo.kt @@ -1,111 +1,159 @@ +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import kotlinx.coroutines.delay import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.css.* -import react.Props -import react.fc +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.AttrBuilderContext +import org.jetbrains.compose.web.dom.Div +import org.w3c.dom.HTMLDivElement +import space.kscience.dataforge.meta.Meta +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.VisionClient +import space.kscience.visionforge.compose.FlexRow +import space.kscience.visionforge.compose.Vision +import space.kscience.visionforge.compose.zIndex import space.kscience.visionforge.markup.VisionOfMarkup -import space.kscience.visionforge.react.flexRow -import space.kscience.visionforge.ring.ThreeCanvasWithControls -import space.kscience.visionforge.ring.solid +import space.kscience.visionforge.plotly.asVision import space.kscience.visionforge.solid.* -import styled.css -import styled.styledDiv +import space.kscience.visionforge.solid.three.compose.ThreeView import kotlin.math.sqrt -external interface DemoProps : Props { - var solids: Solids -} +@Composable +fun Plot( + client: VisionClient, + meta: Meta = Meta.EMPTY, + attrs: AttrBuilderContext? = null, + block: Plot.() -> Unit, +) = Vision( + client = client, + attrs = attrs, + meta = meta, + vision = Plot().apply(block).asVision() +) -val GravityDemo = fc { props -> - val velocityTrace = Trace { - name = "velocity" - } - val energyTrace = Trace { - name = "energy" - } - val markup = VisionOfMarkup() +@Composable +fun Markup( + client: VisionClient, + markup: VisionOfMarkup, + meta: Meta = Meta.EMPTY, + attrs: AttrBuilderContext? = null, +) = Vision( + client = client, + attrs = attrs, + meta = meta, + vision = markup +) - styledDiv { - css { - height = 100.vh - 50.pt + +private val h = 100.0 + +@Composable +fun GravityDemo(solids: Solids, client: VisionClient) { + val velocityTrace = remember { + Trace { + name = "velocity" } - styledDiv { - css { - height = 50.vh + } + + val energyTrace = remember { + Trace { + name = "energy" + } + } + + val markup = remember { VisionOfMarkup() } + + val solid = remember { + SolidGroup { + pointLight(200, 200, 200, name = "light") { + color(Colors.white) } - child(ThreeCanvasWithControls) { - attrs { - solids = props.solids - solid { - pointLight(200, 200, 200, name = "light"){ - color(Colors.white) - } - ambientLight() + ambientLight() - sphere(5.0, "ball") { - detail = 16 - color("red") - val h = 100.0 - y = h - solids.context.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 - val energy = g * y.toDouble() + velocity * velocity / 2 - y = y.toDouble() + velocity * dt + sphere(5.0, "ball") { + detail = 16 + color("red") + y = h - velocityTrace.appendXYLatest(time, y) - energyTrace.appendXYLatest(time, energy) - if (y.toDouble() <= 2.5) { - //conservation of energy - velocity = sqrt(2 * g * h) - } - - markup.content = """ - ## Bouncing sphere parameters - - **velocity** = $velocity - - **energy** = $energy - """.trimIndent() - } - } - } - - box(200, 5, 200, name = "floor") { - y = -2.5 - } - } + box(200, 5, 200, name = "floor") { + y = -2.5 } } } - flexRow { - css { - alignContent = Align.stretch - alignItems = Align.stretch - height = 50.vh - 50.pt + } + + LaunchedEffect(solid) { + val ball = solid["ball"]!! + 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 + val energy = g * ball.y.toDouble() + velocity * velocity / 2 + ball.y = ball.y.toDouble() + velocity * dt + + velocityTrace.appendXYLatest(time, ball.y) + energyTrace.appendXYLatest(time, energy) + + if (ball.y.toDouble() <= 2.5) { + //conservation of energy + velocity = sqrt(2 * g * h) } - plotly { + + markup.content = """ + ## Bouncing sphere parameters + + **velocity** = $velocity + + **energy** = $energy + """.trimIndent() + } + } + + Div({ + style { + height(100.vh - 50.pt) + } + }) { + Div({ + style { + height(50.vh) + } + }) { + ThreeView(solids, solid) + } + FlexRow({ + style { + alignContent(AlignContent.Stretch) + alignItems(AlignItems.Stretch) + height(50.vh - 50.pt) + } + }) { + Plot(client) { traces(velocityTrace, energyTrace) layout { xaxis.title = "time" } } - Markup { - attrs { - this.markup = markup + Markup(client, markup, attrs = { + style { + width(100.percent) + height(100.percent) + border(2.pt, LineStyle.Solid, Color.blue) + paddingLeft(8.pt) + backgroundColor(Color.white) + flex(1) + zIndex(10000) } - } + }) } } } \ No newline at end of file diff --git a/demo/js-playground/src/jsMain/kotlin/markupComponent.kt b/demo/js-playground/src/jsMain/kotlin/markupComponent.kt deleted file mode 100644 index a47c28cf..00000000 --- a/demo/js-playground/src/jsMain/kotlin/markupComponent.kt +++ /dev/null @@ -1,55 +0,0 @@ -import kotlinx.css.* -import kotlinx.dom.clear -import kotlinx.html.dom.append -import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor -import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor -import org.w3c.dom.Element -import org.w3c.dom.HTMLElement -import react.Props -import react.fc -import react.useEffect -import react.useRef -import space.kscience.visionforge.markup.VisionOfMarkup -import space.kscience.visionforge.markup.markdown -import space.kscience.visionforge.useProperty -import styled.css -import styled.styledDiv - -external interface MarkupProps : Props { - var markup: VisionOfMarkup? -} - -val Markup = fc("Markup") { props -> - val elementRef = useRef(null) - - useEffect(props.markup, elementRef) { - val element = elementRef.current as? HTMLElement ?: error("Markup element not found") - props.markup?.let { vision -> - val flavour = when (vision.format) { - VisionOfMarkup.COMMONMARK_FORMAT -> CommonMarkFlavourDescriptor() - VisionOfMarkup.GFM_FORMAT -> GFMFlavourDescriptor() - //TODO add new formats via plugins - else -> error("Format ${vision.format} not recognized") - } - vision.useProperty(VisionOfMarkup::content) { content: String? -> - element.clear() - element.append { - markdown(flavour) { content ?: "" } - } - } - } - } - - styledDiv { - css { - width = 100.pct - height = 100.pct - border= Border(2.pt, BorderStyle.solid, Color.blue) - padding = Padding(left = 8.pt) - backgroundColor = Color.white - flex = Flex(1.0) - zIndex = 10000 - } - ref = elementRef - } -} \ No newline at end of file diff --git a/demo/js-playground/src/jsMain/kotlin/plotlyComponent.kt b/demo/js-playground/src/jsMain/kotlin/plotlyComponent.kt deleted file mode 100644 index 80417dde..00000000 --- a/demo/js-playground/src/jsMain/kotlin/plotlyComponent.kt +++ /dev/null @@ -1,43 +0,0 @@ -import kotlinx.css.* -import org.w3c.dom.Element -import org.w3c.dom.HTMLElement -import react.* -import space.kscience.plotly.Plot -import space.kscience.plotly.PlotlyConfig -import space.kscience.plotly.plot -import styled.css -import styled.styledDiv - -external interface PlotlyProps : Props { - var plot: Plot? -} - - -val Plotly = fc("Plotly") { props -> - val elementRef = useRef(null) - - useEffect(props.plot, elementRef) { - val element = elementRef.current as? HTMLElement ?: error("Plotly element not found") - props.plot?.let { - element.plot(PlotlyConfig { - responsive = true - }, it) - } - } - - styledDiv { - css { - width = 100.pct - height = 100.pct - border = Border(2.pt, BorderStyle.solid, Color.blue) - flex = Flex(1.0) - } - ref = elementRef - } -} - -fun RBuilder.plotly(plotbuilder: Plot.() -> Unit) = Plotly { - attrs { - this.plot = Plot().apply(plotbuilder) - } -} \ No newline at end of file diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index d01b9dc9..f2b0e4dd 100644 --- a/demo/muon-monitor/build.gradle.kts +++ b/demo/muon-monitor/build.gradle.kts @@ -15,13 +15,7 @@ kscience { "muon-monitor.js", jvmConfig = { withJava() }, jsConfig = { useCommonJs() } - ) { - commonWebpackConfig { - cssSupport { - enabled.set(false) - } - } - } + ) commonMain { implementation(projects.visionforgeSolid) @@ -34,7 +28,6 @@ kscience { implementation("ch.qos.logback:logback-classic:1.2.11") } jsMain { - implementation(projects.ui.ring) implementation(projects.visionforgeThreejs) //implementation(devNpm("webpack-bundle-analyzer", "4.4.0")) } diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt index 07dc7c7c..2ecf9472 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt @@ -1,281 +1,106 @@ package ru.mipt.npm.muon.monitor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import app.softwork.bootstrapcompose.Button import kotlinx.browser.window -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.await import kotlinx.coroutines.launch -import kotlinx.css.* -import kotlinx.html.js.onClickFunction import kotlinx.serialization.json.Json +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.P +import org.jetbrains.compose.web.dom.Span +import org.jetbrains.compose.web.dom.Text import org.w3c.fetch.RequestInit -import react.Props -import react.dom.attrs -import react.dom.button -import react.dom.p -import react.fc -import react.useMemo -import react.useState import space.kscience.dataforge.meta.invoke import space.kscience.dataforge.names.Name import space.kscience.visionforge.Colors -import space.kscience.visionforge.react.flexColumn -import space.kscience.visionforge.react.flexRow -import space.kscience.visionforge.ring.ThreeCanvasWithControls -import space.kscience.visionforge.ring.tab +import space.kscience.visionforge.compose.FlexColumn +import space.kscience.visionforge.compose.FlexRow import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.ambientLight import space.kscience.visionforge.solid.edges import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.specifications.Canvas3DOptions -import styled.css -import styled.styledDiv -import styled.styledSpan +import space.kscience.visionforge.solid.three.compose.ThreeView import kotlin.math.PI -external interface MMAppProps : Props { - var model: Model - var solids: Solids - var selected: Name? -} +@Composable +fun MMApp(solids: Solids, model: Model, selected: Name? = null) { -@OptIn(DelicateCoroutinesApi::class) -@JsExport -val MMApp = fc("Muon monitor") { props -> - - val mmOptions = useMemo { + val mmOptions = remember { Canvas3DOptions { camera { distance = 2100.0 latitude = PI / 6 azimuth = PI + PI / 6 } - } } - val root = useMemo(props.model) { - props.model.root.apply { + val root = remember(model) { + model.root.apply { edges() - ambientLight{ + ambientLight { color(Colors.white) } } } - var events: Set by useState(emptySet()) + val events = remember { mutableStateListOf() } - styledDiv { - css { - height = 100.vh - 12.pt + Div({ + style { + height(100.vh - 12.pt) } - child(ThreeCanvasWithControls) { - attrs { - this.solids = props.solids - this.builderOfSolid = CompletableDeferred(root) - this.selected = props.selected - this.options = mmOptions - tab("Events") { - flexColumn { - flexRow { - button { - +"Next" - attrs { - onClickFunction = { - solids.context.launch { - val event = window.fetch( - "http://localhost:8080/event", - RequestInit("GET") - ).then { response -> - if (response.ok) { - response.text() - } else { - error("Failed to get event") - } - }.then { body -> - Json.decodeFromString(Event.serializer(), body) - }.await() - events = events + event - props.model.displayEvent(event) - } + }) { + ThreeView(solids, root, selected, mmOptions) { + Tab("Events") { + + FlexColumn { + FlexRow { + Button("Next") { + solids.context.launch { + val event = window.fetch( + "http://localhost:8080/event", + RequestInit("GET") + ).then { response -> + if (response.ok) { + response.text() + } else { + error("Failed to get event") } - } - } - button { - +"Clear" - attrs { - onClickFunction = { - events = emptySet() - props.model.reset() - } - } + }.then { body -> + Json.decodeFromString(Event.serializer(), body) + }.await() + events.add(event) + model.displayEvent(event) } } + Button("Clear") { + events.clear() + model.reset() + } } - events.forEach { event -> - p { - styledSpan { - +event.id.toString() - } - +" : " - styledSpan { - css { - color = Color.blue - } - +event.hits.toString() + } + events.forEach { event -> + P { + Span { + Text(event.id.toString()) + } + Text(" : ") + Span({ + style { + color(Color.blue) } + }) { + Text(event.hits.toString()) } } } } - } } - -// var selected by useState { props.selected } -// -// val onSelect: (Name?) -> Unit = { -// selected = it -// } -// - -// -// gridRow { -// flexColumn { -// css { -// +"col-lg-3" -// +"order-lg-1" -// +"order-2" -// padding(0.px) -// overflowY = Overflow.auto -// height = 100.vh -// } -// //tree -// card("Object tree") { -// css { -// flex(1.0, 1.0, FlexBasis.auto) -// } -// visionTree(root, selected, onSelect) -// } -// } -// flexColumn { -// css { -// +"col-lg-6" -// +"order-lg-2" -// +"order-1" -// height = 100.vh -// } -// h1("mx-auto page-header") { -// +"Muon monitor demo" -// } -// //canvas -// -// child(ThreeCanvasComponent) { -// attrs { -// this.context = props.context -// this.solid = root -// this.selected = selected -// this.options = mmOptions -// } -// } -// } -// flexColumn { -// css { -// +"col-lg-3" -// +"order-3" -// padding(0.px) -// height = 100.vh -// } -// styledDiv { -// css { -// flex(0.0, 1.0, FlexBasis.zero) -// } -// //settings -// card("Canvas configuration") { -// canvasControls(mmOptions, root) -// } -// -// card("Events") { -// button { -// +"Next" -// attrs { -// onClickFunction = { -// GlobalScope.launch { -// val event = props.connection.get("http://localhost:8080/event") -// props.model.displayEvent(event) -// } -// } -// } -// } -// button { -// +"Clear" -// attrs { -// onClickFunction = { -// props.model.reset() -// } -// } -// } -// } -// } -// styledDiv { -// css { -// padding(0.px) -// } -// nav { -// attrs { -// attributes["aria-label"] = "breadcrumb" -// } -// ol("breadcrumb") { -// li("breadcrumb-item") { -// button(classes = "btn btn-link p-0") { -// +"World" -// attrs { -// onClickFunction = { -// selected = Name.EMPTY -// } -// } -// } -// } -// if (selected != null) { -// val tokens = ArrayList(selected?.length ?: 1) -// selected?.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") -// selected = fullName -// } -// } -// } -// } -// } -// } -// } -// } -// } -// styledDiv { -// css { -// overflowY = Overflow.auto -// } -// //properties -// card("Properties") { -// selected.let { selected -> -// val selectedObject: Vision? = when { -// selected == null -> null -// selected.isEmpty() -> root -// else -> root[selected] -// } -// if (selectedObject != null) { -// visionPropertyEditor(selectedObject, key = selected) -// } -// } -// } -// } -// } -// -// } } \ No newline at end of file diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt index afc9ce80..0fc98ac5 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt @@ -1,12 +1,11 @@ package ru.mipt.npm.muon.monitor +import org.jetbrains.compose.web.renderComposable import org.w3c.dom.Document 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.react.createRoot -import space.kscience.visionforge.react.render import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.startApplication @@ -24,13 +23,8 @@ private class MMDemoApp : Application { val model = Model(visionManager) val element = document.getElementById("app") ?: error("Element with id 'app' not found on page") - createRoot(element).render { - child(MMApp) { - attrs { - this.model = model - this.solids = context.request(Solids) - } - } + renderComposable(element) { + MMApp(context.request(Solids), model) } } } diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index c51f6691..22b109ac 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -14,17 +14,10 @@ repositories { kotlin { js(IR) { - useCommonJs() browser { webpackTask { mainOutputFileName.set("js/visionforge-playground.js") } - commonWebpackConfig { - sourceMaps = true - cssSupport{ - enabled.set(false) - } - } } binaries.executable() } @@ -57,7 +50,6 @@ kotlin { val jsMain by getting { dependencies { - implementation(projects.ui.ring) implementation(projects.visionforgeThreejs) compileOnly(npm("webpack-bundle-analyzer","4.5.0")) } diff --git a/demo/playground/src/jsMain/kotlin/playgroundMain.kt b/demo/playground/src/jsMain/kotlin/playgroundMain.kt index feff4de1..32a518ba 100644 --- a/demo/playground/src/jsMain/kotlin/playgroundMain.kt +++ b/demo/playground/src/jsMain/kotlin/playgroundMain.kt @@ -2,13 +2,13 @@ import space.kscience.dataforge.misc.DFExperimental import space.kscience.visionforge.jupyter.VFNotebookClient import space.kscience.visionforge.markup.MarkupPlugin import space.kscience.visionforge.plotly.PlotlyPlugin -import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.runVisionClient +import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.tables.TableVisionJsPlugin @DFExperimental fun main() = runVisionClient { - plugin(ThreeWithControlsPlugin) + plugin(ThreePlugin) plugin(PlotlyPlugin) plugin(MarkupPlugin) plugin(TableVisionJsPlugin) diff --git a/demo/sat-demo/build.gradle.kts b/demo/sat-demo/build.gradle.kts index 6cf37859..5e414f46 100644 --- a/demo/sat-demo/build.gradle.kts +++ b/demo/sat-demo/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + + plugins { id("space.kscience.gradle.mpp") alias(spclibs.plugins.ktor) @@ -19,6 +22,8 @@ kscience { group = "center.sciprog" +kotlin.explicitApi = ExplicitApiMode.Disabled + application { mainClass.set("ru.mipt.npm.sat.SatServerKt") } diff --git a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt index 50e7f174..8715f75d 100644 --- a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt +++ b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt @@ -72,7 +72,7 @@ class ThreeDemoGrid(element: Element) : VisionLayout { } } val element = document.getElementById("output-$name") ?: error("Element not found") - three.getOrCreateCanvas(element, canvasOptions) + ThreeCanvas(three, element, canvasOptions) }.render(vision) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8611d0aa..b2a1b7aa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -45,7 +45,7 @@ include( ":ui:ring", // ":ui:material", ":ui:bootstrap", - ":ui:compose", + ":visionforge-compose", ":visionforge-core", ":visionforge-solid", // ":visionforge-fx", diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeCanvas.kt b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeCanvas.kt deleted file mode 100644 index 9816b5c2..00000000 --- a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeCanvas.kt +++ /dev/null @@ -1,52 +0,0 @@ -package space.kscience.visionforge.compose - -import androidx.compose.runtime.* -import kotlinx.dom.clear -import org.jetbrains.compose.web.css.* -import org.jetbrains.compose.web.dom.Div -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 - -@Composable -public fun ThreeCanvas( - context: Context, - options: Canvas3DOptions?, - solid: Solid?, - selected: Name?, -) { - - val three: ThreePlugin by derivedStateOf { context.request(ThreePlugin) } - - Div({ - style { - maxWidth(100.vw) - maxHeight(100.vh) - width(100.percent) - height(100.percent) - } - }) { - var canvas: ThreeCanvas? = null - DisposableEffect(options) { - canvas = ThreeCanvas(three, scopeElement, options ?: Canvas3DOptions()) - onDispose { - scopeElement.clear() - canvas = null - } - } - LaunchedEffect(solid) { - if (solid != null) { - canvas?.render(solid) - } else { - canvas?.clear() - } - } - LaunchedEffect(selected) { - canvas?.select(selected) - } - } -} \ No newline at end of file diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeViewWithControls.kt b/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeViewWithControls.kt deleted file mode 100644 index 7a347110..00000000 --- a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeViewWithControls.kt +++ /dev/null @@ -1,169 +0,0 @@ -@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, - 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) - } -} - - diff --git a/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt b/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt index a2223692..bf557bb8 100644 --- a/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt +++ b/ui/ring/src/jsMain/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt @@ -7,8 +7,6 @@ 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.meta.boolean -import space.kscience.dataforge.meta.get import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.visionforge.ElementVisionRenderer @@ -28,16 +26,12 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer { 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) { - if (meta["controls.enabled"].boolean == false) { - three.render(element, client, name, vision, meta) - } else { - 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 } - } + 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 } } } } diff --git a/ui/compose/build.gradle.kts b/visionforge-compose/build.gradle.kts similarity index 76% rename from ui/compose/build.gradle.kts rename to visionforge-compose/build.gradle.kts index f77c8e26..f8388b5c 100644 --- a/ui/compose/build.gradle.kts +++ b/visionforge-compose/build.gradle.kts @@ -2,8 +2,6 @@ plugins { id("space.kscience.gradle.mpp") alias(spclibs.plugins.compose) -// id("org.jetbrains.compose") version "1.5.11" -// id("com.android.library") } kscience{ @@ -15,9 +13,9 @@ kscience{ kotlin { // android() sourceSets { - val commonMain by getting { - dependencies { - + commonMain{ + dependencies{ + api(projects.visionforgeCore) } } @@ -35,7 +33,6 @@ kotlin { 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) } } } diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt similarity index 98% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt index 5aaf4140..f83d4da6 100644 --- a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt +++ b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/MetaViewer.kt @@ -10,7 +10,6 @@ import org.jetbrains.compose.web.dom.Text 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 diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/bootstrap.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/bootstrap.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/bootstrap.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/bootstrap.kt diff --git a/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/composeVision.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/composeVision.kt new file mode 100644 index 00000000..e9b22261 --- /dev/null +++ b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/composeVision.kt @@ -0,0 +1,49 @@ +package space.kscience.visionforge.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import kotlinx.dom.clear +import org.jetbrains.compose.web.dom.AttrBuilderContext +import org.jetbrains.compose.web.dom.Div +import org.w3c.dom.HTMLDivElement +import space.kscience.dataforge.context.gather +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 + + +/** + * Render an Element vision via injected vision renderer inside compose-html + */ +@Composable +public fun Vision( + client: VisionClient, + vision: Vision, + name: Name = "@vision[${vision.hashCode().toString(16)}]".asName(), + meta: Meta = Meta.EMPTY, + attrs: AttrBuilderContext? = null, +): Unit = Div(attrs) { + + val renderer by derivedStateOf { + client.context.gather(ElementVisionRenderer.TYPE).values.mapNotNull { + val rating = it.rateVision(vision) + if (rating > 0) { + rating to it + } else { + null + } + }.maxBy { it.first }.second + } + + DisposableEffect(vision, name, renderer, meta) { + renderer.render(scopeElement, client, name, vision, meta) + onDispose { + scopeElement.clear() + } + } +} diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/css.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/css.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/css.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/css.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt similarity index 100% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/layouts.kt diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt similarity index 98% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt rename to visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt index b6aefee7..cf1ad9f8 100644 --- a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt +++ b/visionforge-compose/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt @@ -20,7 +20,6 @@ import space.kscience.dataforge.meta.descriptors.ValueRestriction import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.visionforge.Colors import space.kscience.visionforge.widgetType -import three.math.Color @Composable @@ -151,7 +150,8 @@ public fun ColorValueChooser( value( value?.let { value -> if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) - else "#" + Color(value.string).getHexString() + else value.string + //else "#" + Color(value.string).getHexString() } ?: "#000000" ) onChange { diff --git a/visionforge-core/api/visionforge-core.api b/visionforge-core/api/visionforge-core.api index 0cd2b904..89e926ec 100644 --- a/visionforge-core/api/visionforge-core.api +++ b/visionforge-core/api/visionforge-core.api @@ -225,6 +225,8 @@ public abstract interface class space/kscience/visionforge/ControlVision : space public final class space/kscience/visionforge/ControlVisionKt { public static final fun VisionClickEvent (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionClickEvent; public static synthetic fun VisionClickEvent$default (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionClickEvent; + public static final fun VisionInputEvent (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionInputEvent; + public static synthetic fun VisionInputEvent$default (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionInputEvent; public static final fun VisionValueChangeEvent (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/visionforge/VisionValueChangeEvent; public static synthetic fun VisionValueChangeEvent$default (Lspace/kscience/dataforge/meta/Value;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/visionforge/VisionValueChangeEvent; public static final fun onClick (Lspace/kscience/visionforge/ClickControl;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; @@ -495,6 +497,7 @@ public final class space/kscience/visionforge/VisionClientKt { public static final fun notifyPropertyChanged (Lspace/kscience/visionforge/VisionClient;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Ljava/lang/String;)V public static final fun notifyPropertyChanged (Lspace/kscience/visionforge/VisionClient;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;)V public static final fun notifyPropertyChanged (Lspace/kscience/visionforge/VisionClient;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Z)V + public static final fun sendEventAsync (Lspace/kscience/visionforge/VisionClient;Lspace/kscience/dataforge/names/Name;Lspace/kscience/visionforge/VisionEvent;)Lkotlinx/coroutines/Job; } public abstract interface class space/kscience/visionforge/VisionContainer { @@ -560,6 +563,30 @@ public final class space/kscience/visionforge/VisionGroupKt { public static synthetic fun group$default (Lspace/kscience/visionforge/MutableVisionContainer;Lspace/kscience/dataforge/names/Name;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/visionforge/SimpleVisionGroup; } +public final class space/kscience/visionforge/VisionInputEvent : space/kscience/visionforge/VisionControlEvent { + public static final field Companion Lspace/kscience/visionforge/VisionInputEvent$Companion; + public fun (Lspace/kscience/dataforge/meta/Meta;)V + public fun getMeta ()Lspace/kscience/dataforge/meta/Meta; + public final fun getName ()Lspace/kscience/dataforge/names/Name; + public final fun getValue ()Lspace/kscience/dataforge/meta/Value; + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/visionforge/VisionInputEvent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/visionforge/VisionInputEvent$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/visionforge/VisionInputEvent; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/visionforge/VisionInputEvent;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/visionforge/VisionInputEvent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class space/kscience/visionforge/VisionKt { public static final fun getVisible (Lspace/kscience/visionforge/Vision;)Ljava/lang/Boolean; public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; @@ -649,8 +676,8 @@ public final class space/kscience/visionforge/VisionPropertiesKt { public static final fun get (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lspace/kscience/dataforge/meta/Meta; public static synthetic fun get$default (Lspace/kscience/visionforge/MutableVisionProperties;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/MutableMeta; public static synthetic fun get$default (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/Meta; - public static final fun getValue (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lspace/kscience/dataforge/meta/Value; - public static synthetic fun getValue$default (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/Value; + public static final fun getValue (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;ZLjava/lang/Boolean;)Lspace/kscience/dataforge/meta/Value; + public static synthetic fun getValue$default (Lspace/kscience/visionforge/VisionProperties;Ljava/lang/String;ZLjava/lang/Boolean;ILjava/lang/Object;)Lspace/kscience/dataforge/meta/Value; public static final fun invoke (Lspace/kscience/visionforge/MutableVisionProperties;Lkotlin/jvm/functions/Function1;)V public static final fun remove (Lspace/kscience/visionforge/MutableVisionProperties;Ljava/lang/String;)V public static final fun remove (Lspace/kscience/visionforge/MutableVisionProperties;Lspace/kscience/dataforge/names/Name;)V @@ -781,8 +808,8 @@ public abstract class space/kscience/visionforge/html/VisionOfHtml : space/kscie public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtml$Companion; public fun ()V public synthetic fun (ILspace/kscience/dataforge/meta/MutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public final fun getClasses ()Ljava/util/List; - public final fun setClasses (Ljava/util/List;)V + public final fun getClasses ()Ljava/util/Set; + public final fun setClasses (Ljava/util/Set;)V public static final synthetic fun write$Self (Lspace/kscience/visionforge/html/VisionOfHtml;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } @@ -813,9 +840,16 @@ public final class space/kscience/visionforge/html/VisionOfHtmlButton$Companion } public abstract class space/kscience/visionforge/html/VisionOfHtmlControl : space/kscience/visionforge/html/VisionOfHtml, space/kscience/visionforge/ControlVision { + public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlControl$Companion; public fun ()V + public synthetic fun (ILspace/kscience/dataforge/meta/MutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun dispatchControlEvent (Lspace/kscience/visionforge/VisionControlEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getControlEventFlow ()Lkotlinx/coroutines/flow/SharedFlow; + public static final synthetic fun write$Self (Lspace/kscience/visionforge/html/VisionOfHtmlControl;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/visionforge/html/VisionOfHtmlControl$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class space/kscience/visionforge/html/VisionOfHtmlForm : space/kscience/visionforge/html/VisionOfHtmlControl { @@ -849,11 +883,9 @@ public final class space/kscience/visionforge/html/VisionOfHtmlFormKt { public class space/kscience/visionforge/html/VisionOfHtmlInput : space/kscience/visionforge/html/VisionOfHtmlControl { public static final field Companion Lspace/kscience/visionforge/html/VisionOfHtmlInput$Companion; - public synthetic fun (ILjava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Ljava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;)V - public synthetic fun (Ljava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILspace/kscience/dataforge/meta/MutableMeta;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;)V public final fun getDisabled ()Z - public final fun getFeedbackMode ()Lspace/kscience/visionforge/html/InputFeedbackMode; public final fun getFieldName ()Ljava/lang/String; public final fun getInputType ()Ljava/lang/String; public final fun getValue ()Lspace/kscience/dataforge/meta/Value; @@ -891,6 +923,8 @@ public final class space/kscience/visionforge/html/VisionOfHtmlKt { public static synthetic fun htmlRangeField$default (Lspace/kscience/visionforge/html/VisionOutput;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/visionforge/html/VisionOfRangeField; public static final fun htmlTextField (Lspace/kscience/visionforge/html/VisionOutput;Lkotlin/jvm/functions/Function1;)Lspace/kscience/visionforge/html/VisionOfTextField; public static synthetic fun htmlTextField$default (Lspace/kscience/visionforge/html/VisionOutput;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/visionforge/html/VisionOfTextField; + public static final fun onInput (Lspace/kscience/visionforge/html/VisionOfHtmlInput;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static synthetic fun onInput$default (Lspace/kscience/visionforge/html/VisionOfHtmlInput;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun onValueChange (Lspace/kscience/visionforge/html/VisionOfHtmlInput;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; public static synthetic fun onValueChange$default (Lspace/kscience/visionforge/html/VisionOfHtmlInput;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt index dc27662f..627a2aa1 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ControlVision.kt @@ -83,9 +83,32 @@ public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEven override fun toString(): String = meta.toString() } + public fun VisionValueChangeEvent(value: Value?, name: Name? = null): VisionValueChangeEvent = VisionValueChangeEvent( Meta { this.value = value name?.let { set("name", it.toString()) } } ) + + +@Serializable +@SerialName("control.input") +public class VisionInputEvent(override val meta: Meta) : VisionControlEvent() { + + public val value: Value? get() = meta.value + + /** + * The name of a control that fired the event + */ + public val name: Name? get() = meta["name"]?.string?.parseAsName() + + override fun toString(): String = meta.toString() +} + +public fun VisionInputEvent(value: Value?, name: Name? = null): VisionInputEvent = VisionInputEvent( + Meta { + this.value = value + name?.let { set("name", it.toString()) } + } +) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt index 7a62de7d..2d77dfd0 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -1,5 +1,7 @@ package space.kscience.visionforge +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import space.kscience.dataforge.context.Plugin import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.names.Name @@ -16,6 +18,10 @@ public interface VisionClient: Plugin { public fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) } +public fun VisionClient.sendEventAsync(targetName: Name, event: VisionEvent): Job = context.launch { + sendEvent(targetName, event) +} + public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Meta?) { notifyPropertyChanged(visionName, propertyName.parseAsName(true), item) } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt index 9b5a21ac..84ed8286 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt @@ -85,6 +85,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont subclass(VisionMetaEvent.serializer()) subclass(VisionClickEvent.serializer()) subclass(VisionValueChangeEvent.serializer()) + subclass(VisionInputEvent.serializer()) } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionProperties.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionProperties.kt index 3c375c62..b7a66625 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionProperties.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionProperties.kt @@ -265,14 +265,14 @@ public abstract class AbstractVisionProperties( public fun VisionProperties.getValue( name: String, - inherit: Boolean? = null, + inherit: Boolean, includeStyles: Boolean? = null, ): Value? = getValue(name.parseAsName(), inherit, includeStyles) /** * Get [Vision] property using key as a String */ -public fun VisionProperties.get( +public operator fun VisionProperties.get( name: String, inherit: Boolean? = null, includeStyles: Boolean? = null, @@ -292,7 +292,7 @@ public fun MutableVisionProperties.root( /** * Get [Vision] property using key as a String */ -public fun MutableVisionProperties.get( +public operator fun MutableVisionProperties.get( name: String, inherit: Boolean? = null, includeStyles: Boolean? = null, diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt index 0738ddc6..2bd7b9de 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionOfHtml.kt @@ -12,15 +12,16 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.asName -import space.kscience.visionforge.AbstractVision -import space.kscience.visionforge.ControlVision -import space.kscience.visionforge.VisionControlEvent -import space.kscience.visionforge.VisionValueChangeEvent +import space.kscience.visionforge.* @Serializable public abstract class VisionOfHtml : AbstractVision() { - public var classes: List by properties.stringList(*emptyArray()) + public var classes: Set + get() = properties.get(::classes.name,false).stringList?.toSet() ?: emptySet() + set(value) { + properties[::classes.name] = value.map { it.asValue() } + } } @Serializable @@ -58,6 +59,7 @@ public enum class InputFeedbackMode { NONE } +@Serializable public abstract class VisionOfHtmlControl: VisionOfHtml(), ControlVision{ @Transient @@ -76,7 +78,6 @@ public abstract class VisionOfHtmlControl: VisionOfHtml(), ControlVision{ @SerialName("html.input") public open class VisionOfHtmlInput( public val inputType: String, - public val feedbackMode: InputFeedbackMode = InputFeedbackMode.ONCHANGE, ) : VisionOfHtmlControl() { public var value: Value? by properties.value() public var disabled: Boolean by properties.boolean { false } @@ -92,6 +93,11 @@ public fun VisionOfHtmlInput.onValueChange( callback: suspend VisionValueChangeEvent.() -> Unit, ): Job = controlEventFlow.filterIsInstance().onEach(callback).launchIn(scope) +public fun VisionOfHtmlInput.onInput( + scope: CoroutineScope = manager?.context ?: error("Coroutine context is not resolved for $this"), + callback: suspend VisionInputEvent.() -> Unit, +): Job = controlEventFlow.filterIsInstance().onEach(callback).launchIn(scope) + @Suppress("UnusedReceiverParameter") public inline fun VisionOutput.htmlInput( inputType: String, diff --git a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt index b1ca970a..e8ebe406 100644 --- a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt +++ b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt @@ -40,7 +40,7 @@ internal class VisionPropertyTest { @Test fun testPropertyEdit() { val vision = manager.group() - vision.properties.get("fff.ddd").apply { + vision.properties["fff.ddd"].apply { value = 2.asValue() } assertEquals(2, vision.properties.getValue("fff.ddd")?.int) @@ -50,7 +50,7 @@ internal class VisionPropertyTest { @Test fun testPropertyUpdate() { val vision = manager.group() - vision.properties.get("fff").updateWith(TestScheme) { + vision.properties["fff"].updateWith(TestScheme) { ddd = 2 } assertEquals(2, vision.properties.getValue("fff.ddd")?.int) diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt index 4112370a..3deb00da 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/inputRenderers.kt @@ -1,18 +1,14 @@ package space.kscience.visionforge -import kotlinx.coroutines.launch import kotlinx.dom.clear import kotlinx.html.InputType import kotlinx.html.div import kotlinx.html.js.input import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement -import org.w3c.dom.events.Event -import space.kscience.dataforge.meta.Value import space.kscience.dataforge.meta.asValue import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.string -import space.kscience.dataforge.names.Name import space.kscience.visionforge.html.* /** @@ -26,13 +22,6 @@ internal fun HTMLElement.subscribeToVision(vision: VisionOfHtml) { } } - -private fun VisionClient.sendInputEvent(name: Name, value: Value?) { - context.launch { - sendEvent(name, VisionValueChangeEvent(value, name)) - } -} - /** * Subscribes the HTML input element to a given vision. * @@ -62,16 +51,13 @@ internal val inputVisionRenderer: ElementVisionRenderer = ElementVisionRenderer< input { type = InputType.text }.also { htmlInputElement -> - val onEvent: (Event) -> Unit = { - client.sendInputEvent(name, htmlInputElement.value.asValue()) + + htmlInputElement.onchange = { + client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name)) } - - when (vision.feedbackMode) { - InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent - - InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent - InputFeedbackMode.NONE -> {} + htmlInputElement.oninput = { + client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name)) } htmlInputElement.subscribeToInput(vision) @@ -86,18 +72,16 @@ internal val checkboxVisionRenderer: ElementVisionRenderer = input { type = InputType.checkBox }.also { htmlInputElement -> - val onEvent: (Event) -> Unit = { - client.sendInputEvent(name, htmlInputElement.checked.asValue()) + + htmlInputElement.onchange = { + client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name)) } - - when (vision.feedbackMode) { - InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent - - InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent - InputFeedbackMode.NONE -> {} + htmlInputElement.oninput = { + client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name)) } + htmlInputElement.subscribeToInput(vision) vision.useProperty(VisionOfCheckbox::checked) { htmlInputElement.checked = it ?: false @@ -110,16 +94,13 @@ internal val textVisionRenderer: ElementVisionRenderer = input { type = InputType.text }.also { htmlInputElement -> - val onEvent: (Event) -> Unit = { - client.sendInputEvent(name, htmlInputElement.value.asValue()) + + htmlInputElement.onchange = { + client.sendEventAsync(name, VisionValueChangeEvent(htmlInputElement.value.asValue(), name)) } - - when (vision.feedbackMode) { - InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent - - InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent - InputFeedbackMode.NONE -> {} + htmlInputElement.oninput = { + client.sendEventAsync(name, VisionInputEvent(htmlInputElement.value.asValue(), name)) } htmlInputElement.subscribeToInput(vision) @@ -135,18 +116,19 @@ internal val numberVisionRenderer: ElementVisionRenderer = type = InputType.number }.also { htmlInputElement -> - val onEvent: (Event) -> Unit = { + htmlInputElement.onchange = { htmlInputElement.value.toDoubleOrNull()?.let { - client.sendInputEvent(name, htmlInputElement.value.asValue()) + client.sendEventAsync(name, VisionValueChangeEvent(it.asValue(), name)) } } - when (vision.feedbackMode) { - InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent - - InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent - InputFeedbackMode.NONE -> {} + htmlInputElement.oninput = { + htmlInputElement.value.toDoubleOrNull()?.let { + client.sendEventAsync(name, VisionInputEvent(it.asValue(), name)) + } } + + htmlInputElement.subscribeToInput(vision) vision.useProperty(VisionOfNumberField::value) { htmlInputElement.valueAsNumber = it?.double ?: 0.0 @@ -163,18 +145,18 @@ internal val rangeVisionRenderer: ElementVisionRenderer = step = vision.step.toString() }.also { htmlInputElement -> - val onEvent: (Event) -> Unit = { + htmlInputElement.onchange = { htmlInputElement.value.toDoubleOrNull()?.let { - client.sendInputEvent(name, htmlInputElement.value.asValue()) + client.sendEventAsync(name, VisionValueChangeEvent(it.asValue(), name)) } } - when (vision.feedbackMode) { - InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent - - InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent - InputFeedbackMode.NONE -> {} + htmlInputElement.oninput = { + htmlInputElement.value.toDoubleOrNull()?.let { + client.sendEventAsync(name, VisionInputEvent(it.asValue(), name)) + } } + htmlInputElement.subscribeToInput(vision) vision.useProperty(VisionOfRangeField::value) { htmlInputElement.valueAsNumber = it?.double ?: 0.0 diff --git a/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts index e6fd162b..b9883d9b 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts +++ b/visionforge-jupyter/visionforge-jupyter-common/build.gradle.kts @@ -7,16 +7,7 @@ description = "Jupyter api artifact including all common modules" kscience { fullStack( "js/visionforge-jupyter-common.js", - jsConfig = { useCommonJs() } - ) { - commonWebpackConfig { - sourceMaps = false - cssSupport { - enabled.set(false) - } - } - } - + ) dependencies { api(projects.visionforgeSolid) api(projects.visionforgePlotly) @@ -30,7 +21,6 @@ kscience { } jsMain { - implementation(projects.ui.ring) implementation(projects.visionforgeThreejs) } diff --git a/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt b/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt index e5fb4edd..ae0e29e2 100644 --- a/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt +++ b/visionforge-jupyter/visionforge-jupyter-common/src/jsMain/kotlin/commonJupyter.kt @@ -3,12 +3,12 @@ package space.kscience.visionforge.gdml.jupyter import space.kscience.visionforge.jupyter.VFNotebookClient import space.kscience.visionforge.markup.MarkupPlugin import space.kscience.visionforge.plotly.PlotlyPlugin -import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.runVisionClient +import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.tables.TableVisionJsPlugin public fun main(): Unit = runVisionClient { - plugin(ThreeWithControlsPlugin) + plugin(ThreePlugin) plugin(PlotlyPlugin) plugin(MarkupPlugin) plugin(TableVisionJsPlugin) diff --git a/visionforge-solid/api/visionforge-solid.api b/visionforge-solid/api/visionforge-solid.api index a3dc7838..cfeecfe3 100644 --- a/visionforge-solid/api/visionforge-solid.api +++ b/visionforge-solid/api/visionforge-solid.api @@ -1141,14 +1141,14 @@ public final class space/kscience/visionforge/solid/specifications/Canvas3DOptio public final fun getAxes ()Lspace/kscience/visionforge/solid/specifications/AxesScheme; public final fun getCamera ()Lspace/kscience/visionforge/solid/specifications/CameraScheme; public final fun getClipping ()Lspace/kscience/visionforge/solid/specifications/PointScheme; - public final fun getControls ()Lspace/kscience/visionforge/solid/specifications/ControlsScheme; + public final fun getControls ()Lspace/kscience/visionforge/solid/specifications/Canvas3DUIScheme; public final fun getLayers ()Ljava/util/List; public final fun getOnSelect ()Lkotlin/jvm/functions/Function1; public final fun getSize ()Lspace/kscience/visionforge/solid/specifications/CanvasSize; public final fun setAxes (Lspace/kscience/visionforge/solid/specifications/AxesScheme;)V public final fun setCamera (Lspace/kscience/visionforge/solid/specifications/CameraScheme;)V public final fun setClipping (Lspace/kscience/visionforge/solid/specifications/PointScheme;)V - public final fun setControls (Lspace/kscience/visionforge/solid/specifications/ControlsScheme;)V + public final fun setControls (Lspace/kscience/visionforge/solid/specifications/Canvas3DUIScheme;)V public final fun setLayers (Ljava/util/List;)V public final fun setOnSelect (Lkotlin/jvm/functions/Function1;)V public final fun setSize (Lspace/kscience/visionforge/solid/specifications/CanvasSize;)V @@ -1163,6 +1163,16 @@ public final class space/kscience/visionforge/solid/specifications/Canvas3DOptio public static final fun computeWidth (Lspace/kscience/visionforge/solid/specifications/CanvasSize;Ljava/lang/Number;)I } +public final class space/kscience/visionforge/solid/specifications/Canvas3DUIScheme : space/kscience/dataforge/meta/Scheme { + public static final field Companion Lspace/kscience/visionforge/solid/specifications/Canvas3DUIScheme$Companion; + public fun ()V + public final fun getEnabled ()Z + public final fun setEnabled (Z)V +} + +public final class space/kscience/visionforge/solid/specifications/Canvas3DUIScheme$Companion : space/kscience/dataforge/meta/SchemeSpec { +} + public final class space/kscience/visionforge/solid/specifications/CanvasSize : space/kscience/dataforge/meta/Scheme { public static final field Companion Lspace/kscience/visionforge/solid/specifications/CanvasSize$Companion; public fun ()V @@ -1189,14 +1199,6 @@ public final class space/kscience/visionforge/solid/specifications/Clipping : sp public fun getDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor; } -public final class space/kscience/visionforge/solid/specifications/ControlsScheme : space/kscience/dataforge/meta/Scheme { - public static final field Companion Lspace/kscience/visionforge/solid/specifications/ControlsScheme$Companion; - public fun ()V -} - -public final class space/kscience/visionforge/solid/specifications/ControlsScheme$Companion : space/kscience/dataforge/meta/SchemeSpec { -} - public final class space/kscience/visionforge/solid/specifications/PointScheme : space/kscience/dataforge/meta/Scheme { public static final field Companion Lspace/kscience/visionforge/solid/specifications/PointScheme$Companion; public fun ()V diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index b24c9b75..11f179ce 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -62,7 +62,7 @@ public class Canvas3DOptions : Scheme() { @Suppress("DEPRECATION") public var axes: AxesScheme by spec(AxesScheme) public var camera: CameraScheme by spec(CameraScheme) - public var controls: ControlsScheme by spec(ControlsScheme) + public var controls: Canvas3DUIScheme by spec(Canvas3DUIScheme) public var size: CanvasSize by spec(CanvasSize) @@ -92,7 +92,7 @@ public class Canvas3DOptions : Scheme() { hide() } - scheme(Canvas3DOptions::controls, ControlsScheme) { + scheme(Canvas3DOptions::controls, Canvas3DUIScheme) { hide() } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DUIScheme.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DUIScheme.kt new file mode 100644 index 00000000..4424e2fc --- /dev/null +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DUIScheme.kt @@ -0,0 +1,13 @@ +package space.kscience.visionforge.solid.specifications + +import space.kscience.dataforge.meta.Scheme +import space.kscience.dataforge.meta.SchemeSpec +import space.kscience.dataforge.meta.boolean + + +public class Canvas3DUIScheme : Scheme() { + + public var enabled: Boolean by boolean{true} + + public companion object : SchemeSpec(::Canvas3DUIScheme) +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/ControlsScheme.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/ControlsScheme.kt deleted file mode 100644 index 5e68f37c..00000000 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/ControlsScheme.kt +++ /dev/null @@ -1,9 +0,0 @@ -package space.kscience.visionforge.solid.specifications - -import space.kscience.dataforge.meta.Scheme -import space.kscience.dataforge.meta.SchemeSpec - - -public class ControlsScheme : Scheme() { - public companion object : SchemeSpec(::ControlsScheme) -} \ 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 4991c12d..7b559ab7 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 @@ -4,6 +4,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest +import space.kscience.dataforge.meta.getValue import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.set import space.kscience.dataforge.meta.string diff --git a/visionforge-tables/build.gradle.kts b/visionforge-tables/build.gradle.kts index f4197864..9eb9f2cf 100644 --- a/visionforge-tables/build.gradle.kts +++ b/visionforge-tables/build.gradle.kts @@ -9,23 +9,17 @@ kscience { js { useCommonJs() binaries.library() - browser { - commonWebpackConfig { - cssSupport { - enabled.set(true) - } - } - } } - dependencies { + + useSerialization() + commonMain { api(projects.visionforgeCore) api("space.kscience:tables-kt:${tablesVersion}") } - dependencies(jsMain) { + jsMain { implementation(npm("tabulator-tables", "5.5.2")) implementation(npm("@types/tabulator-tables", "5.5.3")) } - useSerialization() } readme { diff --git a/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/TableVisionPlugin.kt b/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/TableVisionPlugin.kt index d6bc9b1d..4a8a06d1 100644 --- a/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/TableVisionPlugin.kt +++ b/visionforge-tables/src/commonMain/kotlin/space/kscience/visionforge/tables/TableVisionPlugin.kt @@ -13,12 +13,11 @@ import space.kscience.visionforge.VisionPlugin public class TableVisionPlugin : VisionPlugin() { override val tag: PluginTag get() = Companion.tag - override val visionSerializersModule: SerializersModule - get() = SerializersModule { - polymorphic(Vision::class) { - subclass(VisionOfTable.serializer()) - } + override val visionSerializersModule: SerializersModule = SerializersModule { + polymorphic(Vision::class) { + subclass(VisionOfTable.serializer()) } + } public companion object : PluginFactory { override val tag: PluginTag = PluginTag("vision.table", PluginTag.DATAFORGE_GROUP) diff --git a/visionforge-threejs/build.gradle.kts b/visionforge-threejs/build.gradle.kts index 41714618..6e522585 100644 --- a/visionforge-threejs/build.gradle.kts +++ b/visionforge-threejs/build.gradle.kts @@ -1,22 +1,26 @@ plugins { id("space.kscience.gradle.mpp") + alias(spclibs.plugins.compose) } -kotlin{ +kotlin { explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Disabled } -kscience{ - js{ +kscience { + js { binaries.library() } - jsMain{ - dependencies { - api(projects.visionforgeSolid) - implementation(npm("three", "0.143.0")) - implementation(npm("three-csg-ts", "3.1.10")) - implementation(npm("three.meshline","1.4.0")) - } + + commonMain { + api(projects.visionforgeSolid) + api(projects.visionforgeCompose) + } + + jsMain { + implementation(npm("three", "0.143.0")) + implementation(npm("three-csg-ts", "3.1.13")) + implementation(npm("three.meshline", "1.4.0")) } } diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index b65d1fce..5723626a 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -236,7 +236,7 @@ public class ThreeCanvas( // } // } - private fun addControls(element: Node, controls: ControlsScheme) { + private fun addControls(element: Node, controls: Canvas3DUIScheme) { when (controls.meta["type"].string) { "trackball" -> TrackballControls(camera, element) else -> OrbitControls(camera, element) diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index 84552dd4..724c52a7 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -2,14 +2,15 @@ package space.kscience.visionforge.solid.three import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import org.jetbrains.compose.web.renderComposable import org.w3c.dom.Element -import org.w3c.dom.HTMLElement import space.kscience.dataforge.context.* import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.names.* import space.kscience.visionforge.* import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.specifications.Canvas3DOptions +import space.kscience.visionforge.solid.three.compose.ThreeView import space.kscience.visionforge.solid.three.set import three.core.Object3D import kotlin.collections.set @@ -21,7 +22,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { public val solids: Solids by require(Solids) - public val client: VisionClient? get() = context.plugins.get() + public val client: VisionClient by require(JsVisionClient) private val objectFactories = HashMap, ThreeFactory<*>>() private val compositeFactory = ThreeCompositeFactory(this) @@ -123,15 +124,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } } - private val canvasCache = HashMap() - - public fun getOrCreateCanvas( - element: Element, - options: Canvas3DOptions, - ): ThreeCanvas = canvasCache.getOrPut(element) { - ThreeCanvas(this, element, options) - } - override fun content(target: String): Map { return when (target) { ElementVisionRenderer.TYPE -> mapOf("three".asName() to this) @@ -142,20 +134,11 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { override fun rateVision(vision: Vision): Int = if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING - internal fun renderSolid( - element: Element, - vision: Solid, - options: Canvas3DOptions, - ): ThreeCanvas = getOrCreateCanvas(element, options).apply { - render(vision) - } - override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) { - renderSolid( - element, - vision as? Solid ?: error("Solid expected but ${vision::class} found"), - Canvas3DOptions.read(meta) - ) + require(vision is Solid) { "Expected Solid but found ${vision::class}" } + renderComposable(element) { + ThreeView(solids, vision, null, Canvas3DOptions.read(meta)) + } } public companion object : PluginFactory { @@ -165,14 +148,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } } -public fun ThreePlugin.render( - element: HTMLElement, - obj: Solid, - optionsBuilder: Canvas3DOptions.() -> Unit = {}, -): ThreeCanvas = renderSolid(element, obj, Canvas3DOptions(optionsBuilder)).apply { - options.apply(optionsBuilder) -} - internal operator fun Object3D.set(token: NameToken, object3D: Object3D) { object3D.name = token.toString() add(object3D) diff --git a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeControls.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt similarity index 95% rename from ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeControls.kt rename to visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt index c559b946..238aea55 100644 --- a/ui/compose/src/jsMain/kotlin/space/kscience/visionforge/compose/ThreeControls.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt @@ -1,4 +1,4 @@ -package space.kscience.visionforge.compose +package space.kscience.visionforge.solid.three.compose import androidx.compose.runtime.Composable import org.jetbrains.compose.web.css.* @@ -9,6 +9,7 @@ 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.solid.specifications.Canvas3DOptions diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt new file mode 100644 index 00000000..4b680b80 --- /dev/null +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt @@ -0,0 +1,205 @@ +package space.kscience.visionforge.solid.three.compose + +import androidx.compose.runtime.* +import app.softwork.bootstrapcompose.Card +import kotlinx.dom.clear +import org.jetbrains.compose.web.ExperimentalComposeWebApi +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.* +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.Vision +import space.kscience.visionforge.compose.* +import space.kscience.visionforge.root +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 +import space.kscience.visionforge.solid.three.ThreeCanvas +import space.kscience.visionforge.solid.three.ThreePlugin +import space.kscience.visionforge.styles + +@Composable +private fun SimpleThreeView( + context: Context, + options: Canvas3DOptions?, + solid: Solid?, + selected: Name?, +) { + + val three: ThreePlugin by derivedStateOf { context.request(ThreePlugin) } + + Div({ + style { + maxWidth(100.vw) + maxHeight(100.vh) + width(100.percent) + height(100.percent) + } + }) { + var canvas: ThreeCanvas? = null + DisposableEffect(options) { + canvas = ThreeCanvas(three, scopeElement, options ?: Canvas3DOptions()) + onDispose { + scopeElement.clear() + canvas = null + } + } + LaunchedEffect(solid) { + if (solid != null) { + canvas?.render(solid) + } else { + canvas?.clear() + } + } + LaunchedEffect(selected) { + canvas?.select(selected) + } + } +} + + +@Composable +public fun ThreeView( + solids: Solids, + solid: Solid?, + initialSelected: Name? = null, + options: Canvas3DOptions? = null, + sidebarTabs: @Composable TabsBuilder.() -> Unit = {}, +) { + var selected: Name? by remember { mutableStateOf(initialSelected) } + + val optionsSnapshot = remember(options) { + (options ?: Canvas3DOptions()).apply { + this.onSelect = { + selected = it + } + } + } + + val selectedVision: Vision? = remember(solid, selected) { + selected?.let { + when { + it.isEmpty() -> solid + else -> (solid as? SolidGroup)?.get(it) + } + } + } + + if (optionsSnapshot.controls.enabled) { + + + 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) + + @OptIn(ExperimentalComposeWebApi::class) 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 { + SimpleThreeView(solids.context, optionsSnapshot, 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, optionsSnapshot, selected, onSelect = { selected = it }, tabBuilder = sidebarTabs) + } + } else { + SimpleThreeView(solids.context, optionsSnapshot, solid, selected) + } +} \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/build.gradle.kts b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts index 059f3784..395fea38 100644 --- a/visionforge-threejs/visionforge-threejs-server/build.gradle.kts +++ b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts @@ -1,29 +1,24 @@ plugins { id("space.kscience.gradle.mpp") + alias(spclibs.plugins.compose) } val ktorVersion: String by rootProject.extra kscience { - fullStack("js/visionforge-three.js") { - commonWebpackConfig { - cssSupport { - enabled.set(false) - } - } - } + fullStack("js/visionforge-three.js") - dependencies { + commonMain { api(projects.visionforgeSolid) + api(projects.visionforgeCompose) } - dependencies(jvmMain) { + jvmMain{ api(projects.visionforgeServer) } - dependencies(jsMain) { + jsMain{ api(projects.visionforgeThreejs) - api(projects.ui.ring) compileOnly(npm("webpack-bundle-analyzer","4.5.0")) } } \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/space/kscience/visionforge/three/jsMain.kt b/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/space/kscience/visionforge/three/jsMain.kt index d1635ddf..ed2908f5 100644 --- a/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/space/kscience/visionforge/three/jsMain.kt +++ b/visionforge-threejs/visionforge-threejs-server/src/jsMain/kotlin/space/kscience/visionforge/three/jsMain.kt @@ -1,11 +1,11 @@ package space.kscience.visionforge.three import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.runVisionClient +import space.kscience.visionforge.solid.three.ThreePlugin @DFExperimental public fun main(): Unit = runVisionClient { - plugin(ThreeWithControlsPlugin) + plugin(ThreePlugin) } \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/webpack.config.d/01.ring.js b/visionforge-threejs/visionforge-threejs-server/webpack.config.d/01.ring.js deleted file mode 100644 index b3cc4770..00000000 --- a/visionforge-threejs/visionforge-threejs-server/webpack.config.d/01.ring.js +++ /dev/null @@ -1,23 +0,0 @@ -const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; -const path = require('path'); - -config.module.rules.push(...ringConfig.module.rules) - -config.module.rules.push( - { - test: /\.css$/, - exclude: [ - path.resolve(__dirname, "../../node_modules/@jetbrains/ring-ui") - ], - use: [ - { - loader: 'style-loader', - options: {} - }, - { - loader: 'css-loader', - options: {} - } - ] - } -) \ No newline at end of file diff --git a/visionforge-threejs/visionforge-threejs-server/webpack.config.d/02.bundle.js b/visionforge-threejs/visionforge-threejs-server/webpack.config.d/02.bundle.js deleted file mode 100644 index 947253ca..00000000 --- a/visionforge-threejs/visionforge-threejs-server/webpack.config.d/02.bundle.js +++ /dev/null @@ -1,10 +0,0 @@ -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -module.exports = { - plugins: [ - new BundleAnalyzerPlugin({ - analyzerMode: "static", - reportFilename: "bundle-report.html" - }) - ] -} \ No newline at end of file