Moving to compose

This commit is contained in:
Alexander Nozik 2023-12-18 09:41:05 +03:00
parent 0c9d849e97
commit 4f6f4b9268
59 changed files with 765 additions and 1050 deletions

View File

@ -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`.

View File

@ -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<KotlinJsCompile>{
kotlinOptions{
useEsClasses = true
}
}
// tasks.withType<KotlinJsCompile>{
// kotlinOptions{
// useEsClasses = true
// }
// }
}
ksciencePublish {

View File

@ -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"))
}

View File

@ -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<GDMLAppProps>("GDMLApp") { props ->
var deferredVision: Deferred<Solid?> 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<Solid?> {
val deferred = CompletableDeferred<Solid?>()
fun readFileAsync(file: File) {
FileReader().apply {
onload = {
val data = result as String
@ -49,7 +36,7 @@ val GDMLApp = fc<GDMLAppProps>("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<GDMLAppProps>("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)
}
}
}
}
}
}

View File

@ -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<String, Any>) {
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)
}
}
}

View File

@ -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)
}
}

View File

@ -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<String, Any>) {
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() {

View File

@ -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<HTMLDivElement>? = null,
block: Plot.() -> Unit,
) = Vision(
client = client,
attrs = attrs,
meta = meta,
vision = Plot().apply(block).asVision()
)
val GravityDemo = fc<DemoProps> { 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<HTMLDivElement>? = 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)
}
}
})
}
}
}

View File

@ -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<MarkupProps>("Markup") { props ->
val elementRef = useRef<Element>(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
}
}

View File

@ -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<PlotlyProps>("Plotly") { props ->
val elementRef = useRef<Element>(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)
}
}

View File

@ -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"))
}

View File

@ -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<MMAppProps>("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<Event> by useState(emptySet())
val events = remember { mutableStateListOf<Event>() }
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<Event>("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<NameToken>(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)
// }
// }
// }
// }
// }
//
// }
}

View File

@ -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)
}
}
}

View File

@ -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"))
}

View File

@ -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)

View File

@ -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")
}

View File

@ -72,7 +72,7 @@ class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
}
}
val element = document.getElementById("output-$name") ?: error("Element not found")
three.getOrCreateCanvas(element, canvasOptions)
ThreeCanvas(three, element, canvasOptions)
}.render(vision)
}
}

View File

@ -45,7 +45,7 @@ include(
":ui:ring",
// ":ui:material",
":ui:bootstrap",
":ui:compose",
":visionforge-compose",
":visionforge-core",
":visionforge-solid",
// ":visionforge-fx",

View File

@ -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)
}
}
}

View File

@ -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<Solid?>,
initialSelected: Name?,
options: Canvas3DOptions?,
tabBuilder: @Composable TabsBuilder.() -> Unit = {},
) {
var selected: Name? by remember { mutableStateOf(initialSelected) }
var solid: Solid? by remember { mutableStateOf(null) }
LaunchedEffect(builderOfSolid) {
solids.context.launch {
solid = builderOfSolid.await()
//ensure that the solid is properly rooted
if (solid?.parent == null) {
solid?.setAsRoot(solids.context.visionManager)
}
}
}
val optionsWithSelector = remember(options) {
(options ?: Canvas3DOptions()).apply {
this.onSelect = {
selected = it
}
}
}
val selectedVision: Vision? = remember(builderOfSolid, selected) {
selected?.let {
when {
it.isEmpty() -> solid
else -> (solid as? SolidGroup)?.get(it)
}
}
}
FlexRow({
style {
height(100.percent)
width(100.percent)
flexWrap(FlexWrap.Wrap)
alignItems(AlignItems.Stretch)
alignContent(AlignContent.Stretch)
}
}) {
FlexColumn({
style {
height(100.percent)
minWidth(600.px)
flex(10, 1, 600.px)
position(Position.Relative)
}
}) {
if (solid == null) {
Div({
style {
position(Position.Fixed)
width(100.percent)
height(100.percent)
zIndex(1000)
top(40.percent)
left(0.px)
opacity(0.5)
filter {
opacity(50.percent)
}
}
}) {
Div({ classes("d-flex", " justify-content-center") }) {
Div({
classes("spinner-grow", "text-primary")
style {
width(3.cssRem)
height(3.cssRem)
zIndex(20)
}
attr("role", "status")
}) {
Span({ classes("sr-only") }) { Text("Loading 3D vision") }
}
}
}
} else {
ThreeCanvas(solids.context, optionsWithSelector, solid, selected)
}
selectedVision?.let { vision ->
Div({
style {
position(Position.Absolute)
top(5.px)
right(5.px)
width(450.px)
}
}) {
Card(
headerAttrs = {
// border = true
},
header = {
NameCrumbs(selected) { selected = it }
}
) {
PropertyEditor(
scope = solids.context,
meta = vision.properties.root(),
getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
},
updates = vision.properties.changes,
rootDescriptor = vision.descriptor
)
}
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
P {
B { Text("Styles: ") }
Text(styles.joinToString(separator = ", "))
}
}
}
}
}
}
FlexColumn({
style {
paddingAll(4.px)
minWidth(400.px)
height(100.percent)
overflowY("auto")
flex(1, 10, 300.px)
}
}) {
ThreeControls(solid, optionsWithSelector, selected, onSelect = { selected = it }, tabBuilder = tabBuilder)
}
}

View File

@ -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 }
}
}
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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<HTMLDivElement>? = null,
): Unit = Div(attrs) {
val renderer by derivedStateOf {
client.context.gather<ElementVisionRenderer>(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()
}
}
}

View File

@ -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 {

View File

@ -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 <init> (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 <init> ()V
public synthetic fun <init> (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 <init> ()V
public synthetic fun <init> (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 <init> (ILjava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;)V
public synthetic fun <init> (Ljava/lang/String;Lspace/kscience/visionforge/html/InputFeedbackMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ILspace/kscience/dataforge/meta/MutableMeta;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (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;
}

View File

@ -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()) }
}
)

View File

@ -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)
}

View File

@ -85,6 +85,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont
subclass(VisionMetaEvent.serializer())
subclass(VisionClickEvent.serializer())
subclass(VisionValueChangeEvent.serializer())
subclass(VisionInputEvent.serializer())
}
}

View File

@ -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,

View File

@ -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<String> by properties.stringList(*emptyArray())
public var classes: Set<String>
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<VisionValueChangeEvent>().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<VisionInputEvent>().onEach(callback).launchIn(scope)
@Suppress("UnusedReceiverParameter")
public inline fun VisionOutput.htmlInput(
inputType: String,

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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 <init> ()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 <init> ()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 <init> ()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 <init> ()V

View File

@ -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()
}

View File

@ -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>(::Canvas3DUIScheme)
}

View File

@ -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>(::ControlsScheme)
}

View File

@ -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

View File

@ -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 {

View File

@ -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<TableVisionPlugin> {
override val tag: PluginTag = PluginTag("vision.table", PluginTag.DATAFORGE_GROUP)

View File

@ -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"))
}
}

View File

@ -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)

View File

@ -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<VisionClient>()
public val client: VisionClient by require(JsVisionClient)
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this)
@ -123,15 +124,6 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
}
}
private val canvasCache = HashMap<Element, ThreeCanvas>()
public fun getOrCreateCanvas(
element: Element,
options: Canvas3DOptions,
): ThreeCanvas = canvasCache.getOrPut(element) {
ThreeCanvas(this, element, options)
}
override fun content(target: String): Map<Name, Any> {
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<ThreePlugin> {
@ -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)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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"))
}
}

View File

@ -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)
}

View File

@ -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: {}
}
]
}
)

View File

@ -1,10 +0,0 @@
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "bundle-report.html"
})
]
}