Compare commits
2 Commits
b5a1296070
...
da0f4c0ff0
Author | SHA1 | Date | |
---|---|---|---|
da0f4c0ff0 | |||
7871987df1 |
@ -1,27 +1,36 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("space.kscience.gradle.mpp")
|
id("space.kscience.gradle.mpp")
|
||||||
|
alias(spclibs.plugins.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "demo"
|
group = "demo"
|
||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
jvm()
|
// jvm()
|
||||||
js {
|
js {
|
||||||
browser {
|
browser {
|
||||||
binaries.executable()
|
binaries.executable()
|
||||||
|
commonWebpackConfig{
|
||||||
|
cssSupport{
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
scssSupport{
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
sourceMaps = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.visionforgeSolid)
|
implementation(projects.visionforgeSolid)
|
||||||
implementation(projects.visionforgeGdml)
|
implementation(projects.visionforgeGdml)
|
||||||
}
|
}
|
||||||
jvmMain {
|
// jvmMain {
|
||||||
// implementation(project(":visionforge-fx"))
|
//// implementation(project(":visionforge-fx"))
|
||||||
implementation(spclibs.logback.classic)
|
// implementation(spclibs.logback.classic)
|
||||||
}
|
// }
|
||||||
jsMain {
|
jsMain {
|
||||||
implementation(projects.visionforgeThreejs)
|
implementation(projects.visionforgeThreejs)
|
||||||
implementation(npm("react-file-drop", "3.0.6"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
package space.kscience.visionforge.gdml.demo
|
package space.kscience.visionforge.gdml.demo
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import app.softwork.bootstrapcompose.Container
|
||||||
|
import app.softwork.bootstrapcompose.Icon
|
||||||
import org.jetbrains.compose.web.ExperimentalComposeWebApi
|
import org.jetbrains.compose.web.ExperimentalComposeWebApi
|
||||||
import org.jetbrains.compose.web.attributes.InputType
|
import org.jetbrains.compose.web.attributes.InputType
|
||||||
import org.jetbrains.compose.web.attributes.name
|
import org.jetbrains.compose.web.attributes.name
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
||||||
import org.jetbrains.compose.web.dom.Div
|
|
||||||
import org.jetbrains.compose.web.dom.I
|
|
||||||
import org.jetbrains.compose.web.dom.Input
|
import org.jetbrains.compose.web.dom.Input
|
||||||
import org.jetbrains.compose.web.dom.Text
|
import org.jetbrains.compose.web.dom.Text
|
||||||
import org.w3c.files.FileList
|
import org.w3c.files.FileList
|
||||||
@ -22,7 +22,7 @@ fun FileDrop(
|
|||||||
) {
|
) {
|
||||||
var dragOver by remember { mutableStateOf(false) }
|
var dragOver by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Div({
|
Container(attrs = {
|
||||||
id("dropzone")
|
id("dropzone")
|
||||||
style {
|
style {
|
||||||
border(
|
border(
|
||||||
@ -69,8 +69,7 @@ fun FileDrop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
Icon("cloud-upload"){ classes("dropzone-icon") }
|
||||||
I({ classes("bi", "bi-cloud-upload", "dropzone-icon") })
|
|
||||||
Text(title)
|
Text(title)
|
||||||
Input(type = InputType.File, attrs = {
|
Input(type = InputType.File, attrs = {
|
||||||
style {
|
style {
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.*
|
|||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
||||||
import org.jetbrains.compose.web.dom.Div
|
import org.jetbrains.compose.web.dom.Div
|
||||||
import org.jetbrains.compose.web.dom.H2
|
import org.jetbrains.compose.web.dom.P
|
||||||
import org.jetbrains.compose.web.dom.Text
|
import org.jetbrains.compose.web.dom.Text
|
||||||
import org.w3c.files.File
|
import org.w3c.files.File
|
||||||
import org.w3c.files.FileReader
|
import org.w3c.files.FileReader
|
||||||
@ -66,7 +66,7 @@ fun GDMLApp(solids: Solids, initialVision: Solid?, selected: Name? = null) {
|
|||||||
}) {
|
}) {
|
||||||
ThreeView(solids, vision, selected) {
|
ThreeView(solids, vision, selected) {
|
||||||
Tab("Load") {
|
Tab("Load") {
|
||||||
H2 {
|
P {
|
||||||
Text("Drag and drop .gdml or .json VisionForge files here")
|
Text("Drag and drop .gdml or .json VisionForge files here")
|
||||||
}
|
}
|
||||||
FileDrop("(drag file here)") { files ->
|
FileDrop("(drag file here)") { files ->
|
||||||
|
@ -47,8 +47,7 @@ private class JsPlaygroundApp : Application {
|
|||||||
width(100.vw)
|
width(100.vw)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Tabs {
|
Tabs("gravity") {
|
||||||
active = "gravity"
|
|
||||||
Tab("gravity") {
|
Tab("gravity") {
|
||||||
GravityDemo(solids, client)
|
GravityDemo(solids, client)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ import androidx.compose.runtime.mutableStateListOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import app.softwork.bootstrapcompose.Button
|
import app.softwork.bootstrapcompose.Button
|
||||||
import app.softwork.bootstrapcompose.ButtonGroup
|
import app.softwork.bootstrapcompose.ButtonGroup
|
||||||
|
import app.softwork.bootstrapcompose.Color.Secondary
|
||||||
import app.softwork.bootstrapcompose.Container
|
import app.softwork.bootstrapcompose.Container
|
||||||
|
import app.softwork.bootstrapcompose.Layout.Width
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.coroutines.await
|
import kotlinx.coroutines.await
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -64,7 +66,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
|
|||||||
options = mmOptions,
|
options = mmOptions,
|
||||||
sidebarTabs = {
|
sidebarTabs = {
|
||||||
Tab("Events") {
|
Tab("Events") {
|
||||||
ButtonGroup {
|
ButtonGroup({ Layout.width = Width.Full }) {
|
||||||
Button("Next") {
|
Button("Next") {
|
||||||
solids.context.launch {
|
solids.context.launch {
|
||||||
val event = window.fetch(
|
val event = window.fetch(
|
||||||
@ -83,7 +85,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
|
|||||||
model.displayEvent(event)
|
model.displayEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button("Clear") {
|
Button("Clear", color = Secondary) {
|
||||||
events.clear()
|
events.clear()
|
||||||
model.reset()
|
model.reset()
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,12 @@ import space.kscience.dataforge.meta.Null
|
|||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.html.VisionPage
|
import space.kscience.visionforge.html.VisionPage
|
||||||
|
import space.kscience.visionforge.html.meta
|
||||||
import space.kscience.visionforge.server.close
|
import space.kscience.visionforge.server.close
|
||||||
import space.kscience.visionforge.server.openInBrowser
|
import space.kscience.visionforge.server.openInBrowser
|
||||||
import space.kscience.visionforge.server.visionPage
|
import space.kscience.visionforge.server.visionPage
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
import space.kscience.visionforge.three.threeJsHeader
|
import space.kscience.visionforge.three.threeJsHeader
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@ -47,7 +49,12 @@ fun main() {
|
|||||||
) {
|
) {
|
||||||
div("flex-column") {
|
div("flex-column") {
|
||||||
h1 { +"Satellite detector demo" }
|
h1 { +"Satellite detector demo" }
|
||||||
vision { sat }
|
vision {
|
||||||
|
meta(Canvas3DOptions {
|
||||||
|
controls.enabled = false
|
||||||
|
})
|
||||||
|
sat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
|
|||||||
mesh.scale.z = properties.getValue(VALUE)?.number?.toDouble() ?: 1.0
|
mesh.scale.z = properties.getValue(VALUE)?.number?.toDouble() ?: 1.0
|
||||||
|
|
||||||
//add listener to object properties
|
//add listener to object properties
|
||||||
onPropertyChange { name ->
|
onPropertyChange(three.context) { name ->
|
||||||
when {
|
when {
|
||||||
name == VALUE -> {
|
name == VALUE -> {
|
||||||
val value = properties.getValue(VALUE)?.int ?: 0
|
val value = properties.getValue(VALUE)?.int ?: 0
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import org.jetbrains.compose.web.css.Style
|
||||||
|
import org.jetbrains.compose.web.dom.DOMScope
|
||||||
|
import org.jetbrains.compose.web.renderComposable
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.visionforge.ElementVisionRenderer
|
||||||
|
import space.kscience.visionforge.Vision
|
||||||
|
import space.kscience.visionforge.VisionClient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [ElementVisionRenderer] that could be used directly in Compose-html as well as a stand-alone renderer
|
||||||
|
*/
|
||||||
|
public interface ComposeVisionRenderer: ElementVisionRenderer {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun DOMScope<Element>.render(client: VisionClient, name: Name, vision: Vision, meta: Meta)
|
||||||
|
|
||||||
|
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
|
||||||
|
renderComposable(element) {
|
||||||
|
Style(VisionForgeStyles)
|
||||||
|
render(client, name, vision, meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import org.jetbrains.compose.web.dom.Li
|
import org.jetbrains.compose.web.dom.*
|
||||||
import org.jetbrains.compose.web.dom.Nav
|
|
||||||
import org.jetbrains.compose.web.dom.Ol
|
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
import space.kscience.dataforge.names.length
|
import space.kscience.dataforge.names.length
|
||||||
@ -17,7 +14,7 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
|||||||
classes("breadcrumb")
|
classes("breadcrumb")
|
||||||
style {
|
style {
|
||||||
property("--bs-breadcrumb-divider", "'.'")
|
property("--bs-breadcrumb-divider", "'.'")
|
||||||
property("--bs-breadcrumb-item-padding-x",".1rem")
|
property("--bs-breadcrumb-item-padding-x", ".1rem")
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Li({
|
Li({
|
||||||
@ -26,7 +23,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
|||||||
link(Name.EMPTY)
|
link(Name.EMPTY)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text("\u2302")
|
A("#") {
|
||||||
|
Text("\u2302")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
@ -41,7 +40,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
|||||||
link(fullName)
|
link(fullName)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(token.toString())
|
A("#") {
|
||||||
|
Text(token.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,9 @@ import space.kscience.visionforge.hidden
|
|||||||
* The display state of a property
|
* The display state of a property
|
||||||
*/
|
*/
|
||||||
public sealed class EditorPropertyState {
|
public sealed class EditorPropertyState {
|
||||||
public object Defined : EditorPropertyState()
|
public data object Defined : EditorPropertyState()
|
||||||
public class Default(public val source: String = "unknown") : EditorPropertyState()
|
public data class Default(public val source: String = "unknown") : EditorPropertyState()
|
||||||
public object Undefined : EditorPropertyState()
|
public data object Undefined : EditorPropertyState()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ public class TabsBuilder {
|
|||||||
@Composable
|
@Composable
|
||||||
public fun Tab(
|
public fun Tab(
|
||||||
key: String,
|
key: String,
|
||||||
label: ContentBuilder<HTMLAnchorElement> = { Text(key) },
|
label: ContentBuilder<HTMLAnchorElement> = { A("#") { Text(key) } },
|
||||||
disabled: Boolean = false,
|
disabled: Boolean = false,
|
||||||
content: ContentBuilder<HTMLDivElement>,
|
content: ContentBuilder<HTMLDivElement>,
|
||||||
) {
|
) {
|
||||||
|
@ -10,7 +10,7 @@ public object TreeStyles : StyleSheet(VisionForgeStyles) {
|
|||||||
* Remove default bullets
|
* Remove default bullets
|
||||||
*/
|
*/
|
||||||
public val tree: String by style {
|
public val tree: String by style {
|
||||||
paddingLeft(5.px)
|
paddingLeft(10.px)
|
||||||
marginLeft(0.px)
|
marginLeft(0.px)
|
||||||
listStyleType("none")
|
listStyleType("none")
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,14 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.uuid.UUID
|
||||||
|
import kotlinx.uuid.generateUUID
|
||||||
import org.jetbrains.compose.web.attributes.*
|
import org.jetbrains.compose.web.attributes.*
|
||||||
import org.jetbrains.compose.web.dom.Input
|
import org.jetbrains.compose.web.dom.*
|
||||||
import org.jetbrains.compose.web.dom.Option
|
|
||||||
import org.jetbrains.compose.web.dom.Select
|
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
import org.w3c.dom.HTMLOptionElement
|
import org.w3c.dom.HTMLOptionElement
|
||||||
import org.w3c.dom.asList
|
import org.w3c.dom.asList
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
import space.kscience.dataforge.meta.descriptors.ValueRestriction
|
|
||||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.widgetType
|
import space.kscience.visionforge.widgetType
|
||||||
@ -47,20 +45,31 @@ public fun BooleanValueChooser(
|
|||||||
value: Value?,
|
value: Value?,
|
||||||
onValueChange: (Value?) -> Unit,
|
onValueChange: (Value?) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val uid = remember { "checkbox[${UUID.generateUUID().toString(false)}]" }
|
||||||
var innerValue by remember(value, descriptor) {
|
var innerValue by remember(value, descriptor) {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
value?.boolean ?: descriptor?.defaultValue?.boolean
|
value?.boolean ?: descriptor?.defaultValue?.boolean
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Input(type = InputType.Checkbox) {
|
Input(type = InputType.Checkbox) {
|
||||||
classes("w-100")
|
classes("btn-check")
|
||||||
checked(innerValue ?: false)
|
checked(innerValue ?: false)
|
||||||
|
autoComplete(AutoComplete.off)
|
||||||
|
id(uid)
|
||||||
|
|
||||||
onInput { event ->
|
onInput { event ->
|
||||||
innerValue = event.value
|
innerValue = event.value
|
||||||
onValueChange(event.value.asValue())
|
onValueChange(event.value.asValue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Label(uid, attrs = { classes("btn", "btn-sm", "btn-outline-secondary", "w-100") }) {
|
||||||
|
if (innerValue == true) {
|
||||||
|
Text("On")
|
||||||
|
} else {
|
||||||
|
Text("Off")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -70,7 +79,7 @@ public fun NumberValueChooser(
|
|||||||
value: Value?,
|
value: Value?,
|
||||||
onValueChange: (Value?) -> Unit,
|
onValueChange: (Value?) -> Unit,
|
||||||
) {
|
) {
|
||||||
var innerValue by remember(value, descriptor) { mutableStateOf(value?.number) }
|
var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) }
|
||||||
Input(type = InputType.Number) {
|
Input(type = InputType.Number) {
|
||||||
classes("w-100")
|
classes("w-100")
|
||||||
|
|
||||||
@ -159,6 +168,7 @@ public fun MultiSelectChooser(
|
|||||||
onValueChange: (Value?) -> Unit,
|
onValueChange: (Value?) -> Unit,
|
||||||
) {
|
) {
|
||||||
Select({
|
Select({
|
||||||
|
classes("w-100","form-select")
|
||||||
onChange { event ->
|
onChange { event ->
|
||||||
val newSelected = event.target.selectedOptions.asList()
|
val newSelected = event.target.selectedOptions.asList()
|
||||||
.map { (it as HTMLOptionElement).value.asValue() }
|
.map { (it as HTMLOptionElement).value.asValue() }
|
||||||
@ -184,39 +194,18 @@ public fun RangeValueChooser(
|
|||||||
value: Value?,
|
value: Value?,
|
||||||
onValueChange: (Value?) -> Unit,
|
onValueChange: (Value?) -> Unit,
|
||||||
) {
|
) {
|
||||||
var innerValue by remember(value, descriptor) { mutableStateOf(value?.double) }
|
var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) }
|
||||||
var rangeDisabled: Boolean by remember { mutableStateOf(state != EditorPropertyState.Defined) }
|
|
||||||
|
|
||||||
|
|
||||||
FlexRow {
|
|
||||||
if (descriptor?.valueRestriction != ValueRestriction.REQUIRED) {
|
|
||||||
Input(type = InputType.Checkbox) {
|
|
||||||
if (!rangeDisabled) defaultChecked()
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
val checkBoxValue = it.target.checked
|
|
||||||
rangeDisabled = !checkBoxValue
|
|
||||||
onValueChange(
|
|
||||||
if (!checkBoxValue) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
innerValue?.asValue()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Input(type = InputType.Range) {
|
Input(type = InputType.Range) {
|
||||||
classes("w-100")
|
classes("w-100", "form-range")
|
||||||
|
|
||||||
if (rangeDisabled) disabled()
|
|
||||||
value(innerValue?.toString() ?: "")
|
value(innerValue?.toString() ?: "")
|
||||||
onChange {
|
onInput { event ->
|
||||||
val newValue = it.target.value
|
innerValue = event.value
|
||||||
onValueChange(newValue.toDoubleOrNull()?.asValue())
|
}
|
||||||
innerValue = newValue.toDoubleOrNull()
|
onChange { event ->
|
||||||
|
innerValue = event.value
|
||||||
|
onValueChange(innerValue?.asValue())
|
||||||
}
|
}
|
||||||
descriptor?.attributes?.get("min").string?.let {
|
descriptor?.attributes?.get("min").string?.let {
|
||||||
min(it)
|
min(it)
|
||||||
|
@ -79,8 +79,8 @@ public var Vision.visible: Boolean?
|
|||||||
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
|
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
|
||||||
*/
|
*/
|
||||||
public fun Vision.onPropertyChange(
|
public fun Vision.onPropertyChange(
|
||||||
scope: CoroutineScope? = manager?.context,
|
scope: CoroutineScope,
|
||||||
callback: suspend (Name) -> Unit,
|
callback: suspend (Name) -> Unit,
|
||||||
): Job = properties.changes.onEach {
|
): Job = properties.changes.onEach {
|
||||||
callback(it)
|
callback(it)
|
||||||
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
|
}.launchIn(scope)
|
@ -4,10 +4,7 @@ import kotlinx.html.*
|
|||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.ContextAware
|
import space.kscience.dataforge.context.ContextAware
|
||||||
import space.kscience.dataforge.context.PluginFactory
|
import space.kscience.dataforge.context.PluginFactory
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.meta.MetaSerializer
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
|
||||||
import space.kscience.dataforge.meta.isEmpty
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
import space.kscience.dataforge.names.asName
|
import space.kscience.dataforge.names.asName
|
||||||
@ -46,9 +43,14 @@ public class VisionOutput @PublishedApi internal constructor(override val contex
|
|||||||
newContext.visionManager
|
newContext.visionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun meta(block: MutableMeta.() -> Unit) {
|
}
|
||||||
this.meta = Meta(block)
|
|
||||||
}
|
public inline fun VisionOutput.meta(block: MutableMeta.() -> Unit) {
|
||||||
|
this.meta = Meta(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun VisionOutput.meta(metaRepr: MetaRepr) {
|
||||||
|
this.meta = metaRepr.toMeta()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,7 @@ public fun Vision.useProperty(
|
|||||||
propertyName: Name,
|
propertyName: Name,
|
||||||
inherit: Boolean? = null,
|
inherit: Boolean? = null,
|
||||||
includeStyles: Boolean? = null,
|
includeStyles: Boolean? = null,
|
||||||
scope: CoroutineScope? = manager?.context,
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
||||||
callback: (Meta) -> Unit,
|
callback: (Meta) -> Unit,
|
||||||
): Job {
|
): Job {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
@ -28,14 +28,14 @@ public fun Vision.useProperty(
|
|||||||
if (name.startsWith(propertyName)) {
|
if (name.startsWith(propertyName)) {
|
||||||
callback(properties.get(propertyName, inherit, includeStyles))
|
callback(properties.get(propertyName, inherit, includeStyles))
|
||||||
}
|
}
|
||||||
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Vision.useProperty(
|
public fun Vision.useProperty(
|
||||||
propertyName: String,
|
propertyName: String,
|
||||||
inherit: Boolean? = null,
|
inherit: Boolean? = null,
|
||||||
includeStyles: Boolean? = null,
|
includeStyles: Boolean? = null,
|
||||||
scope: CoroutineScope? = manager?.context,
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
||||||
callback: (Meta) -> Unit,
|
callback: (Meta) -> Unit,
|
||||||
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ public class CanvasSize : Scheme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Canvas3DOptions : Scheme() {
|
public class Canvas3DOptions : Scheme() {
|
||||||
|
public var canvasName: String by string("vision")
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
public var axes: AxesScheme by spec(AxesScheme)
|
public var axes: AxesScheme by spec(AxesScheme)
|
||||||
public var camera: CameraScheme by spec(CameraScheme)
|
public var camera: CameraScheme by spec(CameraScheme)
|
||||||
|
@ -52,7 +52,7 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory
|
|||||||
applyProperties(vision)
|
applyProperties(vision)
|
||||||
|
|
||||||
if (observe) {
|
if (observe) {
|
||||||
vision.onPropertyChange { name ->
|
vision.onPropertyChange(three.context) { name ->
|
||||||
when {
|
when {
|
||||||
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||||
name.startsWith(EDGES_KEY) -> applyEdges(vision)
|
name.startsWith(EDGES_KEY) -> applyEdges(vision)
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
package space.kscience.visionforge.solid.three
|
package space.kscience.visionforge.solid.three
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.jetbrains.compose.web.renderComposable
|
import org.jetbrains.compose.web.dom.DOMScope
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.visionforge.*
|
import space.kscience.visionforge.*
|
||||||
|
import space.kscience.visionforge.compose.ComposeVisionRenderer
|
||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
import space.kscience.visionforge.solid.three.compose.ThreeView
|
import space.kscience.visionforge.solid.three.compose.ThreeView
|
||||||
@ -21,7 +23,7 @@ import three.objects.Group as ThreeGroup
|
|||||||
/**
|
/**
|
||||||
* A plugin that handles Three Object3D representation of Visions.
|
* A plugin that handles Three Object3D representation of Visions.
|
||||||
*/
|
*/
|
||||||
public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
public class ThreePlugin : AbstractPlugin(), ComposeVisionRenderer {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
|
|
||||||
public val solids: Solids by require(Solids)
|
public val solids: Solids by require(Solids)
|
||||||
@ -75,7 +77,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
// disable tracking changes for statics
|
// disable tracking changes for statics
|
||||||
group[token] = object3D
|
group[token] = object3D
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.error(ex) { "Failed to render $child" }
|
logger.error(ex) { "Failed to render vision with token $token and type ${child::class}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +117,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
val object3D = buildObject3D(child)
|
val object3D = buildObject3D(child)
|
||||||
set(childName, object3D)
|
set(childName, object3D)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.error(ex) { "Failed to render $child" }
|
logger.error(ex) { "Failed to render vision with name $childName" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(context)
|
}.launchIn(context)
|
||||||
@ -185,11 +187,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
|||||||
render(vision)
|
render(vision)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
|
@Composable
|
||||||
|
override fun DOMScope<Element>.render(client: VisionClient, name: Name, vision: Vision, meta: Meta) {
|
||||||
require(vision is Solid) { "Expected Solid but found ${vision::class}" }
|
require(vision is Solid) { "Expected Solid but found ${vision::class}" }
|
||||||
renderComposable(element) {
|
ThreeView(solids, vision, null, Canvas3DOptions.read(meta))
|
||||||
ThreeView(solids, vision, null, Canvas3DOptions.read(meta))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object : PluginFactory<ThreePlugin> {
|
public companion object : PluginFactory<ThreePlugin> {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package space.kscience.visionforge.solid.three.compose
|
package space.kscience.visionforge.solid.three.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import app.softwork.bootstrapcompose.Button
|
||||||
|
import app.softwork.bootstrapcompose.Color.Info
|
||||||
import app.softwork.bootstrapcompose.Column
|
import app.softwork.bootstrapcompose.Column
|
||||||
import app.softwork.bootstrapcompose.Layout.Height
|
import app.softwork.bootstrapcompose.Layout.Height
|
||||||
import app.softwork.bootstrapcompose.Row
|
import app.softwork.bootstrapcompose.Layout.Width
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.dom.Hr
|
||||||
import org.jetbrains.compose.web.dom.Button
|
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
import org.w3c.files.Blob
|
import org.w3c.files.Blob
|
||||||
import org.w3c.files.BlobPropertyBag
|
import org.w3c.files.BlobPropertyBag
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
@ -22,32 +22,16 @@ internal fun CanvasControls(
|
|||||||
options: Canvas3DOptions,
|
options: Canvas3DOptions,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Row(attrs = {
|
vision?.let { vision ->
|
||||||
style {
|
Button("Export", color = Info, styling = { Layout.width = Width.Full }) {
|
||||||
border {
|
val json = vision.encodeToString()
|
||||||
width(1.px)
|
|
||||||
style(LineStyle.Solid)
|
|
||||||
color(Color("blue"))
|
|
||||||
}
|
|
||||||
padding(4.px)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
vision?.let { vision ->
|
|
||||||
Button({
|
|
||||||
onClick { event ->
|
|
||||||
val json = vision.encodeToString()
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
|
val fileSaver = kotlinext.js.require<dynamic>("file-saver")
|
||||||
val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
|
val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
|
||||||
fileSaver.saveAs(blob, "object.json") as Unit
|
fileSaver.saveAs(blob, "${options.canvasName}.json") as Unit
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text("Export")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Hr()
|
||||||
PropertyEditor(
|
PropertyEditor(
|
||||||
scope = vision?.manager?.context ?: Global,
|
scope = vision?.manager?.context ?: Global,
|
||||||
properties = options.meta,
|
properties = options.meta,
|
||||||
|
@ -7,7 +7,6 @@ import app.softwork.bootstrapcompose.Layout.Height
|
|||||||
import app.softwork.bootstrapcompose.Layout.Width
|
import app.softwork.bootstrapcompose.Layout.Width
|
||||||
import app.softwork.bootstrapcompose.Row
|
import app.softwork.bootstrapcompose.Row
|
||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
import org.jetbrains.compose.web.ExperimentalComposeWebApi
|
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
||||||
import org.jetbrains.compose.web.dom.*
|
import org.jetbrains.compose.web.dom.*
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -104,32 +103,13 @@ public fun ThreeView(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (solid == null) {
|
if (solid == null) {
|
||||||
Div({
|
Div({ classes("d-flex", "justify-content-center") }) {
|
||||||
style {
|
Div({
|
||||||
position(Position.Fixed)
|
classes("spinner-border")
|
||||||
width(100.percent)
|
attr("role", "status")
|
||||||
height(100.percent)
|
}) {
|
||||||
zIndex(1000)
|
Span({ classes("visually-hidden") }) {
|
||||||
top(40.percent)
|
Text("Loading...")
|
||||||
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") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,33 +124,17 @@ public fun ThreeView(
|
|||||||
else -> (solid as? SolidGroup)?.get(it)
|
else -> (solid as? SolidGroup)?.get(it)
|
||||||
}
|
}
|
||||||
}?.let { vision ->
|
}?.let { vision ->
|
||||||
Card(
|
Card(attrs = {
|
||||||
attrs = {
|
style {
|
||||||
style {
|
position(Position.Absolute)
|
||||||
position(Position.Absolute)
|
top(10.px)
|
||||||
top(5.px)
|
right(10.px)
|
||||||
right(5.px)
|
width(450.px)
|
||||||
width(450.px)
|
overflowY("auto")
|
||||||
overflowY("auto")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
headerAttrs = {
|
|
||||||
style {
|
|
||||||
alignItems(AlignItems.Center)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
header = {
|
|
||||||
NameCrumbs(selected) { selected = it }
|
|
||||||
},
|
|
||||||
footer = {
|
|
||||||
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
|
|
||||||
P {
|
|
||||||
B { Text("Styles: ") }
|
|
||||||
Text(styles.joinToString(separator = ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
}) {
|
||||||
|
NameCrumbs(selected) { selected = it }
|
||||||
|
Hr()
|
||||||
PropertyEditor(
|
PropertyEditor(
|
||||||
scope = solids.context,
|
scope = solids.context,
|
||||||
rootMeta = vision.properties.root(),
|
rootMeta = vision.properties.root(),
|
||||||
@ -187,6 +151,13 @@ public fun ThreeView(
|
|||||||
updates = vision.properties.changes,
|
updates = vision.properties.changes,
|
||||||
rootDescriptor = vision.descriptor
|
rootDescriptor = vision.descriptor
|
||||||
)
|
)
|
||||||
|
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
|
||||||
|
Hr()
|
||||||
|
P {
|
||||||
|
B { Text("Styles: ") }
|
||||||
|
Text(styles.joinToString(separator = ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +178,13 @@ public fun ThreeView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
ThreeControls(solid, optionsSnapshot, selected, onSelect = { selected = it }, tabBuilder = sidebarTabs)
|
ThreeControls(
|
||||||
|
solid,
|
||||||
|
optionsSnapshot,
|
||||||
|
selected,
|
||||||
|
onSelect = { selected = it },
|
||||||
|
tabBuilder = sidebarTabs
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,21 +6,33 @@ plugins {
|
|||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
fullStack("js/visionforge-three.js")
|
fullStack(
|
||||||
|
bundleName = "js/visionforge-three.js",
|
||||||
|
browserConfig = {
|
||||||
|
webpackTask {
|
||||||
|
cssSupport {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
scssSupport {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
commonMain {
|
commonMain {
|
||||||
api(projects.visionforgeSolid)
|
api(projects.visionforgeSolid)
|
||||||
api(projects.visionforgeComposeHtml)
|
api(projects.visionforgeComposeHtml)
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmMain{
|
jvmMain {
|
||||||
api(projects.visionforgeServer)
|
api(projects.visionforgeServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsMain{
|
jsMain {
|
||||||
api(projects.visionforgeThreejs)
|
api(projects.visionforgeThreejs)
|
||||||
implementation(npm("file-saver","2.0.5"))
|
implementation(npm("file-saver", "2.0.5"))
|
||||||
implementation(npm("@types/file-saver", "2.0.7"))
|
implementation(npm("@types/file-saver", "2.0.7"))
|
||||||
compileOnly(npm("webpack-bundle-analyzer","4.5.0"))
|
compileOnly(npm("webpack-bundle-analyzer", "4.5.0"))
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user