Merge branch 'dev' into dev
This commit is contained in:
commit
350d7c6634
33
.github/workflows/build.yml
vendored
Normal file
33
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Gradle build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev, master ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: DeLaGuardo/setup-graalvm@4.0
|
||||
with:
|
||||
graalvm: 21.2.0
|
||||
java: java11
|
||||
arch: amd64
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.konan
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- run: ./gradlew build --build-cache --no-daemon --stacktrace
|
19
.github/workflows/gradle.yml
vendored
19
.github/workflows/gradle.yml
vendored
@ -1,19 +0,0 @@
|
||||
name: Gradle build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x ./gradlew
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
28
.github/workflows/pages.yml
vendored
Normal file
28
.github/workflows/pages.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: Dokka publication
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: DeLaGuardo/setup-graalvm@4.0
|
||||
with:
|
||||
graalvm: 21.2.0
|
||||
java: java11
|
||||
arch: amd64
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- run: ./gradlew dokkaHtmlMultiModule --build-cache --no-daemon --no-parallel --stacktrace
|
||||
- uses: JamesIves/github-pages-deploy-action@4.1.0
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: build/dokka/htmlMultiModule
|
@ -1,26 +1,21 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.gradle.project")
|
||||
|
||||
//Override kotlin version
|
||||
// val kotlinVersion = "1.5.20-RC"
|
||||
// kotlin("multiplatform") version(kotlinVersion) apply false
|
||||
// kotlin("jvm") version(kotlinVersion) apply false
|
||||
// kotlin("js") version(kotlinVersion) apply false
|
||||
// kotlin("multiplatform") version "1.5.30-RC" apply false
|
||||
}
|
||||
|
||||
val dataforgeVersion by extra("0.4.3")
|
||||
val dataforgeVersion by extra("0.5.1")
|
||||
val fxVersion by extra("11")
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven("https://repo.kotlin.link")
|
||||
maven("https://maven.jzy3d.org/releases")
|
||||
}
|
||||
|
||||
group = "space.kscience"
|
||||
version = "0.2.0-dev-22"
|
||||
version = "0.2.0-dev-23"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@ -29,7 +24,7 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
ksciencePublish{
|
||||
ksciencePublish {
|
||||
github("visionforge")
|
||||
space()
|
||||
sonatype()
|
||||
|
@ -16,7 +16,7 @@ kotlin {
|
||||
jvm {
|
||||
withJava()
|
||||
}
|
||||
js{
|
||||
js {
|
||||
useCommonJs()
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
@ -34,6 +34,7 @@ kotlin {
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-fx"))
|
||||
implementation("ch.qos.logback:logback-classic:1.2.5")
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
@ -53,5 +54,5 @@ application {
|
||||
val convertGdmlToJson by tasks.creating(JavaExec::class) {
|
||||
group = "application"
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
main = "space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt"
|
||||
mainClass.set("space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt")
|
||||
}
|
@ -1,32 +1,41 @@
|
||||
package space.kscience.visionforge.gdml
|
||||
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.string
|
||||
import space.kscience.gdml.GdmlShowCase
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.get
|
||||
import space.kscience.visionforge.setProperty
|
||||
import space.kscience.visionforge.solid.Solid
|
||||
import space.kscience.visionforge.solid.SolidMaterial
|
||||
import space.kscience.visionforge.solid.material
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class GDMLVisionTest {
|
||||
private val cubes = GdmlShowCase.cubes().toVision()
|
||||
|
||||
// @Test
|
||||
// fun testCubesStyles(){
|
||||
// val cubes = gdml.toVision()
|
||||
// val segment = cubes["composite000.segment_0".toName()] as Solid
|
||||
// println(segment.styles)
|
||||
// println(segment.material)
|
||||
// }
|
||||
@Test
|
||||
fun testCubesStyles(){
|
||||
val segment = cubes["composite-000.segment-0"] as Solid
|
||||
println(segment.computeProperties().getValue(Vision.STYLE_KEY))
|
||||
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
|
||||
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
|
||||
|
||||
println(segment.material?.meta)
|
||||
|
||||
//println(Solids.encodeToString(cubes))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testPrototypeProperty() {
|
||||
val vision = GdmlShowCase.cubes().toVision()
|
||||
val child = vision["composite-000.segment-0".toName()]
|
||||
val child = cubes[Name.of("composite-000","segment-0")]
|
||||
assertNotNull(child)
|
||||
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
|
||||
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
|
||||
assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string)
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package space.kscience.visionforge.gdml.demo
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import org.w3c.files.File
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import react.*
|
||||
@ -27,32 +30,43 @@ external interface GDMLAppProps : RProps {
|
||||
@JsExport
|
||||
val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
|
||||
var vision: Solid? by useState { props.vision?.apply { root(visionManager) } }
|
||||
var deferredVision: Deferred<Solid?> by useState {
|
||||
CompletableDeferred(props.vision)
|
||||
}
|
||||
|
||||
fun loadData(name: String, data: String) {
|
||||
val parsedVision = when {
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||
val gdml = Gdml.decodeFromString(data)
|
||||
gdml.toVision().apply {
|
||||
root(visionManager)
|
||||
console.info("Marking layers for file $name")
|
||||
markLayers()
|
||||
fun readFileAsync(file: File): Deferred<Solid?> {
|
||||
val deferred = CompletableDeferred<Solid?>()
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val data = result as String
|
||||
val name = file.name
|
||||
val parsedVision = when {
|
||||
name.endsWith(".gdml") || name.endsWith(".xml") -> {
|
||||
val gdml = Gdml.decodeFromString(data)
|
||||
gdml.toVision().apply {
|
||||
root(visionManager)
|
||||
console.info("Marking layers for file $name")
|
||||
markLayers()
|
||||
}
|
||||
}
|
||||
name.endsWith(".json") -> visionManager.decodeFromString(data)
|
||||
else -> {
|
||||
window.alert("File extension is not recognized: $name")
|
||||
error("File extension is not recognized: $name")
|
||||
}
|
||||
}
|
||||
deferred.complete(parsedVision as? Solid ?: error("Parsed vision is not a solid"))
|
||||
}
|
||||
name.endsWith(".json") -> visionManager.decodeFromString(data)
|
||||
else -> {
|
||||
window.alert("File extension is not recognized: $name")
|
||||
error("File extension is not recognized: $name")
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
|
||||
vision = parsedVision as? Solid ?: error("Parsed vision is not a solid")
|
||||
return deferred
|
||||
}
|
||||
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.solid = vision
|
||||
this.builderOfSolid = deferredVision
|
||||
this.selected = props.selected
|
||||
tab("Load") {
|
||||
h2 {
|
||||
@ -61,13 +75,7 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
fileDrop("(drag file here)") { files ->
|
||||
val file = files?.get(0)
|
||||
if (file != null) {
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val string = result as String
|
||||
loadData(file.name, string)
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
deferredVision = readFileAsync(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,8 @@ import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.gdml.GdmlShowCase
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.describedProperties
|
||||
import space.kscience.visionforge.editor.VisualObjectEditorFragment
|
||||
import space.kscience.visionforge.editor.VisualObjectTreeFragment
|
||||
import space.kscience.visionforge.editor.VisionEditorFragment
|
||||
import space.kscience.visionforge.editor.VisionTreeFragment
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.solid.FX3DPlugin
|
||||
import space.kscience.visionforge.solid.FXCanvas3D
|
||||
@ -29,25 +28,22 @@ class GDMLView : View() {
|
||||
private val visionManager = context.fetch(VisionManager)
|
||||
private val canvas = FXCanvas3D(fx3d)
|
||||
|
||||
private val treeFragment = VisualObjectTreeFragment().apply {
|
||||
private val treeFragment = VisionTreeFragment().apply {
|
||||
this.itemProperty.bind(canvas.rootObjectProperty)
|
||||
}
|
||||
|
||||
private val propertyEditor = VisualObjectEditorFragment {
|
||||
it.describedProperties
|
||||
}.apply {
|
||||
private val propertyEditor = VisionEditorFragment().apply {
|
||||
descriptorProperty.set(SolidMaterial.descriptor)
|
||||
itemProperty.bind(treeFragment.selectedProperty)
|
||||
visionProperty.bind(treeFragment.selectedProperty)
|
||||
}
|
||||
|
||||
|
||||
override val root: Parent = borderpane {
|
||||
top {
|
||||
buttonbar {
|
||||
button("Load GDML/json") {
|
||||
action {
|
||||
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
|
||||
if(file!= null) {
|
||||
if (file != null) {
|
||||
runAsync {
|
||||
visionManager.readFile(file) as Solid
|
||||
} ui {
|
||||
|
@ -1,17 +1,28 @@
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import react.child
|
||||
import react.dom.render
|
||||
import ringui.SmartTabs
|
||||
import ringui.Tab
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.gdml.GdmlShowCase
|
||||
import space.kscience.plotly.models.Trace
|
||||
import space.kscience.plotly.models.appendXY
|
||||
import space.kscience.plotly.scatter
|
||||
import space.kscience.visionforge.Application
|
||||
import space.kscience.visionforge.VisionClient
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.ring.ThreeCanvasWithControls
|
||||
import space.kscience.visionforge.ring.ThreeWithControlsPlugin
|
||||
import space.kscience.visionforge.ring.solid
|
||||
import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.startApplication
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.random.Random
|
||||
|
||||
private class JsPlaygroundApp : Application {
|
||||
|
||||
@ -20,24 +31,124 @@ private class JsPlaygroundApp : Application {
|
||||
val playgroundContext = Context {
|
||||
plugin(ThreeWithControlsPlugin)
|
||||
plugin(VisionClient)
|
||||
plugin(PlotlyPlugin)
|
||||
}
|
||||
|
||||
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
|
||||
|
||||
val visionOfD0 = GdmlShowCase.babyIaxo().toVision()
|
||||
val bouncingSphereTrace = Trace()
|
||||
|
||||
render(element) {
|
||||
styledDiv {
|
||||
css{
|
||||
css {
|
||||
padding(0.pt)
|
||||
margin(0.pt)
|
||||
height = 100.vh
|
||||
width = 100.vw
|
||||
}
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
solid = visionOfD0
|
||||
SmartTabs("gravity") {
|
||||
Tab("gravity") {
|
||||
styledDiv {
|
||||
css {
|
||||
height = 50.vh
|
||||
}
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
solid {
|
||||
sphere(5.0, "ball") {
|
||||
detail = 16
|
||||
color("red")
|
||||
val h = 100.0
|
||||
y = h
|
||||
launch {
|
||||
val g = 10.0
|
||||
val dt = 0.1
|
||||
var time = 0.0
|
||||
var velocity = 0.0
|
||||
while (isActive) {
|
||||
delay(20)
|
||||
time += dt
|
||||
velocity -= g * dt
|
||||
y = y.toDouble() + velocity * dt
|
||||
bouncingSphereTrace.appendXY(time, y)
|
||||
if (y.toDouble() <= 2.5) {
|
||||
//conservation of energy
|
||||
velocity = sqrt(2 * g * h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
box(200, 5, 200, name = "floor") {
|
||||
y = -2.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
height = 40.vh
|
||||
}
|
||||
|
||||
Plotly {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
plot = space.kscience.plotly.Plotly.plot {
|
||||
traces(bouncingSphereTrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab("D0") {
|
||||
// child(ThreeCanvasWithControls) {
|
||||
// attrs {
|
||||
// context = playgroundContext
|
||||
// solid = GdmlShowCase.babyIaxo().toVision()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Tab("spheres") {
|
||||
styledDiv {
|
||||
css {
|
||||
height = 90.vh
|
||||
}
|
||||
child(ThreeCanvasWithControls) {
|
||||
val random = Random(112233)
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
solid {
|
||||
repeat(100) {
|
||||
sphere(5, name = "sphere[$it]") {
|
||||
x = random.nextDouble(-300.0, 300.0)
|
||||
y = random.nextDouble(-300.0, 300.0)
|
||||
z = random.nextDouble(-300.0, 300.0)
|
||||
material {
|
||||
color(random.nextInt())
|
||||
}
|
||||
detail = 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Tab("plotly") {
|
||||
Plotly {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
plot = space.kscience.plotly.Plotly.plot {
|
||||
scatter {
|
||||
x(1, 2, 3)
|
||||
y(5, 8, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
demo/js-playground/src/main/kotlin/plotlyComponent.kt
Normal file
34
demo/js-playground/src/main/kotlin/plotlyComponent.kt
Normal file
@ -0,0 +1,34 @@
|
||||
import kotlinx.css.flex
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RProps
|
||||
import react.functionalComponent
|
||||
import react.useEffect
|
||||
import react.useRef
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.plotly.Plot
|
||||
import space.kscience.plotly.plot
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
external interface PlotlyProps: RProps{
|
||||
var context: Context
|
||||
var plot: Plot?
|
||||
}
|
||||
|
||||
|
||||
val Plotly = functionalComponent<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(it)}
|
||||
}
|
||||
|
||||
styledDiv {
|
||||
css {
|
||||
flex(1.0)
|
||||
}
|
||||
ref = elementRef
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.removeAll
|
||||
import space.kscience.visionforge.root
|
||||
import space.kscience.visionforge.setProperty
|
||||
import space.kscience.visionforge.solid.*
|
||||
import kotlin.math.PI
|
||||
|
||||
@ -37,6 +38,10 @@ class Model(val manager: VisionManager) {
|
||||
|
||||
val root: SolidGroup = SolidGroup().apply {
|
||||
root(this@Model.manager)
|
||||
material {
|
||||
wireframe
|
||||
color("darkgreen")
|
||||
}
|
||||
rotationX = PI / 2
|
||||
group("bottom") {
|
||||
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
|
||||
@ -59,6 +64,7 @@ class Model(val manager: VisionManager) {
|
||||
}
|
||||
|
||||
private fun highlight(pixel: String) {
|
||||
println("highlight $pixel")
|
||||
map[pixel]?.color?.invoke("blue")
|
||||
}
|
||||
|
||||
@ -70,6 +76,7 @@ class Model(val manager: VisionManager) {
|
||||
}
|
||||
|
||||
fun displayEvent(event: Event) {
|
||||
println("Received event: $event")
|
||||
events.add(event)
|
||||
event.hits.forEach {
|
||||
highlight(it)
|
||||
@ -77,6 +84,7 @@ class Model(val manager: VisionManager) {
|
||||
event.track?.let {
|
||||
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
|
||||
thickness = 4
|
||||
color("red")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class SC16(
|
||||
}
|
||||
val offset = Point3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0));
|
||||
val pixelName = "${name}_${index}"
|
||||
SC1(pixelName, center + offset)
|
||||
SC1(pixelName, offset + center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package ru.mipt.npm.muon.monitor
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
@ -23,6 +24,7 @@ import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.visionTree
|
||||
import space.kscience.visionforge.solid.specifications.Camera
|
||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||
import space.kscience.visionforge.solid.three.edges
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import kotlin.math.PI
|
||||
@ -34,6 +36,7 @@ external interface MMAppProps : RProps {
|
||||
var selected: Name?
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@JsExport
|
||||
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
var selected by useState { props.selected }
|
||||
@ -53,7 +56,11 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
}
|
||||
}
|
||||
|
||||
val root = props.model.root
|
||||
val root = useMemo(props.model) {
|
||||
props.model.root.apply {
|
||||
edges()
|
||||
}
|
||||
}
|
||||
|
||||
gridRow {
|
||||
flexColumn {
|
||||
|
@ -7,18 +7,15 @@ import kotlinx.browser.document
|
||||
import react.child
|
||||
import react.dom.render
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.visionforge.Application
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.bootstrap.useBootstrap
|
||||
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||
import space.kscience.visionforge.startApplication
|
||||
|
||||
private class MMDemoApp : Application {
|
||||
|
||||
private val visionManager = Global.fetch(VisionManager)
|
||||
private val model = Model(visionManager)
|
||||
|
||||
private val connection = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer()
|
||||
@ -28,13 +25,18 @@ private class MMDemoApp : Application {
|
||||
override fun start(state: Map<String, Any>) {
|
||||
useBootstrap()
|
||||
|
||||
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
||||
val context = Context("MM-demo"){
|
||||
plugin(ThreePlugin)
|
||||
}
|
||||
val visionManager = context.fetch(VisionManager)
|
||||
|
||||
val context = Context("demo")
|
||||
val model = Model(visionManager)
|
||||
|
||||
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
||||
render(element) {
|
||||
child(MMApp) {
|
||||
attrs {
|
||||
this.model = this@MMDemoApp.model
|
||||
this.model = model
|
||||
this.connection = this@MMDemoApp.connection
|
||||
this.context = context
|
||||
}
|
||||
|
@ -16,10 +16,10 @@ import kotlin.random.Random
|
||||
*/
|
||||
internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
|
||||
// val layer: Layer = findLayer(center.z);
|
||||
private val upLayer =
|
||||
findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0);
|
||||
private val bottomLayer =
|
||||
findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0);
|
||||
private val upLayer = findLayer(sc.center.z + sc.zSize / 2f)
|
||||
//Layer("${name}_up", center.z + zSize / 2.0);
|
||||
private val bottomLayer = findLayer(sc.center.z - sc.zSize / 2f)
|
||||
//Layer("${name}_bottom", center.z - zSize / 2.0);
|
||||
private val centralLayer = findLayer(sc.center.z)
|
||||
|
||||
private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble())
|
||||
@ -115,8 +115,8 @@ internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
|
||||
|
||||
private val auxCache = HashMap<SC1, SC1Aux>()
|
||||
|
||||
fun SC1.isHit(track: Line): Boolean{
|
||||
return auxCache.getOrPut(this){
|
||||
fun SC1.isHit(track: Line): Boolean {
|
||||
return auxCache.getOrPut(this) {
|
||||
SC1Aux(this)
|
||||
}.isHit(track)
|
||||
}
|
@ -20,7 +20,7 @@ kotlin {
|
||||
this.outputFileName = "js/visionforge-playground.js"
|
||||
}
|
||||
commonWebpackConfig {
|
||||
sourceMaps = false
|
||||
sourceMaps = true
|
||||
cssSupport.enabled = false
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ kotlin {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val jsBrowserDistribution by tasks.getting
|
||||
val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution")
|
||||
|
||||
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||
dependsOn(jsBrowserDistribution)
|
||||
@ -67,6 +67,7 @@ kotlin {
|
||||
val jvmMain by getting{
|
||||
dependencies {
|
||||
api(project(":visionforge-server"))
|
||||
api(project(":visionforge-markdown"))
|
||||
api("ch.qos.logback:logback-classic:1.2.3")
|
||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||
}
|
||||
|
91
demo/playground/src/jvmMain/kotlin/markdownDemo.kt
Normal file
91
demo/playground/src/jvmMain/kotlin/markdownDemo.kt
Normal file
@ -0,0 +1,91 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.plotly.layout
|
||||
import space.kscience.plotly.models.ScatterMode
|
||||
import space.kscience.plotly.models.TextPosition
|
||||
import space.kscience.plotly.scatter
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.markup.markdown
|
||||
import space.kscience.visionforge.plotly.PlotlyPlugin
|
||||
import space.kscience.visionforge.plotly.plotly
|
||||
import space.kscience.visionforge.solid.*
|
||||
|
||||
fun main() {
|
||||
val context = Context {
|
||||
plugin(Solids)
|
||||
plugin(PlotlyPlugin)
|
||||
}
|
||||
|
||||
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
|
||||
markdown {
|
||||
//language=markdown
|
||||
"""
|
||||
# Section
|
||||
|
||||
**TBD**
|
||||
|
||||
## Subsection
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
div {
|
||||
h1 { +"Canvas" }
|
||||
vision("canvas") {
|
||||
solid {
|
||||
box(100, 100, 100)
|
||||
material {
|
||||
emissiveColor("red")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vision("plot") {
|
||||
plotly {
|
||||
scatter {
|
||||
x(1, 2, 3, 4)
|
||||
y(10, 15, 13, 17)
|
||||
mode = ScatterMode.markers
|
||||
name = "Team A"
|
||||
text("A-1", "A-2", "A-3", "A-4", "A-5")
|
||||
textposition = TextPosition.`top center`
|
||||
textfont {
|
||||
family = "Raleway, sans-serif"
|
||||
}
|
||||
marker { size = 12 }
|
||||
}
|
||||
|
||||
scatter {
|
||||
x(2, 3, 4, 5)
|
||||
y(10, 15, 13, 17)
|
||||
mode = ScatterMode.lines
|
||||
name = "Team B"
|
||||
text("B-a", "B-b", "B-c", "B-d", "B-e")
|
||||
textposition = TextPosition.`bottom center`
|
||||
textfont {
|
||||
family = "Times New Roman"
|
||||
}
|
||||
marker { size = 12 }
|
||||
}
|
||||
|
||||
layout {
|
||||
title = "Data Labels Hover"
|
||||
xaxis {
|
||||
range(0.75..5.25)
|
||||
}
|
||||
legend {
|
||||
y = 0.5
|
||||
font {
|
||||
family = "Arial, sans-serif"
|
||||
size = 20
|
||||
color("grey")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ fun main() {
|
||||
|
||||
context.makeVisionFile(
|
||||
Paths.get("randomSpheres.html"),
|
||||
resourceLocation = ResourceLocation.EMBED
|
||||
resourceLocation = ResourceLocation.SYSTEM
|
||||
) {
|
||||
h1 { +"Happy new year!" }
|
||||
div {
|
||||
|
@ -26,7 +26,7 @@ public fun Context.makeVisionFile(
|
||||
content: VisionTagConsumer<*>.() -> Unit
|
||||
): Unit {
|
||||
val actualPath = page(title, content = content).makeFile(path) { actualPath ->
|
||||
mapOf("threeJs" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
|
||||
mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
|
||||
}
|
||||
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.solid.box
|
||||
import space.kscience.visionforge.solid.solid
|
||||
import space.kscience.visionforge.solid.*
|
||||
|
||||
fun main() {
|
||||
val context = Context {
|
||||
@ -15,6 +13,9 @@ fun main() {
|
||||
vision("canvas") {
|
||||
solid {
|
||||
box(100, 100, 100)
|
||||
material {
|
||||
emissiveColor("red")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ internal fun visionOfSatellite(
|
||||
ySegmentSize: Number = xSegmentSize,
|
||||
fiberDiameter: Number = 1.0,
|
||||
): SolidGroup = SolidGroup {
|
||||
color("darkgreen")
|
||||
val transparent by style {
|
||||
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.three.server.*
|
||||
import space.kscience.visionforge.visionManager
|
||||
@ -42,7 +42,7 @@ fun main() {
|
||||
val randomLayer = Random.nextInt(1, 11)
|
||||
val randomI = Random.nextInt(1, 4)
|
||||
val randomJ = Random.nextInt(1, 4)
|
||||
val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName()
|
||||
val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]")
|
||||
val targetVision = sat[target] as Solid
|
||||
targetVision.color("red")
|
||||
delay(1000)
|
||||
|
@ -1,7 +1,8 @@
|
||||
package space.kscience.visionforge
|
||||
package space.kscience.visionforge.solid.demo
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
|
||||
public interface VisionLayout<in V: Vision> {
|
||||
public fun render(name: Name, vision: V, meta: Meta = Meta.EMPTY)
|
@ -1,14 +1,10 @@
|
||||
package space.kscience.visionforge.solid.demo
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.invoke
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.VisionLayout
|
||||
import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||
import space.kscience.visionforge.visible
|
||||
@ -23,7 +19,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
|
||||
"title" put title
|
||||
}
|
||||
val vision = SolidGroup(block)
|
||||
render(name.toName(), vision)
|
||||
render(Name.parse(name), vision, meta)
|
||||
}
|
||||
|
||||
val canvasOptions = Canvas3DOptions {
|
||||
@ -40,6 +36,7 @@ val canvasOptions = Canvas3DOptions {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun VisionLayout<Solid>.showcase() {
|
||||
demo("shapes", "Basic shapes") {
|
||||
box(100.0, 100.0, 100.0) {
|
||||
@ -77,7 +74,7 @@ fun VisionLayout<Solid>.showcase() {
|
||||
//override color for this cube
|
||||
color(1530)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
while (isActive) {
|
||||
delay(500)
|
||||
visible = !(visible ?: false)
|
||||
@ -86,7 +83,7 @@ fun VisionLayout<Solid>.showcase() {
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
val random = Random(111)
|
||||
while (isActive) {
|
||||
delay(1000)
|
||||
|
@ -15,7 +15,6 @@ import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.VisionLayout
|
||||
import space.kscience.visionforge.solid.Solid
|
||||
import space.kscience.visionforge.solid.three.ThreeCanvas
|
||||
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||
|
@ -3,6 +3,7 @@ package space.kscience.visionforge.solid.demo
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.BoxGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.number
|
||||
import space.kscience.dataforge.names.asName
|
||||
@ -43,13 +44,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
|
||||
it.layers.enable(this@VariableBox.layer)
|
||||
}
|
||||
}
|
||||
mesh.scale.z = getOwnProperty(VALUE).number?.toDouble() ?: 1.0
|
||||
mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0
|
||||
|
||||
//add listener to object properties
|
||||
onPropertyChange(three.context) { name ->
|
||||
onPropertyChange { name ->
|
||||
when {
|
||||
name == VALUE -> {
|
||||
val value = getOwnProperty(VALUE).int ?: 0
|
||||
val value = meta.get(VALUE).int ?: 0
|
||||
val size = value.toFloat() / 255f * 20f
|
||||
mesh.scale.z = size.toDouble()
|
||||
mesh.position.z = size.toDouble() / 2
|
||||
@ -69,7 +70,7 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
|
||||
}
|
||||
|
||||
var value: Int
|
||||
get() = getOwnProperty(VALUE).int ?: 0
|
||||
get() = meta[VALUE].int ?: 0
|
||||
set(value) {
|
||||
setProperty(VALUE, value.asValue())
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class FXDemoApp : App(FXDemoGrid::class) {
|
||||
stage.height = 600.0
|
||||
|
||||
view.showcase()
|
||||
view.showcaseCSG()
|
||||
//view.showcaseCSG()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.VisionLayout
|
||||
import space.kscience.visionforge.solid.FX3DPlugin
|
||||
import space.kscience.visionforge.solid.FXCanvas3D
|
||||
import space.kscience.visionforge.solid.Solid
|
||||
|
@ -1,13 +1,14 @@
|
||||
package space.kscience.visionforge.demo
|
||||
|
||||
import javafx.geometry.Orientation
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.asConfig
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.node
|
||||
import space.kscience.dataforge.meta.descriptors.value
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.visionforge.editor.ConfigEditor
|
||||
import space.kscience.visionforge.editor.FXMeta
|
||||
import space.kscience.visionforge.editor.FXMetaModel
|
||||
import space.kscience.visionforge.editor.MetaViewer
|
||||
import space.kscience.visionforge.editor.MutableMetaEditor
|
||||
import tornadofx.*
|
||||
|
||||
|
||||
@ -15,7 +16,7 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
|
||||
|
||||
class MetaEditorDemo : View("Meta editor demo") {
|
||||
|
||||
val meta = Meta {
|
||||
val meta = MutableMeta {
|
||||
"aNode" put {
|
||||
"innerNode" put {
|
||||
"innerValue" put true
|
||||
@ -23,18 +24,16 @@ class MetaEditorDemo : View("Meta editor demo") {
|
||||
"b" put 223
|
||||
"c" put "StringValue"
|
||||
}
|
||||
}.asConfig()
|
||||
}
|
||||
|
||||
val descriptor = NodeDescriptor {
|
||||
val descriptor = MetaDescriptor {
|
||||
node("aNode") {
|
||||
info = "A root demo node"
|
||||
value("b") {
|
||||
value("b", ValueType.NUMBER) {
|
||||
info = "b number value"
|
||||
type(ValueType.NUMBER)
|
||||
}
|
||||
node("otherNode") {
|
||||
value("otherValue") {
|
||||
type(ValueType.BOOLEAN)
|
||||
value("otherValue", ValueType.BOOLEAN) {
|
||||
default(false)
|
||||
info = "default value"
|
||||
}
|
||||
@ -46,12 +45,13 @@ class MetaEditorDemo : View("Meta editor demo") {
|
||||
}
|
||||
}
|
||||
|
||||
private val rootNode = FXMeta.root(meta, descriptor)
|
||||
private val rootNode:FXMetaModel<MutableMeta> = FXMetaModel.root(meta, descriptor)
|
||||
|
||||
override val root =
|
||||
splitpane(Orientation.HORIZONTAL, MetaViewer(rootNode).root, ConfigEditor(
|
||||
rootNode
|
||||
).root)
|
||||
override val root = splitpane(
|
||||
Orientation.HORIZONTAL,
|
||||
MetaViewer(rootNode).root,
|
||||
MutableMetaEditor(rootNode).root
|
||||
)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
## Library design
|
||||
The central point of the library design is the `Vision` interface. The `Vision` stores an optional reference to its parent and is able to store a number of mutable or read-only properties. Each property is represented by its `Name`, and a `MetaItem` value-tree, both following DataForge library specification (discussed in the [Appendix](appendix.md)). The `Vision` objects are organized in a tree using `VisionGroup` as nodes. `VisionGroup` additionally to all `Vision` properties holds a `children` container that holds named references to its direct children `Vision`s. Thus, `Vision`s form a doubly linked tree (a parent stores references to all its children and children store a reference to the parent).
|
||||
The central point of the library design is the `Vision` interface. The `Vision` stores an optional reference to its parent and is able to store a number of mutable or read-only properties. Each property is represented by its `Name`, and a `Meta` value-tree, both following DataForge library specification (discussed in the [Appendix](appendix.md)). The `Vision` objects are organized in a tree using `VisionGroup` as nodes. `VisionGroup` additionally to all `Vision` properties holds a `children` container that holds named references to its direct children `Vision`s. Thus, `Vision`s form a doubly linked tree (a parent stores references to all its children and children store a reference to the parent).
|
||||
|
||||
An important concept using in the VisionForge is the property layering mechanism. It means that if the property with a given name is not found in the `Vision` it is requested from, it could be requested from the parent `Vision`, form the style declaration, the prototype for the vision or any other place defined by the component author. For example, let's take a `color` attribute used in 3D visualization. When one draws a group of objects, he usually wants to make the color of all objects in the group to be defined by a single handle in the group common ancestor. So when the parent color changes, all children color must follow suite, but we also want to change children color individually without changing the parent. In this case two property layers are defined:
|
||||
|
||||
|
@ -2,9 +2,11 @@
|
||||
'https://plantuml.com/class-diagram
|
||||
interface Vision
|
||||
|
||||
|
||||
interface Solid
|
||||
Vision <- Solid
|
||||
|
||||
|
||||
class VisionGroup
|
||||
Vision <-- VisionGroup
|
||||
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
2
gradlew
vendored
Normal file → Executable file
2
gradlew
vendored
Normal file → Executable file
@ -72,7 +72,7 @@ case "`uname`" in
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
|
@ -1,6 +1,6 @@
|
||||
pluginManagement {
|
||||
|
||||
val toolsVersion = "0.10.0"
|
||||
val toolsVersion = "0.10.2"
|
||||
|
||||
repositories {
|
||||
maven("https://repo.kotlin.link")
|
||||
@ -33,6 +33,7 @@ include(
|
||||
":visionforge-gdml",
|
||||
":visionforge-server",
|
||||
":visionforge-plotly",
|
||||
":visionforge-markdown",
|
||||
":demo:solid-showcase",
|
||||
":demo:gdml",
|
||||
":demo:muon-monitor",
|
||||
|
@ -14,6 +14,7 @@ import react.dom.attrs
|
||||
import react.dom.button
|
||||
import space.kscience.dataforge.meta.withDefault
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.encodeToString
|
||||
import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.flexRow
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
@ -43,19 +44,19 @@ public external interface CanvasControlsProps : RProps {
|
||||
public var vision: Vision?
|
||||
}
|
||||
|
||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
flexColumn {
|
||||
flexRow {
|
||||
css {
|
||||
border(1.px, BorderStyle.solid, Color.blue)
|
||||
padding(4.px)
|
||||
}
|
||||
props.vision?.manager?.let { manager ->
|
||||
props.vision?.let{ vision ->
|
||||
button {
|
||||
+"Export"
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
val json = manager.encodeToString(props.vision!!)
|
||||
val json = vision.encodeToString()
|
||||
saveData(it, "object.json", "text/json") {
|
||||
json
|
||||
}
|
||||
@ -65,8 +66,8 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
|
||||
}
|
||||
}
|
||||
propertyEditor(
|
||||
ownProperties = props.canvasOptions,
|
||||
allProperties = props.canvasOptions.withDefault(Canvas3DOptions.descriptor.defaultMeta),
|
||||
ownProperties = props.canvasOptions.meta,
|
||||
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
|
||||
descriptor = Canvas3DOptions.descriptor,
|
||||
expanded = false
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ public external class TabProps : RProps {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val Tab: FunctionalComponent<TabProps> = functionalComponent { props ->
|
||||
public val Tab: FunctionComponent<TabProps> = functionalComponent { props ->
|
||||
props.children()
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ public external class TabPaneProps : RProps {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val TabPane: FunctionalComponent<TabPaneProps> = functionalComponent("TabPane") { props ->
|
||||
public val TabPane: FunctionComponent<TabPaneProps> = functionalComponent("TabPane") { props ->
|
||||
var activeTab: String? by useState(props.activeTab)
|
||||
|
||||
val children: Array<out ReactElement?> = Children.map(props.children) {
|
||||
|
@ -21,7 +21,7 @@ public external interface ThreeControlsProps : RProps {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
|
||||
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionalComponent { props ->
|
||||
tabPane(if (props.selected != null) "Properties" else null) {
|
||||
tab("Canvas") {
|
||||
card("Canvas configuration") {
|
||||
|
@ -3,23 +3,25 @@ package space.kscience.visionforge.bootstrap
|
||||
import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.render
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.getStyle
|
||||
import space.kscience.visionforge.react.metaViewer
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
import space.kscience.visionforge.solid.SolidReference
|
||||
import space.kscience.visionforge.styles
|
||||
|
||||
public fun RBuilder.visionPropertyEditor(
|
||||
vision: Vision,
|
||||
descriptor: NodeDescriptor? = vision.descriptor,
|
||||
descriptor: MetaDescriptor? = vision.descriptor,
|
||||
key: Any? = null,
|
||||
) {
|
||||
|
||||
card("Properties") {
|
||||
propertyEditor(
|
||||
ownProperties = vision.ownProperties,
|
||||
allProperties = vision.allProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
descriptor = descriptor,
|
||||
key = key
|
||||
)
|
||||
@ -47,7 +49,7 @@ public fun RBuilder.visionPropertyEditor(
|
||||
|
||||
public fun Element.visionPropertyEditor(
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
descriptor: MetaDescriptor? = item.descriptor,
|
||||
): Unit = render(this) {
|
||||
visionPropertyEditor(item, descriptor = descriptor)
|
||||
}
|
@ -8,12 +8,10 @@ import react.*
|
||||
import react.dom.a
|
||||
import react.dom.attrs
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaItemNode
|
||||
import space.kscience.dataforge.meta.MetaItemValue
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
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
|
||||
import space.kscience.dataforge.names.lastOrNull
|
||||
@ -41,18 +39,19 @@ public external interface MetaViewerProps : RProps {
|
||||
/**
|
||||
* Root descriptor
|
||||
*/
|
||||
public var descriptor: NodeDescriptor?
|
||||
public var descriptor: MetaDescriptor?
|
||||
}
|
||||
|
||||
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
|
||||
private val MetaViewerItem: FunctionComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
|
||||
metaViewerItem(props)
|
||||
}
|
||||
|
||||
private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
||||
var expanded: Boolean by useState { true }
|
||||
val item = props.root[props.name]
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val actualItem = item ?: descriptorItem?.defaultValue
|
||||
val descriptorItem: MetaDescriptor? = props.descriptor?.get(props.name)
|
||||
val actualValue = item?.value ?: descriptorItem?.defaultValue
|
||||
val actualMeta = item ?: descriptorItem?.defaultNode
|
||||
|
||||
val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: ""
|
||||
|
||||
@ -60,90 +59,75 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
when (actualItem) {
|
||||
is MetaItemNode -> {
|
||||
flexRow {
|
||||
flexRow {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
}
|
||||
if (actualMeta?.isLeaf == false) {
|
||||
styledSpan {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeCaret
|
||||
if (expanded) {
|
||||
+TreeStyles.treeCaredDown
|
||||
}
|
||||
}
|
||||
attrs {
|
||||
onClickFunction = expanderClick
|
||||
+TreeStyles.treeCaret
|
||||
if (expanded) {
|
||||
+TreeStyles.treeCaredDown
|
||||
}
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (item == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
}
|
||||
if (expanded) {
|
||||
flexColumn {
|
||||
css {
|
||||
+TreeStyles.tree
|
||||
}
|
||||
val keys = buildSet {
|
||||
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
|
||||
add(NameToken(it))
|
||||
}
|
||||
actualItem.node.items.keys.let { addAll(it) }
|
||||
}
|
||||
|
||||
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(MetaViewerItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.root = props.root
|
||||
this.name = props.name + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||
}
|
||||
}
|
||||
attrs {
|
||||
onClickFunction = expanderClick
|
||||
}
|
||||
}
|
||||
}
|
||||
is MetaItemValue -> {
|
||||
flexRow {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (item == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
styledSpan {
|
||||
}
|
||||
+token
|
||||
}
|
||||
styledDiv {
|
||||
a {
|
||||
+actualValue.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expanded) {
|
||||
flexColumn {
|
||||
css {
|
||||
+TreeStyles.tree
|
||||
}
|
||||
val keys = buildSet {
|
||||
descriptorItem?.children?.keys?.forEach {
|
||||
add(NameToken(it))
|
||||
}
|
||||
actualMeta!!.items.keys.let { addAll(it) }
|
||||
}
|
||||
|
||||
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (item == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(MetaViewerItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.root = props.root
|
||||
this.name = props.name + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
styledDiv {
|
||||
a {
|
||||
+actualItem.value.toString()
|
||||
}
|
||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val MetaViewer: FunctionalComponent<MetaViewerProps> =
|
||||
public val MetaViewer: FunctionComponent<MetaViewerProps> =
|
||||
functionalComponent<MetaViewerProps>("MetaViewer") { props ->
|
||||
child(MetaViewerItem) {
|
||||
attrs {
|
||||
@ -155,7 +139,7 @@ public val MetaViewer: FunctionalComponent<MetaViewerProps> =
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) {
|
||||
public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) {
|
||||
child(MetaViewer) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
|
@ -5,32 +5,28 @@ import org.w3c.dom.HTMLOptionElement
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.asList
|
||||
import org.w3c.dom.events.Event
|
||||
import react.FunctionalComponent
|
||||
import react.FunctionComponent
|
||||
import react.dom.attrs
|
||||
import react.dom.option
|
||||
import react.dom.select
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import space.kscience.dataforge.meta.value
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.string
|
||||
|
||||
@JsExport
|
||||
public val MultiSelectChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val MultiSelectChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("MultiSelectChooser") { props ->
|
||||
var selectedItems by useState { props.item.value?.list ?: emptyList() }
|
||||
|
||||
val onChange: (Event) -> Unit = { event: Event ->
|
||||
val newSelected= (event.target as HTMLSelectElement).selectedOptions.asList()
|
||||
val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList()
|
||||
.map { (it as HTMLOptionElement).value.asValue() }
|
||||
props.valueChanged?.invoke(newSelected.asValue())
|
||||
selectedItems = newSelected
|
||||
props.meta.value = newSelected.asValue()
|
||||
}
|
||||
|
||||
select {
|
||||
attrs {
|
||||
multiple = true
|
||||
values = selectedItems.mapTo(HashSet()) { it.string }
|
||||
values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
|
||||
onChangeFunction = onChange
|
||||
}
|
||||
props.descriptor?.allowedValues?.forEach { optionValue ->
|
||||
|
@ -1,14 +1,5 @@
|
||||
package space.kscience.visionforge.react
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.TextDecoration
|
||||
import kotlinx.html.js.onClickFunction
|
||||
@ -18,15 +9,10 @@ import react.*
|
||||
import react.dom.attrs
|
||||
import react.dom.render
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.lastOrNull
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.visionforge.hidden
|
||||
import styled.css
|
||||
import styled.styledButton
|
||||
@ -36,34 +22,24 @@ import styled.styledSpan
|
||||
public external interface PropertyEditorProps : RProps {
|
||||
|
||||
/**
|
||||
* Root config object - always non null
|
||||
* Root config object - always non-null
|
||||
*/
|
||||
public var ownProperties: MutableItemProvider
|
||||
public var meta: ObservableMutableMeta
|
||||
|
||||
/**
|
||||
* Provide default item (greyed out if used)
|
||||
*/
|
||||
public var allProperties: ItemProvider?
|
||||
public var withDefault: MetaProvider
|
||||
|
||||
/**
|
||||
* Full path to the displayed node in [ownProperties]. Could be empty
|
||||
* Full path to the displayed node in [meta]. Could be empty
|
||||
*/
|
||||
public var name: Name
|
||||
|
||||
/**
|
||||
* Root descriptor
|
||||
*/
|
||||
public var descriptor: NodeDescriptor?
|
||||
|
||||
/**
|
||||
* A coroutine scope for updates
|
||||
*/
|
||||
public var scope: CoroutineScope?
|
||||
|
||||
/**
|
||||
* Flow names of updated properties
|
||||
*/
|
||||
public var updateFlow: Flow<Name>?
|
||||
public var descriptor: MetaDescriptor?
|
||||
|
||||
/**
|
||||
* Initial expanded state
|
||||
@ -71,67 +47,60 @@ public external interface PropertyEditorProps : RProps {
|
||||
public var expanded: Boolean?
|
||||
}
|
||||
|
||||
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> =
|
||||
functionalComponent("ConfigEditorItem") { props ->
|
||||
private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
|
||||
functionalComponent("PropertyEditorItem") { props ->
|
||||
propertyEditorItem(props)
|
||||
}
|
||||
|
||||
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
var expanded: Boolean by useState { props.expanded ?: true }
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
var ownProperty: MetaItem? by useState { props.ownProperties.getItem(props.name) }
|
||||
val actualItem: MetaItem? = props.allProperties?.getItem(props.name)
|
||||
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
|
||||
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
|
||||
|
||||
val keys = useMemo(descriptor) {
|
||||
buildSet {
|
||||
descriptor?.children?.filterNot {
|
||||
it.key.startsWith("@") || it.value.hidden
|
||||
}?.forEach {
|
||||
add(NameToken(it.key))
|
||||
}
|
||||
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val token = props.name.lastOrNull()?.toString() ?: "Properties"
|
||||
|
||||
fun update() {
|
||||
ownProperty = props.ownProperties.getItem(props.name)
|
||||
ownProperty = props.meta.getOrCreate(props.name)
|
||||
}
|
||||
|
||||
if (props.updateFlow != null) {
|
||||
useEffect(props.ownProperties, props.updateFlow) {
|
||||
val updateJob = props.updateFlow!!.onEach { updatedName ->
|
||||
if (updatedName == props.name) {
|
||||
update()
|
||||
}
|
||||
}.launchIn(props.scope ?: GlobalScope)
|
||||
cleanup {
|
||||
updateJob.cancel()
|
||||
useEffect(props.meta) {
|
||||
props.meta.onChange(props) { updatedName ->
|
||||
if (updatedName == props.name) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
props.meta.removeListener(props)
|
||||
}
|
||||
}
|
||||
|
||||
val expanderClick: (Event) -> Unit = {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
val valueChanged: (Value?) -> Unit = {
|
||||
if (it == null) {
|
||||
props.ownProperties.remove(props.name)
|
||||
} else {
|
||||
props.ownProperties[props.name] = it
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
val removeClick: (Event) -> Unit = {
|
||||
props.ownProperties.remove(props.name)
|
||||
props.meta.remove(props.name)
|
||||
update()
|
||||
}
|
||||
|
||||
if (actualItem is MetaItemNode) {
|
||||
val keys = buildSet {
|
||||
(descriptorItem as? NodeDescriptor)?.items?.filterNot {
|
||||
it.key.startsWith("@") || it.value.hidden
|
||||
}?.forEach {
|
||||
add(NameToken(it.key))
|
||||
}
|
||||
ownProperty?.node?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
|
||||
}
|
||||
// Do not show nodes without visible children
|
||||
if (keys.isEmpty()) return
|
||||
|
||||
flexRow {
|
||||
|
||||
flexRow {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
}
|
||||
if (keys.isNotEmpty()) {
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeCaret
|
||||
@ -143,67 +112,30 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
onClickFunction = expanderClick
|
||||
}
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (ownProperty == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
}
|
||||
if (expanded) {
|
||||
flexColumn {
|
||||
css {
|
||||
+TreeStyles.tree
|
||||
}
|
||||
keys.forEach { token ->
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.ownProperties = props.ownProperties
|
||||
this.allProperties = props.allProperties
|
||||
this.name = props.name + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flexRow {
|
||||
styledSpan {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
}
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (ownProperty == null) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
+TreeStyles.treeLabel
|
||||
if (ownProperty.isEmpty()) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
+token
|
||||
}
|
||||
|
||||
+token
|
||||
}
|
||||
if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
|
||||
styledDiv {
|
||||
css {
|
||||
//+TreeStyles.resizeableInput
|
||||
width = 160.px
|
||||
margin(1.px, 5.px)
|
||||
}
|
||||
valueChooser(
|
||||
props.name,
|
||||
actualItem,
|
||||
descriptorItem as? ValueDescriptor,
|
||||
valueChanged
|
||||
)
|
||||
ValueChooser{
|
||||
attrs {
|
||||
this.descriptor = descriptor
|
||||
this.meta = ownProperty
|
||||
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
styledButton {
|
||||
@ -225,83 +157,85 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
}
|
||||
+"\u00D7"
|
||||
attrs {
|
||||
if (ownProperty == null) {
|
||||
if (ownProperty.isEmpty()) {
|
||||
disabled = true
|
||||
} else {
|
||||
onClickFunction = removeClick
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (expanded) {
|
||||
flexColumn {
|
||||
css {
|
||||
+TreeStyles.tree
|
||||
}
|
||||
keys.forEach { token ->
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.meta = props.meta
|
||||
this.withDefault = props.withDefault
|
||||
this.name = props.name + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsExport
|
||||
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
|
||||
public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = ""
|
||||
this.ownProperties = props.ownProperties
|
||||
this.allProperties = props.allProperties
|
||||
this.meta = props.meta
|
||||
this.withDefault = props.withDefault
|
||||
this.name = Name.EMPTY
|
||||
this.descriptor = props.descriptor
|
||||
this.scope = props.scope
|
||||
this.expanded = props.expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.propertyEditor(
|
||||
ownProperties: MutableItemProvider,
|
||||
allProperties: ItemProvider? = ownProperties,
|
||||
updateFlow: Flow<Name>? = null,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
ownProperties: ObservableMutableMeta,
|
||||
allProperties: MetaProvider = ownProperties,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
key: Any? = null,
|
||||
expanded: Boolean? = null
|
||||
) {
|
||||
child(PropertyEditor) {
|
||||
attrs {
|
||||
this.ownProperties = ownProperties
|
||||
this.allProperties = allProperties
|
||||
this.updateFlow = updateFlow
|
||||
this.meta = ownProperties
|
||||
this.withDefault = allProperties
|
||||
this.descriptor = descriptor
|
||||
this.key = key?.toString() ?: ""
|
||||
this.scope = scope
|
||||
this.expanded = expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun Config.flowUpdates(): Flow<Name> = callbackFlow {
|
||||
onChange(this) { name, _, _ ->
|
||||
launch {
|
||||
send(name)
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
removeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun RBuilder.configEditor(
|
||||
config: Config,
|
||||
default: ItemProvider? = null,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
config: ObservableMutableMeta,
|
||||
default: MetaProvider = config,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
key: Any? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
|
||||
): Unit = propertyEditor(config, default, descriptor, key = key)
|
||||
|
||||
public fun Element.configEditor(
|
||||
config: Config,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
default: Meta? = null,
|
||||
config: ObservableMutableMeta,
|
||||
default: Meta = config,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
key: Any? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = render(this) {
|
||||
configEditor(config, default, descriptor, key, scope)
|
||||
configEditor(config, default, descriptor, key = key)
|
||||
}
|
@ -6,10 +6,11 @@ import kotlinx.html.InputType
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.FunctionalComponent
|
||||
import react.FunctionComponent
|
||||
import react.dom.attrs
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||
import space.kscience.dataforge.meta.double
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
@ -18,32 +19,34 @@ import styled.css
|
||||
import styled.styledInput
|
||||
|
||||
@JsExport
|
||||
public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("RangeValueChooser") { props ->
|
||||
var innerValue by useState(props.item.double)
|
||||
var rangeDisabled: Boolean by useState(props.item == null)
|
||||
var innerValue by useState(props.actual.double)
|
||||
var rangeDisabled: Boolean by useState(props.meta.value == null)
|
||||
|
||||
val handleDisable: (Event) -> Unit = {
|
||||
val checkBoxValue = (it.target as HTMLInputElement).checked
|
||||
rangeDisabled = !checkBoxValue
|
||||
if(!checkBoxValue) {
|
||||
props.valueChanged?.invoke(null)
|
||||
props.meta.value = if(!checkBoxValue) {
|
||||
null
|
||||
} else {
|
||||
props.valueChanged?.invoke(innerValue?.asValue())
|
||||
innerValue?.asValue()
|
||||
}
|
||||
}
|
||||
|
||||
val handleChange: (Event) -> Unit = {
|
||||
val newValue = (it.target as HTMLInputElement).value
|
||||
props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue())
|
||||
props.meta.value = newValue.toDoubleOrNull()?.asValue()
|
||||
innerValue = newValue.toDoubleOrNull()
|
||||
}
|
||||
|
||||
flexRow {
|
||||
styledInput(type = InputType.checkBox) {
|
||||
attrs {
|
||||
defaultChecked = rangeDisabled.not()
|
||||
onChangeFunction = handleDisable
|
||||
if(props.descriptor?.valueRequirement != ValueRequirement.REQUIRED) {
|
||||
styledInput(type = InputType.checkBox) {
|
||||
attrs {
|
||||
defaultChecked = rangeDisabled.not()
|
||||
onChangeFunction = handleDisable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +58,7 @@ public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
disabled = rangeDisabled
|
||||
value = innerValue?.toString() ?: ""
|
||||
onChangeFunction = handleChange
|
||||
consumer.onTagEvent(this, "input", handleChange)
|
||||
val minValue = props.descriptor?.attributes?.get("min").string
|
||||
minValue?.let {
|
||||
min = it
|
||||
|
@ -21,7 +21,7 @@ public external interface ThreeCanvasProps : RProps {
|
||||
public var selected: Name?
|
||||
}
|
||||
|
||||
public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functionalComponent(
|
||||
public val ThreeCanvasComponent: FunctionComponent<ThreeCanvasProps> = functionalComponent(
|
||||
"ThreeCanvasComponent"
|
||||
) { props ->
|
||||
val elementRef = useRef<Element>(null)
|
||||
|
@ -107,7 +107,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ObjectTree: FunctionalComponent<ObjectTreeProps> = functionalComponent("ObjectTree") { props ->
|
||||
public val ObjectTree: FunctionComponent<ObjectTreeProps> = functionalComponent("ObjectTree") { props ->
|
||||
visionTree(props)
|
||||
}
|
||||
|
||||
|
@ -14,9 +14,12 @@ import react.*
|
||||
import react.dom.attrs
|
||||
import react.dom.option
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.*
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.int
|
||||
import space.kscience.dataforge.values.string
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.widgetType
|
||||
import styled.css
|
||||
@ -24,29 +27,26 @@ import styled.styledInput
|
||||
import styled.styledSelect
|
||||
|
||||
public external interface ValueChooserProps : RProps {
|
||||
public var item: MetaItem?
|
||||
public var descriptor: ValueDescriptor?
|
||||
//public var nullable: Boolean?
|
||||
public var valueChanged: ((Value?) -> Unit)?
|
||||
public var descriptor: MetaDescriptor?
|
||||
public var meta: ObservableMutableMeta
|
||||
public var actual: Meta
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val StringValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("StringValueChooser") { props ->
|
||||
var value by useState(props.item.string ?: "")
|
||||
var value by useState(props.actual.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
value = (event.target as HTMLInputElement).value
|
||||
if (value != props.item.string) {
|
||||
props.valueChanged?.invoke(value.asValue())
|
||||
}
|
||||
props.meta.value = value.asValue()
|
||||
}
|
||||
}
|
||||
val handleChange: (Event) -> Unit = {
|
||||
value = (it.target as HTMLInputElement).value
|
||||
}
|
||||
styledInput(type = InputType.text) {
|
||||
css{
|
||||
css {
|
||||
width = 100.pct
|
||||
}
|
||||
attrs {
|
||||
@ -58,28 +58,28 @@ public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("BooleanValueChooser") { props ->
|
||||
val handleChange: (Event) -> Unit = {
|
||||
val newValue = (it.target as HTMLInputElement).checked
|
||||
props.valueChanged?.invoke(newValue.asValue())
|
||||
props.meta.value = newValue.asValue()
|
||||
}
|
||||
styledInput(type = InputType.checkBox) {
|
||||
css{
|
||||
css {
|
||||
width = 100.pct
|
||||
}
|
||||
attrs {
|
||||
//this.attributes["indeterminate"] = (props.item == null).toString()
|
||||
defaultChecked = props.item.boolean ?: false
|
||||
defaultChecked = props.actual.boolean ?: false
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("NumberValueChooser") { props ->
|
||||
var innerValue by useState(props.item.string ?: "")
|
||||
var innerValue by useState(props.actual.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
innerValue = (event.target as HTMLInputElement).value
|
||||
@ -87,7 +87,7 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
if (number == null) {
|
||||
console.error("The input value $innerValue is not a number")
|
||||
} else {
|
||||
props.valueChanged?.invoke(number.asValue())
|
||||
props.meta.value = number.asValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
innerValue = (it.target as HTMLInputElement).value
|
||||
}
|
||||
styledInput(type = InputType.number) {
|
||||
css{
|
||||
css {
|
||||
width = 100.pct
|
||||
}
|
||||
attrs {
|
||||
@ -116,15 +116,15 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("ComboValueChooser") { props ->
|
||||
var selected by useState(props.item.string ?: "")
|
||||
var selected by useState(props.actual.string ?: "")
|
||||
val handleChange: (Event) -> Unit = {
|
||||
selected = (it.target as HTMLSelectElement).value
|
||||
props.valueChanged?.invoke(selected.asValue())
|
||||
props.meta.value = selected.asValue()
|
||||
}
|
||||
styledSelect {
|
||||
css{
|
||||
css {
|
||||
width = 100.pct
|
||||
}
|
||||
props.descriptor?.allowedValues?.forEach {
|
||||
@ -133,7 +133,7 @@ public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
}
|
||||
}
|
||||
attrs {
|
||||
this.value = props.item?.string ?: ""
|
||||
this.value = props.actual.string ?: ""
|
||||
multiple = false
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
@ -141,20 +141,20 @@ public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ColorValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
public val ColorValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("ColorValueChooser") { props ->
|
||||
var value by useState(
|
||||
props.item.value?.let { value ->
|
||||
props.actual.value?.let { value ->
|
||||
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
||||
else value.string
|
||||
} ?: "#000000"
|
||||
)
|
||||
val handleChange: (Event) -> Unit = {
|
||||
value = (it.target as HTMLInputElement).value
|
||||
props.valueChanged?.invoke(value.asValue())
|
||||
props.meta.value = value.asValue()
|
||||
}
|
||||
styledInput(type = InputType.color) {
|
||||
css{
|
||||
css {
|
||||
width = 100.pct
|
||||
margin(0.px)
|
||||
}
|
||||
@ -166,11 +166,11 @@ public val ColorValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
|
||||
public val ValueChooser: FunctionComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
|
||||
val rawInput by useState(false)
|
||||
|
||||
val descriptor = props.descriptor
|
||||
val type = descriptor?.type?.firstOrNull()
|
||||
val type = descriptor?.valueTypes?.firstOrNull()
|
||||
|
||||
when {
|
||||
rawInput -> child(StringValueChooser, props)
|
||||
@ -184,19 +184,3 @@ public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComp
|
||||
else -> child(StringValueChooser, props)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RBuilder.valueChooser(
|
||||
name: Name,
|
||||
item: MetaItem?,
|
||||
descriptor: ValueDescriptor? = null,
|
||||
callback: (Value?) -> Unit,
|
||||
) {
|
||||
child(ValueChooser) {
|
||||
attrs {
|
||||
key = name.toString()
|
||||
this.item = item
|
||||
this.descriptor = descriptor
|
||||
this.valueChanged = callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ kotlin{
|
||||
|
||||
dependencies{
|
||||
api(project(":ui:react"))
|
||||
//TODO replace by kotlin-wrappers
|
||||
api("ru.mipt.npm:ring-ui:0.1.0")
|
||||
//api("ru.mipt.npm:ring-ui:0.1.0")
|
||||
api("org.jetbrains.kotlin-wrappers:kotlin-ring-ui")
|
||||
|
||||
implementation(npm("@jetbrains/icons", "3.14.1"))
|
||||
implementation(npm("@jetbrains/ring-ui", "4.0.7"))
|
||||
implementation(npm("core-js","3.12.1"))
|
||||
implementation(npm("file-saver", "2.0.2"))
|
||||
compileOnly(npm("url-loader","4.1.1"))
|
||||
compileOnly(npm("postcss-loader","5.2.0"))
|
||||
compileOnly(npm("source-map-loader","2.0.1"))
|
||||
// compileOnly(npm("url-loader","4.1.1"))
|
||||
// compileOnly(npm("postcss-loader","5.2.0"))
|
||||
}
|
19
ui/ring/src/main/kotlin/ringui/Loader.kt
Normal file
19
ui/ring/src/main/kotlin/ringui/Loader.kt
Normal file
@ -0,0 +1,19 @@
|
||||
@file:JsModule("@jetbrains/ring-ui/components/loader/loader")
|
||||
@file:JsNonModule
|
||||
|
||||
package ringui
|
||||
|
||||
import react.ComponentClass
|
||||
import react.dom.WithClassName
|
||||
|
||||
// https://github.com/JetBrains/ring-ui/blob/master/components/loader/loader.js
|
||||
public external interface LoaderProps : WithClassName {
|
||||
public var size: Number
|
||||
public var colors: Array<String>
|
||||
public var message: String
|
||||
public var stop: Boolean
|
||||
public var deterministic: Boolean
|
||||
}
|
||||
|
||||
@JsName("default")
|
||||
public external val Loader: ComponentClass<LoaderProps>
|
16
ui/ring/src/main/kotlin/ringui/LoaderScreen.kt
Normal file
16
ui/ring/src/main/kotlin/ringui/LoaderScreen.kt
Normal file
@ -0,0 +1,16 @@
|
||||
@file:JsModule("@jetbrains/ring-ui/components/loader-screen/loader-screen")
|
||||
@file:JsNonModule
|
||||
|
||||
package ringui
|
||||
|
||||
import react.ComponentClass
|
||||
import react.dom.WithClassName
|
||||
|
||||
// https://github.com/JetBrains/ring-ui/blob/master/components/loader-screen/loader-screen.js
|
||||
public external interface LoaderScreenProps : WithClassName {
|
||||
public var containerClassName: String
|
||||
public var message: String
|
||||
}
|
||||
|
||||
@JsName("default")
|
||||
public external val LoaderScreen: ComponentClass<LoaderScreenProps>
|
@ -1,37 +1,44 @@
|
||||
package space.kscience.visionforge.ring
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import react.*
|
||||
import react.dom.div
|
||||
import react.dom.span
|
||||
import ringui.Island
|
||||
import ringui.IslandContent
|
||||
import ringui.IslandHeader
|
||||
import ringui.Link
|
||||
import ringui.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.dataforge.names.length
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionGroup
|
||||
import space.kscience.visionforge.allProperties
|
||||
import space.kscience.visionforge.ownProperties
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.react.ThreeCanvasComponent
|
||||
import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.flexRow
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
import space.kscience.visionforge.solid.Solid
|
||||
import space.kscience.visionforge.solid.SolidGroup
|
||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
public external interface ThreeCanvasWithControlsProps : RProps {
|
||||
public var context: Context
|
||||
public var solid: Solid?
|
||||
public var builderOfSolid: Deferred<Solid?>
|
||||
public var selected: Name?
|
||||
public var additionalTabs: Map<String, RBuilder.() -> Unit>?
|
||||
}
|
||||
|
||||
public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
|
||||
builderOfSolid = context.async {
|
||||
SolidGroup(block)
|
||||
}
|
||||
}
|
||||
|
||||
public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) {
|
||||
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
|
||||
}
|
||||
@ -70,9 +77,16 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsProps> =
|
||||
public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> =
|
||||
functionalComponent("ThreeViewWithControls") { props ->
|
||||
var selected by useState { props.selected }
|
||||
var solid: Solid? by useState(null)
|
||||
|
||||
useEffect {
|
||||
props.context.launch {
|
||||
solid = props.builderOfSolid.await()
|
||||
}
|
||||
}
|
||||
|
||||
val onSelect: (Name?) -> Unit = {
|
||||
selected = it
|
||||
@ -84,15 +98,16 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
|
||||
}
|
||||
}
|
||||
|
||||
val selectedVision = useMemo(selected) {
|
||||
val selectedVision: Vision? = useMemo(props.builderOfSolid, selected) {
|
||||
selected?.let {
|
||||
when {
|
||||
it.isEmpty() -> props.solid
|
||||
else -> (props.solid as? VisionGroup)?.get(it)
|
||||
it.isEmpty() -> solid
|
||||
else -> (solid as? VisionGroup)?.get(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
flexRow {
|
||||
css {
|
||||
height = 100.pct
|
||||
@ -109,35 +124,42 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
|
||||
position = Position.relative
|
||||
}
|
||||
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.solid = props.solid
|
||||
this.selected = selected
|
||||
this.options = options
|
||||
if (solid == null) {
|
||||
LoaderScreen {
|
||||
attrs {
|
||||
message = "Loading Three vision"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.solid = solid
|
||||
this.selected = selected
|
||||
this.options = options
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedVision?.let { vision ->
|
||||
styledDiv {
|
||||
css{
|
||||
css {
|
||||
position = Position.absolute
|
||||
top = 5.px
|
||||
right = 5.px
|
||||
width = 450.px
|
||||
}
|
||||
Island{
|
||||
IslandHeader{
|
||||
Island {
|
||||
IslandHeader {
|
||||
attrs {
|
||||
border = true
|
||||
}
|
||||
nameCrumbs(selected) { selected = it }
|
||||
}
|
||||
IslandContent{
|
||||
IslandContent {
|
||||
propertyEditor(
|
||||
ownProperties = vision.ownProperties,
|
||||
allProperties = vision.allProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
descriptor = vision.descriptor,
|
||||
key = selected
|
||||
)
|
||||
@ -152,7 +174,8 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
|
||||
minWidth = 400.px
|
||||
flex(1.0, 10.0, FlexBasis("300px"))
|
||||
}
|
||||
ringThreeControls(options, props.solid, selected, onSelect, additionalTabs = props.additionalTabs)
|
||||
ringThreeControls(options, solid, selected, onSelect, additionalTabs = props.additionalTabs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.visionforge.ring
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import org.w3c.dom.Element
|
||||
import react.child
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
@ -28,7 +29,7 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
this.context = this@ThreeWithControlsPlugin.context
|
||||
this.solid = vision as? Solid
|
||||
this.builderOfSolid = context.async { vision as Solid}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,19 @@ import react.dom.render
|
||||
import ringui.Island
|
||||
import ringui.SmartTabs
|
||||
import ringui.Tab
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.getStyle
|
||||
import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.metaViewer
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
import space.kscience.visionforge.solid.SolidReference
|
||||
import space.kscience.visionforge.styles
|
||||
|
||||
public fun RBuilder.ringPropertyEditor(
|
||||
vision: Vision,
|
||||
descriptor: NodeDescriptor? = vision.descriptor,
|
||||
descriptor: MetaDescriptor? = vision.descriptor,
|
||||
key: Any? = null,
|
||||
) {
|
||||
val styles = if (vision is SolidReference) {
|
||||
@ -28,9 +31,8 @@ public fun RBuilder.ringPropertyEditor(
|
||||
flexColumn {
|
||||
Island("Properties") {
|
||||
propertyEditor(
|
||||
ownProperties = vision.ownProperties,
|
||||
allProperties = vision.allProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
descriptor = descriptor,
|
||||
key = key
|
||||
)
|
||||
@ -69,7 +71,7 @@ public fun RBuilder.ringPropertyEditor(
|
||||
|
||||
public fun Element.ringPropertyEditor(
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
descriptor: MetaDescriptor? = item.descriptor,
|
||||
): Unit = render(this) {
|
||||
ringPropertyEditor(item, descriptor = descriptor)
|
||||
}
|
@ -49,7 +49,7 @@ internal external interface CanvasControlsProps : RProps {
|
||||
public var vision: Vision?
|
||||
}
|
||||
|
||||
internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
flexColumn {
|
||||
flexRow {
|
||||
css {
|
||||
@ -72,8 +72,8 @@ internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = function
|
||||
}
|
||||
}
|
||||
propertyEditor(
|
||||
ownProperties = props.options,
|
||||
allProperties = props.options.withDefault(Canvas3DOptions.descriptor.defaultMeta),
|
||||
ownProperties = props.options.meta,
|
||||
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
|
||||
descriptor = Canvas3DOptions.descriptor,
|
||||
expanded = false
|
||||
)
|
||||
@ -91,7 +91,7 @@ public external interface ThreeControlsProps : RProps {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
|
||||
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionalComponent { props ->
|
||||
SmartTabs("Tree") {
|
||||
props.vision?.let {
|
||||
Tab("Tree") {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.number
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.int
|
||||
import space.kscience.dataforge.values.string
|
||||
@ -190,25 +192,18 @@ public object Colors {
|
||||
/**
|
||||
* Convert color represented as Meta to string of format #rrggbb
|
||||
*/
|
||||
fun fromMeta(item: MetaItem): String {
|
||||
return when (item) {
|
||||
is MetaItemNode -> {
|
||||
val node = item.node
|
||||
rgbToString(
|
||||
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||
)
|
||||
}
|
||||
is MetaItemValue -> {
|
||||
if (item.value.type == ValueType.NUMBER) {
|
||||
rgbToString(item.value.int)
|
||||
} else {
|
||||
item.value.string
|
||||
}
|
||||
}
|
||||
fun fromMeta(meta: Meta): String = meta.value?.let { value ->
|
||||
//if value is present, use it
|
||||
if (value.type == ValueType.NUMBER) {
|
||||
rgbToString(value.int)
|
||||
} else {
|
||||
value.string
|
||||
}
|
||||
}
|
||||
} ?: rgbToString(
|
||||
meta[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
meta[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
meta[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||
)
|
||||
|
||||
/**
|
||||
* Convert Int color to string of format #rrggbb
|
||||
|
@ -0,0 +1,84 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
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.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.values.MutableValueProvider
|
||||
import space.kscience.dataforge.values.Value
|
||||
|
||||
private class ComputedVisionProperties(
|
||||
val vision: Vision,
|
||||
val pathName: Name,
|
||||
val visionDescriptor: MetaDescriptor,
|
||||
val parentInheritFlag: Boolean?,
|
||||
val parentStylesFlag: Boolean?
|
||||
) : Meta {
|
||||
|
||||
val descriptor: MetaDescriptor? by lazy { visionDescriptor[pathName] }
|
||||
|
||||
override val items: Map<NameToken, Meta>
|
||||
get() {
|
||||
val metaKeys = vision.meta.getMeta(pathName)?.items?.keys ?: emptySet()
|
||||
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
|
||||
val inheritFlag = descriptor?.inherited ?: parentInheritFlag
|
||||
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag
|
||||
return (metaKeys + descriptorKeys).associateWith {
|
||||
ComputedVisionProperties(
|
||||
vision,
|
||||
pathName + it,
|
||||
visionDescriptor,
|
||||
inheritFlag,
|
||||
stylesFlag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override val value: Value?
|
||||
get() {
|
||||
val inheritFlag = descriptor?.inherited ?: parentInheritFlag ?: false
|
||||
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag ?: true
|
||||
return vision.getPropertyValue(pathName, inheritFlag, stylesFlag, true)
|
||||
}
|
||||
|
||||
override fun toString(): String = Meta.toString(this)
|
||||
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||
override fun hashCode(): Int = Meta.hashCode(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute property node based on inheritance and style information from the descriptor
|
||||
*/
|
||||
public fun Vision.computeProperties(descriptor: MetaDescriptor? = this.descriptor): Meta =
|
||||
if (descriptor == null) meta else ComputedVisionProperties(this, Name.EMPTY, descriptor, null, null)
|
||||
|
||||
public fun Vision.computePropertyNode(
|
||||
name: Name,
|
||||
descriptor: MetaDescriptor? = this.descriptor
|
||||
): Meta? = computeProperties(descriptor)[name]
|
||||
|
||||
/**
|
||||
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
|
||||
*/
|
||||
public fun Vision.computeProperty(name: Name, valueDescriptor: MetaDescriptor? = descriptor?.get(name)): Value? {
|
||||
val inheritFlag = valueDescriptor?.inherited ?: false
|
||||
val stylesFlag = valueDescriptor?.usesStyles ?: true
|
||||
return getPropertyValue(name, inheritFlag, stylesFlag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor to all vision properties
|
||||
*/
|
||||
public fun Vision.computePropertyValues(
|
||||
descriptor: MetaDescriptor? = this.descriptor
|
||||
): MutableValueProvider = object : MutableValueProvider {
|
||||
override fun getValue(name: Name): Value? = computeProperty(name, descriptor?.get(name))
|
||||
|
||||
override fun setValue(name: Name, value: Value?) {
|
||||
setProperty(name, value)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
import space.kscience.dataforge.meta.Specification
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
@ -27,7 +27,7 @@ public fun Vision.useStyle(reference: StyleReference) {
|
||||
@VisionBuilder
|
||||
public fun VisionGroup.style(
|
||||
styleKey: String? = null,
|
||||
builder: MetaBuilder.() -> Unit,
|
||||
builder: MutableMeta.() -> Unit,
|
||||
): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property ->
|
||||
val styleName = styleKey ?: property.name
|
||||
styleSheet.define(styleName, Meta(builder))
|
||||
|
@ -5,6 +5,9 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.stringList
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
/**
|
||||
@ -13,9 +16,9 @@ import kotlin.jvm.JvmInline
|
||||
@JvmInline
|
||||
public value class StyleSheet(private val owner: VisionGroup) {
|
||||
|
||||
private val styleNode get() = owner.ownProperties[STYLESHEET_KEY].node
|
||||
private val styleNode: Meta? get() = owner.meta[STYLESHEET_KEY]
|
||||
|
||||
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
|
||||
public val items: Map<NameToken, Meta>? get() = styleNode?.items
|
||||
|
||||
public operator fun get(key: String): Meta? = owner.getStyle(key)
|
||||
|
||||
@ -23,7 +26,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
|
||||
* Define a style without notifying owner
|
||||
*/
|
||||
public fun define(key: String, style: Meta?) {
|
||||
owner.setProperty(STYLESHEET_KEY + key, style)
|
||||
owner.meta.setMeta(STYLESHEET_KEY + key, style)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +43,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
|
||||
/**
|
||||
* Create and set a style
|
||||
*/
|
||||
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||
public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
|
||||
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
|
||||
set(key, newStyle.seal())
|
||||
}
|
||||
@ -70,9 +73,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
|
||||
* List of names of styles applied to this object. Order matters. Not inherited.
|
||||
*/
|
||||
public var Vision.styles: List<String>
|
||||
get() = ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()
|
||||
get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()
|
||||
set(value) {
|
||||
setProperty(Vision.STYLE_KEY, value)
|
||||
meta.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,7 +88,7 @@ public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
|
||||
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||
*/
|
||||
public fun Vision.useStyle(name: String) {
|
||||
styles = (ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name
|
||||
styles = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
|
||||
}
|
||||
|
||||
|
||||
@ -93,13 +96,18 @@ public fun Vision.useStyle(name: String) {
|
||||
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
|
||||
*/
|
||||
public tailrec fun Vision.getStyle(name: String): Meta? =
|
||||
ownProperties[StyleSheet.STYLESHEET_KEY + name].node ?: parent?.getStyle(name)
|
||||
meta.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
|
||||
|
||||
/**
|
||||
* Resolve a property from all styles
|
||||
*/
|
||||
public fun Vision.getStyleProperty(name: Name): Value? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name)?.value }
|
||||
|
||||
/**
|
||||
* Resolve an item in all style layers
|
||||
*/
|
||||
public fun Vision.getStyleItems(name: Name): List<MetaItem> = styles.mapNotNull {
|
||||
getStyle(it)[name]
|
||||
public fun Vision.getStyleNodes(name: Name): List<Meta> = styles.mapNotNull {
|
||||
getStyle(it)?.get(name)
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,26 +1,29 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.Described
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.boolean
|
||||
import space.kscience.visionforge.Vision.Companion.TYPE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
/**
|
||||
* A root type for display hierarchy
|
||||
*/
|
||||
@Type(TYPE)
|
||||
public interface Vision : Described, CoroutineScope {
|
||||
public interface Vision : Described, Configurable {
|
||||
|
||||
/**
|
||||
* The parent object of this one. If null, this one is a root.
|
||||
@ -32,42 +35,23 @@ public interface Vision : Described, CoroutineScope {
|
||||
*/
|
||||
public val manager: VisionManager? get() = parent?.manager
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = manager?.context?.coroutineContext ?: EmptyCoroutineContext
|
||||
/**
|
||||
* This Vision own properties (ignoring inheritance, styles and defaults
|
||||
*/
|
||||
override val meta: ObservableMutableMeta
|
||||
|
||||
/**
|
||||
* Get property.
|
||||
* Get property value with given layer flags.
|
||||
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.
|
||||
* @param includeStyles toggles inclusion of. Null means inference from descriptor. Default is true.
|
||||
* @param includeStyles toggles inclusion of properties from styles. default is true
|
||||
*/
|
||||
public fun getProperty(
|
||||
public fun getPropertyValue(
|
||||
name: Name,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem?
|
||||
): Value?
|
||||
|
||||
/**
|
||||
* Get an intrinsic property of this Vision excluding any inheritance or defaults. In most cases should be the same as
|
||||
* `getProperty(name, false, false, false`.
|
||||
*/
|
||||
public fun getOwnProperty(name: Name): MetaItem? = getProperty(
|
||||
name,
|
||||
inherit = false,
|
||||
includeStyles = false,
|
||||
includeDefaults = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Set the property value
|
||||
*/
|
||||
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||
|
||||
/**
|
||||
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
|
||||
* if it should include inherited properties etc.
|
||||
*/
|
||||
public val propertyChanges: Flow<Name>
|
||||
|
||||
/**
|
||||
* Notify all listeners that a property has been changed and should be invalidated
|
||||
@ -79,7 +63,7 @@ public interface Vision : Described, CoroutineScope {
|
||||
*/
|
||||
public fun update(change: VisionChange)
|
||||
|
||||
override val descriptor: NodeDescriptor?
|
||||
override val descriptor: MetaDescriptor?
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "vision"
|
||||
@ -90,66 +74,74 @@ public interface Vision : Described, CoroutineScope {
|
||||
}
|
||||
|
||||
/**
|
||||
* Root property node
|
||||
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
|
||||
* if it should include inherited properties etc.
|
||||
*/
|
||||
public val Vision.meta: Meta get() = ownProperties[Name.EMPTY]?.node ?: Meta.EMPTY
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@DFExperimental
|
||||
public val Vision.propertyChanges: Flow<Name>
|
||||
get() = callbackFlow {
|
||||
meta.onChange(this) { name ->
|
||||
launch {
|
||||
send(name)
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
meta.removeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled
|
||||
*/
|
||||
public fun Vision.onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||
propertyChanges.onEach(callback).launchIn(scope)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Own properties, excluding inheritance, styles and descriptor
|
||||
*/
|
||||
public val Vision.ownProperties: MutableItemProvider
|
||||
get() = object : MutableItemProvider {
|
||||
override fun getItem(name: Name): MetaItem? = getOwnProperty(name)
|
||||
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient accessor for all properties of a vision.
|
||||
* @param inherit - inherit property value from the parent by default. If null, inheritance is inferred from descriptor
|
||||
*/
|
||||
public fun Vision.allProperties(
|
||||
inherit: Boolean? = null,
|
||||
includeStyles: Boolean? = null,
|
||||
includeDefaults: Boolean = true,
|
||||
): MutableItemProvider = object : MutableItemProvider {
|
||||
override fun getItem(name: Name): MetaItem? = getProperty(
|
||||
name,
|
||||
inherit = inherit ?: (descriptor?.get(name)?.inherited == true),
|
||||
includeStyles = includeStyles ?: (descriptor?.get(name)?.usesStyles != false),
|
||||
includeDefaults = includeDefaults
|
||||
)
|
||||
|
||||
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
|
||||
public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) {
|
||||
meta.onChange(null, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [Vision] property using key as a String
|
||||
*/
|
||||
public fun Vision.getProperty(
|
||||
public fun Vision.getPropertyValue(
|
||||
key: String,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults)
|
||||
): Value? = getPropertyValue(Name.parse(key), inherit, includeStyles, includeDefaults)
|
||||
|
||||
/**
|
||||
* A convenience method to pair [getProperty]
|
||||
* A convenience method to set property node or value. If Item is null, then node is removed, not a value
|
||||
*/
|
||||
public fun Vision.setProperty(key: Name, item: Any?) {
|
||||
setProperty(key, MetaItem.of(item))
|
||||
public fun Vision.setProperty(name: Name, item: Any?) {
|
||||
when (item) {
|
||||
null -> meta.remove(name)
|
||||
is Meta -> meta.setMeta(name, item)
|
||||
is Value -> meta.setValue(name, item)
|
||||
else -> meta.setValue(name, Value.of(item))
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.setPropertyNode(key: String, item: Any?) {
|
||||
setProperty(Name.parse(key), item)
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to pair [getProperty]
|
||||
* Control visibility of the element
|
||||
*/
|
||||
public fun Vision.setProperty(key: String, item: Any?) {
|
||||
setProperty(key.toName(), MetaItem.of(item))
|
||||
}
|
||||
public var Vision.visible: Boolean?
|
||||
get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
|
||||
set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
|
||||
|
||||
|
||||
public fun <V : Vision, T> V.useProperty(
|
||||
property: KProperty1<V, T>,
|
||||
owner: Any? = null,
|
||||
callBack: V.(T) -> Unit,
|
||||
) {
|
||||
//Pass initial value.
|
||||
callBack(property.get(this))
|
||||
meta.onChange(owner) { name ->
|
||||
if (name.startsWith(property.name.asName())) {
|
||||
callBack(property.get(this@useProperty))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,136 +1,168 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.ObservableMutableMeta
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.value
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
internal data class MetaListener(
|
||||
val owner: Any? = null,
|
||||
val callback: Meta.(name: Name) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* A full base implementation for a [Vision]
|
||||
* @param properties Object own properties excluding styles and inheritance
|
||||
* @param parent the parent object for this vision. Could ve set later. Not serialized.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("vision")
|
||||
public open class VisionBase(
|
||||
override @Transient var parent: VisionGroup? = null,
|
||||
protected var properties: Config? = null
|
||||
@Transient override var parent: VisionGroup? = null,
|
||||
) : Vision {
|
||||
|
||||
@Transient
|
||||
protected open var properties: MutableMeta? = null
|
||||
|
||||
@Synchronized
|
||||
protected fun getOrCreateProperties(): Config {
|
||||
protected fun getOrCreateProperties(): MutableMeta {
|
||||
if (properties == null) {
|
||||
val newProperties = Config()
|
||||
val newProperties = MutableMeta()
|
||||
properties = newProperties
|
||||
}
|
||||
return properties!!
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast accessor method to get own property (no inheritance or styles)
|
||||
*/
|
||||
override fun getOwnProperty(name: Name): MetaItem? = if (name == Name.EMPTY) {
|
||||
properties?.asMetaItem()
|
||||
} else {
|
||||
properties?.getItem(name)
|
||||
@Transient
|
||||
private val listeners: MutableList<MetaListener> = mutableListOf()
|
||||
|
||||
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
|
||||
|
||||
override val items: Map<NameToken, ObservableMutableMeta>
|
||||
get() = properties?.get(pathName)?.items?.mapValues { entry ->
|
||||
VisionProperties(pathName + entry.key)
|
||||
} ?: emptyMap()
|
||||
|
||||
override var value: Value?
|
||||
get() = properties?.get(pathName)?.value
|
||||
set(value) {
|
||||
val oldValue = properties?.get(pathName)?.value
|
||||
getOrCreateProperties().setValue(pathName, value)
|
||||
if (oldValue != value) {
|
||||
invalidate(Name.EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrCreate(name: Name): ObservableMutableMeta = VisionProperties(pathName + name)
|
||||
|
||||
override fun setMeta(name: Name, node: Meta?) {
|
||||
getOrCreateProperties().setMeta(pathName + name, node)
|
||||
invalidate(name)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||
val ownProperties = getOrCreateProperties()
|
||||
if (ownProperties is ObservableMutableMeta) {
|
||||
ownProperties.attach(pathName + name, node)
|
||||
} else {
|
||||
ownProperties.setMeta(pathName + name, node)
|
||||
node.onChange(this) { childName ->
|
||||
ownProperties.setMeta(pathName + name + childName, this[childName])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate(name: Name) {
|
||||
invalidateProperty(pathName + name)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
|
||||
if (pathName.isEmpty()) {
|
||||
listeners.add((MetaListener(owner, callback)))
|
||||
} else {
|
||||
listeners.add(MetaListener(owner) { name ->
|
||||
if (name.startsWith(pathName)) {
|
||||
(this@MetaListener[pathName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(pathName)!!)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner === owner }
|
||||
}
|
||||
|
||||
override fun toString(): String = Meta.toString(this)
|
||||
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||
override fun hashCode(): Int = Meta.hashCode(this)
|
||||
}
|
||||
|
||||
override fun getProperty(
|
||||
override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
|
||||
|
||||
override fun getPropertyValue(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) {
|
||||
getOwnProperty(name)
|
||||
} else {
|
||||
buildList {
|
||||
add(getOwnProperty(name))
|
||||
if (includeStyles) {
|
||||
addAll(getStyleItems(name))
|
||||
}
|
||||
if (inherit) {
|
||||
add(parent?.getProperty(name, inherit, includeStyles, includeDefaults))
|
||||
}
|
||||
if (includeDefaults) {
|
||||
add(descriptor?.defaultMeta?.get(name))
|
||||
}
|
||||
}.merge()
|
||||
}
|
||||
|
||||
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||
val oldItem = properties?.getItem(name)
|
||||
if(oldItem!= item) {
|
||||
getOrCreateProperties().setItem(name, item)
|
||||
if (notify) {
|
||||
invalidateProperty(name)
|
||||
}
|
||||
): Value? {
|
||||
properties?.get(name)?.value?.let { return it }
|
||||
if (includeStyles) {
|
||||
getStyleProperty(name)?.let { return it }
|
||||
}
|
||||
if (inherit) {
|
||||
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
|
||||
}
|
||||
if (includeDefaults) {
|
||||
descriptor?.defaultNode?.get(name)?.value.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor? get() = null
|
||||
override val descriptor: MetaDescriptor? get() = null
|
||||
|
||||
private suspend fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
invalidateProperty(it.key.asName())
|
||||
}
|
||||
}
|
||||
|
||||
//TODO check memory consumption for the flow
|
||||
@Transient
|
||||
private val propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()
|
||||
|
||||
@DFExperimental
|
||||
override val propertyChanges: Flow<Name>
|
||||
get() = propertyInvalidationFlow
|
||||
|
||||
override fun invalidateProperty(propertyName: Name) {
|
||||
launch {
|
||||
if (propertyName == STYLE_KEY) {
|
||||
updateStyles(styles)
|
||||
}
|
||||
propertyInvalidationFlow.emit(propertyName)
|
||||
if (propertyName == STYLE_KEY) {
|
||||
styles.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
invalidateProperty(it.key.asName())
|
||||
}
|
||||
}
|
||||
listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) }
|
||||
}
|
||||
|
||||
override fun update(change: VisionChange) {
|
||||
change.properties?.let {
|
||||
updateProperties(Name.EMPTY, it.asMetaItem())
|
||||
updateProperties(Name.EMPTY, it)
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public val descriptor: NodeDescriptor = NodeDescriptor {
|
||||
value(STYLE_KEY) {
|
||||
type(ValueType.STRING)
|
||||
public val descriptor: MetaDescriptor = MetaDescriptor {
|
||||
value(STYLE_KEY, ValueType.STRING) {
|
||||
multiple = true
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.updateProperties(at: Name, item: MetaItem) {
|
||||
when (item) {
|
||||
is MetaItemValue -> {
|
||||
if (item.value == Null) {
|
||||
setProperty(at, null)
|
||||
} else
|
||||
setProperty(at, item)
|
||||
}
|
||||
is MetaItemNode -> item.node.items.forEach { (token, childItem) ->
|
||||
updateProperties(at + token, childItem)
|
||||
}
|
||||
public fun Vision.updateProperties(at: Name, item: Meta) {
|
||||
meta.setValue(at, item.value)
|
||||
item.items.forEach { (token, item) ->
|
||||
updateProperties(at + token, item)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
|
||||
private var reset: Boolean = false
|
||||
private var vision: Vision? = null
|
||||
private val propertyChange = Config()
|
||||
private val propertyChange = MutableMeta()
|
||||
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
|
||||
|
||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
|
||||
@ -30,17 +30,17 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
|
||||
children.getOrPut(visionName) { VisionChangeBuilder() }
|
||||
|
||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) {
|
||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
|
||||
if (visionName == Name.EMPTY) {
|
||||
//Write property removal as [Null]
|
||||
propertyChange[propertyName] = (item ?: Null.asMetaItem())
|
||||
propertyChange[propertyName] = (item ?: Meta(Null))
|
||||
} else {
|
||||
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun set(name: Name?, child: Vision?) {
|
||||
if(name == null) error("Static children are not allowed in VisionChange")
|
||||
if (name == null) error("Static children are not allowed in VisionChange")
|
||||
getOrPutChild(name).apply {
|
||||
vision = child
|
||||
reset = vision == null
|
||||
@ -82,6 +82,7 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde
|
||||
VisionChangeBuilder().apply(block).isolate(manager)
|
||||
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
private fun CoroutineScope.collectChange(
|
||||
name: Name,
|
||||
source: Vision,
|
||||
@ -89,8 +90,8 @@ private fun CoroutineScope.collectChange(
|
||||
) {
|
||||
|
||||
//Collect properties change
|
||||
source.onPropertyChange(this) { propertyName ->
|
||||
val newItem = source.ownProperties[propertyName]
|
||||
source.onPropertyChange { propertyName ->
|
||||
val newItem = source.meta[propertyName]
|
||||
collector().propertyChanged(name, propertyName, newItem)
|
||||
}
|
||||
|
||||
@ -102,11 +103,13 @@ private fun CoroutineScope.collectChange(
|
||||
|
||||
//Subscribe for structure change
|
||||
if (source is MutableVisionGroup) {
|
||||
source.structureChanges.onEach { (token, _, after) ->
|
||||
source.structureChanges.onEach { changedName ->
|
||||
val after = source[changedName]
|
||||
val fullName = name + changedName
|
||||
if (after != null) {
|
||||
collectChange(name + token, after, collector)
|
||||
collectChange(fullName, after, collector)
|
||||
}
|
||||
collector()[name + token] = after
|
||||
collector()[fullName] = after
|
||||
}.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.provider.Provider
|
||||
|
||||
@DslMarker
|
||||
public annotation class VisionBuilder
|
||||
|
||||
public interface VisionContainer<out V : Vision> {
|
||||
public operator fun get(name: Name): V?
|
||||
}
|
||||
@ -66,21 +74,36 @@ public interface VisionContainerBuilder<in V : Vision> {
|
||||
* Mutable version of [VisionGroup]
|
||||
*/
|
||||
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
|
||||
public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit)
|
||||
|
||||
public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?)
|
||||
|
||||
/**
|
||||
* Flow structure changes of this group. Unconsumed changes are discarded
|
||||
*/
|
||||
public val structureChanges: Flow<StructureChange>
|
||||
public fun removeStructureListener(owner: Any?)
|
||||
}
|
||||
|
||||
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
|
||||
|
||||
/**
|
||||
* Flow structure changes of this group. Unconsumed changes are discarded
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@DFExperimental
|
||||
public val MutableVisionGroup.structureChanges: Flow<Name>
|
||||
get() = callbackFlow {
|
||||
meta.onChange(this) { name ->
|
||||
launch {
|
||||
send(name)
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
removeStructureListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(Name.parse(str))
|
||||
|
||||
public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
|
||||
set(token.asName(), child)
|
||||
|
||||
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit =
|
||||
set(key?.toName(), child)
|
||||
set(key?.let(Name::parse), child)
|
||||
|
||||
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -1,13 +1,12 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import space.kscience.dataforge.names.*
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
private class StructureChangeListener(val owner: Any?, val callback: VisionGroup.(Name) -> Unit)
|
||||
|
||||
/**
|
||||
* Abstract implementation of mutable group of [Vision]
|
||||
@ -40,16 +39,24 @@ public open class VisionGroupBase(
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow()
|
||||
private val structureListeners = HashSet<StructureChangeListener>()
|
||||
|
||||
override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges
|
||||
@Synchronized
|
||||
override fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) {
|
||||
structureListeners.add(StructureChangeListener(owner, block))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeStructureListener(owner: Any?) {
|
||||
structureListeners.removeAll { it.owner == owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate children change event upwards
|
||||
*/
|
||||
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||
launch {
|
||||
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
|
||||
protected fun childrenChanged(name: Name) {
|
||||
structureListeners.forEach {
|
||||
it.callback(this, name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +90,12 @@ public open class VisionGroupBase(
|
||||
}
|
||||
}
|
||||
if (before != child) {
|
||||
childrenChanged(token, before, child)
|
||||
childrenChanged(token.asName())
|
||||
if (child is MutableVisionGroup) {
|
||||
child.onStructureChanged(this) { changedName ->
|
||||
this@VisionGroupBase.childrenChanged(token + changedName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,6 +163,6 @@ internal class RootVisionGroup(override val manager: VisionManager) : VisionGrou
|
||||
/**
|
||||
* Designate this [VisionGroup] as a root group and assign a [VisionManager] as its parent
|
||||
*/
|
||||
public fun Vision.root(manager: VisionManager){
|
||||
public fun Vision.root(manager: VisionManager) {
|
||||
parent = RootVisionGroup(manager)
|
||||
}
|
@ -8,12 +8,10 @@ import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.node
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.toJson
|
||||
import space.kscience.dataforge.meta.toMetaItem
|
||||
import space.kscience.dataforge.meta.toMeta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.toName
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
@ -48,12 +46,11 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
jsonFormat.encodeToJsonElement(visionSerializer, vision)
|
||||
|
||||
//TODO remove double transformation with dedicated Meta serial format
|
||||
public fun decodeFromMeta(meta: Meta, descriptor: NodeDescriptor? = null): Vision =
|
||||
public fun decodeFromMeta(meta: Meta, descriptor: MetaDescriptor? = null): Vision =
|
||||
decodeFromJson(meta.toJson(descriptor))
|
||||
|
||||
public fun encodeToMeta(vision: Vision, descriptor: NodeDescriptor? = null): Meta =
|
||||
encodeToJsonElement(vision).toMetaItem(descriptor).node
|
||||
?: error("Expected node, but value found. Check your serializer!")
|
||||
public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta =
|
||||
encodeToJsonElement(vision).toMeta(descriptor)
|
||||
|
||||
public companion object : PluginFactory<VisionManager> {
|
||||
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
|
||||
@ -89,7 +86,7 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
|
||||
protected abstract val visionSerializersModule: SerializersModule
|
||||
|
||||
override fun content(target: String): Map<Name, Any> = when (target) {
|
||||
VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(tag.toString().toName() to visionSerializersModule)
|
||||
VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(Name.parse(tag.toString()) to visionSerializersModule)
|
||||
else -> super.content(target)
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,34 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.Config
|
||||
import space.kscience.dataforge.meta.MetaItem
|
||||
import space.kscience.dataforge.meta.Configurable
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.ObservableMutableMeta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.set
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.Value
|
||||
|
||||
/**
|
||||
* Property containers are used to create a symmetric behaviors for vision properties and style builders
|
||||
*/
|
||||
public interface VisionPropertyContainer<out V: Vision> {
|
||||
public fun getProperty(
|
||||
public interface VisionPropertyContainer<out V : Vision> {
|
||||
|
||||
public val meta: MutableMeta
|
||||
|
||||
public fun getPropertyValue(
|
||||
name: Name,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem?
|
||||
|
||||
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||
): Value?
|
||||
}
|
||||
|
||||
public open class SimpleVisionPropertyContainer<out V: Vision>(protected val config: Config): VisionPropertyContainer<V>{
|
||||
override fun getProperty(
|
||||
public open class SimpleVisionPropertyContainer<out V : Vision>(
|
||||
override val meta: ObservableMutableMeta,
|
||||
) : VisionPropertyContainer<V>, Configurable {
|
||||
override fun getPropertyValue(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean
|
||||
): MetaItem? = config[name]
|
||||
|
||||
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||
config[name] = item
|
||||
}
|
||||
|
||||
): Value? = meta[name]?.value
|
||||
}
|
@ -2,14 +2,16 @@ package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MetaSerializer
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.isEmpty
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.root
|
||||
import kotlin.collections.set
|
||||
|
||||
@DslMarker
|
||||
@ -25,7 +27,7 @@ public class VisionOutput @PublishedApi internal constructor(public val manager:
|
||||
|
||||
//TODO expose a way to define required plugins.
|
||||
|
||||
public inline fun meta(block: MetaBuilder.() -> Unit) {
|
||||
public inline fun meta(block: MutableMeta.() -> Unit) {
|
||||
this.meta = Meta(block)
|
||||
}
|
||||
}
|
||||
@ -36,7 +38,7 @@ public class VisionOutput @PublishedApi internal constructor(public val manager:
|
||||
@VisionDSL
|
||||
public abstract class VisionTagConsumer<R>(
|
||||
private val root: TagConsumer<R>,
|
||||
public val manager:VisionManager,
|
||||
public val manager: VisionManager,
|
||||
private val idPrefix: String? = null,
|
||||
) : TagConsumer<R> by root {
|
||||
|
||||
@ -83,6 +85,7 @@ public abstract class VisionTagConsumer<R>(
|
||||
): T {
|
||||
val output = VisionOutput(manager)
|
||||
val vision = output.visionProvider()
|
||||
vision.root(manager)
|
||||
return vision(name, vision, output.meta)
|
||||
}
|
||||
|
||||
@ -94,11 +97,11 @@ public abstract class VisionTagConsumer<R>(
|
||||
public inline fun <T> TagConsumer<T>.vision(
|
||||
name: String = DEFAULT_VISION_NAME,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T = vision(name.toName(), visionProvider)
|
||||
): T = vision(Name.parse(name), visionProvider)
|
||||
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
vision: Vision,
|
||||
): T = vision("vision[${vision.hashCode()}]".toName(), vision)
|
||||
): T = vision(NameToken("vision", vision.hashCode().toString()).asName(), vision)
|
||||
|
||||
/**
|
||||
* Process the resulting object produced by [TagConsumer]
|
||||
|
@ -1,28 +0,0 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.values.asValue
|
||||
|
||||
@DslMarker
|
||||
public annotation class VisionBuilder
|
||||
|
||||
public fun List<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull { it != null }) {
|
||||
null -> null
|
||||
is MetaItemValue -> first //fast search for first entry if it is value
|
||||
is MetaItemNode -> {
|
||||
//merge nodes if first encountered node is meta
|
||||
val laminate: Laminate = Laminate(mapNotNull { it.node })
|
||||
MetaItemNode(laminate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Control visibility of the element
|
||||
*/
|
||||
public var Vision.visible: Boolean?
|
||||
get() = getProperty(Vision.VISIBLE_KEY).boolean
|
||||
set(value) = setProperty(Vision.VISIBLE_KEY, value?.asValue())
|
||||
|
||||
public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta))
|
||||
|
||||
public fun Vision.configure(block: MetaBuilder.() -> Unit): Unit = configure(Meta(block))
|
@ -1,67 +0,0 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
import space.kscience.dataforge.meta.SchemeSpec
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptorBuilder
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptorBuilder
|
||||
import space.kscience.dataforge.meta.toConfig
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* TODO to be moved into the core
|
||||
*/
|
||||
public inline fun <S : Scheme, reified T> NodeDescriptorBuilder.value(
|
||||
property: KProperty1<S, T>,
|
||||
noinline block: ValueDescriptorBuilder.() -> Unit = {},
|
||||
) {
|
||||
when (typeOf<T>()) {
|
||||
typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() ->
|
||||
value(property.name) {
|
||||
type(ValueType.NUMBER)
|
||||
block()
|
||||
}
|
||||
typeOf<Number?>(), typeOf<Int?>(), typeOf<Double?>(), typeOf<Short?>(), typeOf<Long?>(), typeOf<Float?>() ->
|
||||
value(property.name) {
|
||||
type(ValueType.NUMBER)
|
||||
block()
|
||||
}
|
||||
typeOf<Boolean>() -> value(property.name) {
|
||||
type(ValueType.BOOLEAN)
|
||||
block()
|
||||
}
|
||||
typeOf<List<Number>>(), typeOf<List<Int>>(), typeOf<List<Double>>(), typeOf<List<Short>>(), typeOf<List<Long>>(), typeOf<List<Float>>(),
|
||||
typeOf<IntArray>(), typeOf<DoubleArray>(), typeOf<ShortArray>(), typeOf<LongArray>(), typeOf<FloatArray>(),
|
||||
-> value(property.name) {
|
||||
type(ValueType.NUMBER)
|
||||
multiple = true
|
||||
block()
|
||||
}
|
||||
typeOf<String>() -> value(property.name) {
|
||||
type(ValueType.STRING)
|
||||
block()
|
||||
}
|
||||
typeOf<List<String>>(), typeOf<Array<String>>() -> value(property.name) {
|
||||
type(ValueType.STRING)
|
||||
multiple = true
|
||||
block()
|
||||
}
|
||||
else -> value(property.name, block)
|
||||
}
|
||||
}
|
||||
|
||||
public fun NodeDescriptor.copy(block: NodeDescriptorBuilder.() -> Unit = {}): NodeDescriptor {
|
||||
return NodeDescriptorBuilder(toMeta().toConfig()).apply(block)
|
||||
}
|
||||
|
||||
public inline fun <S : Scheme, reified T : Scheme> NodeDescriptorBuilder.scheme(
|
||||
property: KProperty1<S, T>,
|
||||
spec: SchemeSpec<T>,
|
||||
noinline block: NodeDescriptorBuilder.() -> Unit = {},
|
||||
) {
|
||||
spec.descriptor?.let { descriptor ->
|
||||
item(property.name, descriptor.copy(block))
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.number
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
//public fun Vision.propertyNode(
|
||||
// name: Name? = null,
|
||||
// inherit: Boolean = false,
|
||||
// includeStyles: Boolean = true,
|
||||
// includeDefaults: Boolean = true,
|
||||
//): ReadWriteProperty<Any?, Meta?> = object : ReadWriteProperty<Any?, Meta?> {
|
||||
// override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? =
|
||||
// getProperty(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)
|
||||
//
|
||||
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
|
||||
// meta.setMeta(name ?: Name.parse(property.name), value)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public fun <T> Vision.propertyNode(
|
||||
// converter: MetaConverter<T>,
|
||||
// name: Name? = null,
|
||||
// inherit: Boolean = false,
|
||||
// includeStyles: Boolean = true,
|
||||
// includeDefaults: Boolean = true,
|
||||
//): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> {
|
||||
// override fun getValue(thisRef: Any?, property: KProperty<*>): T? = getProperty(
|
||||
// name ?: Name.parse(property.name),
|
||||
// inherit,
|
||||
// includeStyles,
|
||||
// includeDefaults
|
||||
// )?.let(converter::metaToObject)
|
||||
//
|
||||
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
||||
// meta.setMeta(name ?: Name.parse(property.name), value?.let(converter::objectToMeta))
|
||||
// }
|
||||
//}
|
||||
|
||||
public fun Vision.propertyValue(
|
||||
name: Name? = null,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): ReadWriteProperty<Any?, Value?> = object : ReadWriteProperty<Any?, Value?> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? =
|
||||
getPropertyValue(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
|
||||
meta.setValue(name ?: Name.parse(property.name), value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> Vision.propertyValue(
|
||||
name: Name? = null,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
setter: (T) -> Value? = { it?.let(Value::of) },
|
||||
getter: (Value?) -> T,
|
||||
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T = getPropertyValue(
|
||||
name ?: Name.parse(property.name),
|
||||
inherit,
|
||||
includeStyles,
|
||||
includeDefaults
|
||||
).let(getter)
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
meta.setValue(name ?: Name.parse(property.name), value?.let(setter))
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.numberProperty(
|
||||
name: Name? = null,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true
|
||||
): ReadWriteProperty<Any?, Number?> = propertyValue(name, inherit, includeStyles, includeDefaults) { it?.number }
|
||||
|
||||
public fun Vision.numberProperty(
|
||||
name: Name? = null,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
default: () -> Number
|
||||
): ReadWriteProperty<Any?, Number> = propertyValue(name, inherit, includeStyles, includeDefaults) {
|
||||
it?.number ?: default()
|
||||
}
|
@ -2,87 +2,54 @@ package space.kscience.visionforge
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.asValue
|
||||
|
||||
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
||||
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
|
||||
|
||||
public val ItemDescriptor.inherited: Boolean
|
||||
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
|
||||
public val MetaDescriptor.inherited: Boolean?
|
||||
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean
|
||||
|
||||
public var ItemDescriptorBuilder.inherited: Boolean
|
||||
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
|
||||
set(value) = attributes {
|
||||
set(INHERITED_DESCRIPTOR_ATTRIBUTE, value)
|
||||
}
|
||||
|
||||
public val ItemDescriptor.usesStyles: Boolean
|
||||
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
|
||||
|
||||
public var ItemDescriptorBuilder.usesStyles: Boolean
|
||||
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
|
||||
set(value) = attributes {
|
||||
set(STYLE_DESCRIPTOR_ATTRIBUTE, value)
|
||||
}
|
||||
public var MetaDescriptorBuilder.inherited: Boolean?
|
||||
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean
|
||||
set(value) = attributes.set(INHERITED_DESCRIPTOR_ATTRIBUTE, value?.asValue())
|
||||
|
||||
|
||||
public val Vision.describedProperties: Meta
|
||||
get() = Meta {
|
||||
descriptor?.items?.forEach { (key, descriptor) ->
|
||||
key put getProperty(key, inherit = descriptor.inherited)
|
||||
}
|
||||
}
|
||||
public val MetaDescriptor.usesStyles: Boolean?
|
||||
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean
|
||||
|
||||
public val ValueDescriptor.widget: Meta
|
||||
get() = attributes["widget"].node ?: Meta.EMPTY
|
||||
public var MetaDescriptorBuilder.usesStyles: Boolean?
|
||||
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean
|
||||
set(value) = attributes.set(STYLE_DESCRIPTOR_ATTRIBUTE, value?.asValue())
|
||||
|
||||
public val MetaDescriptor.widget: Meta
|
||||
get() = attributes["widget"] ?: Meta.EMPTY
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||
*/
|
||||
public var ValueDescriptorBuilder.widget: Meta
|
||||
get() = attributes["widget"].node ?: Meta.EMPTY
|
||||
public var MetaDescriptorBuilder.widget: Meta
|
||||
get() = attributes["widget"] ?: Meta.EMPTY
|
||||
set(value) {
|
||||
attributes {
|
||||
set("widget", value)
|
||||
}
|
||||
attributes["widget"] = value
|
||||
}
|
||||
|
||||
public val ValueDescriptor.widgetType: String?
|
||||
public val MetaDescriptor.widgetType: String?
|
||||
get() = attributes["widget.type"].string
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||
* Extension property to access the "widget.type" key of [MetaDescriptorBuilder]
|
||||
*/
|
||||
public var ValueDescriptorBuilder.widgetType: String?
|
||||
public var MetaDescriptorBuilder.widgetType: String?
|
||||
get() = attributes["widget.type"].string
|
||||
set(value) {
|
||||
attributes {
|
||||
set("widget.type", value)
|
||||
}
|
||||
attributes["widget.type"] = value?.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, this item is hidden in property editor. Default is false
|
||||
*/
|
||||
public val ItemDescriptor.hidden: Boolean
|
||||
public val MetaDescriptor.hidden: Boolean
|
||||
get() = attributes["widget.hide"].boolean ?: false
|
||||
|
||||
public fun ItemDescriptorBuilder.hide(): Unit = attributes {
|
||||
set("widget.hide", true)
|
||||
}
|
||||
|
||||
|
||||
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
|
||||
key: Name,
|
||||
default: E?,
|
||||
crossinline modifier: ValueDescriptorBuilder.() -> Unit = {},
|
||||
): Unit = value(key) {
|
||||
type(ValueType.STRING)
|
||||
default?.let {
|
||||
default(default)
|
||||
}
|
||||
allowedValues = enumValues<E>().map { it.asValue() }
|
||||
modifier()
|
||||
}
|
||||
public fun MetaDescriptorBuilder.hide(): Unit = attributes.set("widget.hide", true)
|
@ -5,10 +5,14 @@ import kotlinx.html.stream.createHTML
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.configure
|
||||
import space.kscience.dataforge.meta.set
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionBase
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import kotlin.collections.set
|
||||
import kotlin.test.Test
|
||||
|
||||
typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit
|
||||
|
@ -0,0 +1,44 @@
|
||||
package space.kscience.visionforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.visionforge.VisionBase
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class VisionPropertyTest {
|
||||
@Test
|
||||
fun testPropertyWrite(){
|
||||
val vision = VisionBase()
|
||||
vision.meta["fff"] = 2
|
||||
vision.meta["fff.ddd"] = false
|
||||
|
||||
assertEquals(2, vision.meta["fff"]?.int)
|
||||
assertEquals(false, vision.meta["fff.ddd"]?.boolean)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPropertyEdit(){
|
||||
val vision = VisionBase()
|
||||
vision.meta.getOrCreate("fff.ddd").apply {
|
||||
value = 2.asValue()
|
||||
}
|
||||
assertEquals(2, vision.meta["fff.ddd"]?.int)
|
||||
assertNotEquals(true, vision.meta["fff.ddd"]?.boolean)
|
||||
}
|
||||
|
||||
internal class TestScheme: Scheme(){
|
||||
var ddd by int()
|
||||
companion object: SchemeSpec<TestScheme>(::TestScheme)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPropertyUpdate(){
|
||||
val vision = VisionBase()
|
||||
vision.meta.getOrCreate("fff").updateWith(TestScheme){
|
||||
ddd = 2
|
||||
}
|
||||
assertEquals(2, vision.meta["fff.ddd"]?.int)
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.dom.hasClass
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
|
||||
public external val module: Module
|
||||
@ -25,7 +28,10 @@ public external interface Module {
|
||||
*
|
||||
* Base interface for applications supporting Hot Module Replacement (HMR).
|
||||
*/
|
||||
public interface Application {
|
||||
public interface Application: CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* Starting point for an application.
|
||||
* @param state Initial state between Hot Module Replacement (HMR).
|
||||
|
@ -14,17 +14,10 @@ dependencies {
|
||||
|
||||
api("no.tornado:tornadofx:1.7.20")
|
||||
|
||||
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11") {
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
|
||||
api("de.jensd:fontawesomefx-commons:11.0") {
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
|
||||
api("org.fxyz3d:fxyz3d:0.5.4") {
|
||||
exclude(module = "slf4j-simple")
|
||||
}
|
||||
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}")
|
||||
|
||||
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
|
||||
|
@ -115,5 +115,5 @@ public class ApplicationSurrogate : App() {
|
||||
}
|
||||
|
||||
public fun Context.display(width: Double = 800.0, height: Double = 600.0, component: () -> UIComponent) {
|
||||
plugins.fetch(FXPlugin).display(component(), width, height)
|
||||
fetch(FXPlugin).display(component(), width, height)
|
||||
}
|
@ -9,8 +9,8 @@ import javafx.collections.FXCollections
|
||||
import javafx.scene.control.ComboBox
|
||||
import javafx.util.StringConverter
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.value
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.Value
|
||||
@ -56,7 +56,7 @@ public class ComboBoxValueChooser(public val values: Collection<Value>? = null)
|
||||
override val name: Name = "combo".asName()
|
||||
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
ComboBoxValueChooser(meta["values"].value?.list)
|
||||
ComboBoxValueChooser(meta["values"]?.value?.list)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Text
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.meta.Config
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.visionforge.dfIconView
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A configuration editor fragment
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class ConfigEditor(
|
||||
public val rootNode: FXMetaNode<Config>,
|
||||
public val allowNew: Boolean = true,
|
||||
title: String = "Configuration editor"
|
||||
) : Fragment(title = title, icon = dfIconView) {
|
||||
//TODO replace parameters by properties
|
||||
|
||||
public constructor(config: Config, descriptor: NodeDescriptor?, title: String = "Configuration editor") :
|
||||
this(FXMeta.root(config, descriptor = descriptor), title = title)
|
||||
|
||||
override val root: BorderPane = borderpane {
|
||||
center = treetableview<FXMeta<Config>> {
|
||||
root = TreeItem(rootNode)
|
||||
root.isExpanded = true
|
||||
sortMode = TreeSortMode.ALL_DESCENDANTS
|
||||
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
|
||||
populate {
|
||||
when (val fxMeta = it.value) {
|
||||
is FXMetaNode -> {
|
||||
fxMeta.children
|
||||
}
|
||||
is FXMetaValue -> null
|
||||
}
|
||||
}
|
||||
column("Name", FXMeta<Config>::name) {
|
||||
setCellFactory {
|
||||
object : TextFieldTreeTableCell<FXMeta<Config>, NameToken>() {
|
||||
override fun updateItem(item: NameToken?, empty: Boolean) {
|
||||
super.updateItem(item, empty)
|
||||
contextMenu?.items?.removeIf { it.text == "Remove" }
|
||||
if (!empty) {
|
||||
if (treeTableRow.item != null) {
|
||||
textFillProperty().bind(treeTableRow.item.hasValue.objectBinding {
|
||||
if (it == true) {
|
||||
Color.BLACK
|
||||
} else {
|
||||
Color.GRAY
|
||||
}
|
||||
})
|
||||
if (treeTableRow.treeItem.value.parent != null && treeTableRow.treeItem.value.hasValue.get()) {
|
||||
contextmenu {
|
||||
item("Remove") {
|
||||
action {
|
||||
treeTableRow.item.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMeta<Config>, FXMeta<Config>> ->
|
||||
param.value.valueProperty()
|
||||
}.setCellFactory {
|
||||
ValueCell()
|
||||
}
|
||||
|
||||
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMeta<Config>, String> -> param.value.value.descriptionProperty }
|
||||
.setCellFactory { param: TreeTableColumn<FXMeta<Config>, String> ->
|
||||
val cell = TreeTableCell<FXMeta<Config>, String>()
|
||||
val text = Text()
|
||||
cell.graphic = text
|
||||
cell.prefHeight = Control.USE_COMPUTED_SIZE
|
||||
text.wrappingWidthProperty().bind(param.widthProperty())
|
||||
text.textProperty().bind(cell.itemProperty())
|
||||
cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNodeDialog(): String? {
|
||||
val dialog = TextInputDialog()
|
||||
dialog.title = "Node name selection"
|
||||
dialog.contentText = "Enter a name for new node: "
|
||||
dialog.headerText = null
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
return result.orElse(null)
|
||||
}
|
||||
|
||||
private fun showValueDialog(): String? {
|
||||
val dialog = TextInputDialog()
|
||||
dialog.title = "Value name selection"
|
||||
dialog.contentText = "Enter a name for new value: "
|
||||
dialog.headerText = null
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
return result.orElse(null)
|
||||
}
|
||||
|
||||
private inner class ValueCell : TreeTableCell<FXMeta<Config>, FXMeta<Config>?>() {
|
||||
|
||||
public override fun updateItem(item: FXMeta<Config>?, empty: Boolean) {
|
||||
if (!empty) {
|
||||
if (item != null) {
|
||||
when (item) {
|
||||
is FXMetaValue<Config> -> {
|
||||
text = null
|
||||
val chooser = ValueChooser.build(
|
||||
Global,
|
||||
item.valueProperty,
|
||||
item.descriptor
|
||||
) {
|
||||
item.set(it)
|
||||
}
|
||||
graphic = chooser.node
|
||||
}
|
||||
is FXMetaNode<Config> -> {
|
||||
if (allowNew) {
|
||||
text = null
|
||||
graphic = HBox().apply {
|
||||
val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
|
||||
button("node", graphic = glyph) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
||||
showNodeDialog()?.let {
|
||||
item.addNode(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
||||
showValueDialog()?.let {
|
||||
item.addValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
text = null
|
||||
graphic = null
|
||||
}
|
||||
} else {
|
||||
text = null
|
||||
graphic = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The tag not to display node or value in configurator
|
||||
*/
|
||||
const val NO_CONFIGURATOR_TAG = "nocfg"
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.binding.ListBinding
|
||||
import javafx.beans.binding.ObjectBinding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableBooleanValue
|
||||
import javafx.beans.value.ObservableStringValue
|
||||
import javafx.collections.ObservableList
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.values.Value
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A display for meta and descriptor
|
||||
*/
|
||||
sealed class FXMeta<M : TypedMeta<M>> : Comparable<FXMeta<*>> {
|
||||
abstract val name: NameToken
|
||||
abstract val parent: FXMetaNode<M>?
|
||||
abstract val descriptionProperty: ObservableStringValue
|
||||
abstract val descriptor: ItemDescriptor?
|
||||
|
||||
abstract val hasValue: ObservableBooleanValue
|
||||
|
||||
override fun compareTo(other: FXMeta<*>): Int {
|
||||
return if (this.hasValue.get() == other.hasValue.get()) {
|
||||
this.name.toString().compareTo(other.name.toString())
|
||||
} else {
|
||||
this.hasValue.get().compareTo(other.hasValue.get())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <M : TypedMeta<M>> root(
|
||||
node: M,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
rootName: String = "root"
|
||||
): FXMetaNode<M> =
|
||||
FXMetaNode(NameToken(rootName), null, node, descriptor)
|
||||
|
||||
fun root(node: Meta, descriptor: NodeDescriptor? = null, rootName: String = "root"): FXMetaNode<SealedMeta> =
|
||||
root(node.seal(), descriptor, rootName)
|
||||
}
|
||||
}
|
||||
|
||||
class FXMetaNode<M : TypedMeta<M>>(
|
||||
override val name: NameToken,
|
||||
override val parent: FXMetaNode<M>?,
|
||||
nodeValue: M? = null,
|
||||
descriptorValue: NodeDescriptor? = null
|
||||
) : FXMeta<M>() {
|
||||
|
||||
/**
|
||||
* A descriptor that could be manually set to the node
|
||||
*/
|
||||
private val innerDescriptorProperty = SimpleObjectProperty(descriptorValue)
|
||||
|
||||
/**
|
||||
* Actual descriptor which holds value inferred from parrent
|
||||
*/
|
||||
val descriptorProperty = objectBinding(innerDescriptorProperty) {
|
||||
value ?: parent?.descriptor?.nodes?.get(this@FXMetaNode.name.body)
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor? by descriptorProperty
|
||||
|
||||
private val innerNodeProperty = SimpleObjectProperty(nodeValue)
|
||||
|
||||
val nodeProperty: ObjectBinding<M?> = objectBinding(innerNodeProperty) {
|
||||
value ?: parent?.node?.get(this@FXMetaNode.name).node
|
||||
}
|
||||
|
||||
val node: M? by nodeProperty
|
||||
|
||||
override val descriptionProperty = innerDescriptorProperty.stringBinding { it?.info ?: "" }
|
||||
|
||||
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
|
||||
|
||||
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
|
||||
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
|
||||
}
|
||||
|
||||
val children = object : ListBinding<FXMeta<M>>() {
|
||||
|
||||
init {
|
||||
bind(nodeProperty, descriptorProperty)
|
||||
|
||||
val listener: (Name, MetaItem?, MetaItem?) -> Unit = { name, _, _ ->
|
||||
if (name.length == 1) invalidate()
|
||||
}
|
||||
|
||||
(node as? Config)?.onChange(this, listener)
|
||||
|
||||
nodeProperty.addListener { _, oldValue, newValue ->
|
||||
if (newValue == null) {
|
||||
(oldValue as? Config)?.removeListener(this)
|
||||
}
|
||||
|
||||
if (newValue is Config) {
|
||||
newValue.onChange(this, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun computeValue(): ObservableList<FXMeta<M>> {
|
||||
val nodeKeys = node?.items?.keys?.toSet() ?: emptySet()
|
||||
val descriptorKeys = descriptor?.items?.keys?.map { NameToken(it) } ?: emptyList()
|
||||
val keys: Set<NameToken> = nodeKeys + descriptorKeys
|
||||
|
||||
val items = keys.map { token ->
|
||||
val actualItem = node?.items?.get(token)
|
||||
val actualDescriptor = descriptor?.items?.get(token.body)
|
||||
|
||||
if (actualItem is MetaItemNode || actualDescriptor is NodeDescriptor) {
|
||||
FXMetaNode(token, this@FXMetaNode)
|
||||
} else {
|
||||
FXMetaValue(token, this@FXMetaNode)
|
||||
}
|
||||
}
|
||||
|
||||
return items.filter(filter).asObservable()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (parent != null) {
|
||||
parent.descriptorProperty.onChange { descriptorProperty.invalidate() }
|
||||
parent.nodeProperty.onChange { nodeProperty.invalidate() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FXMetaValue<M : TypedMeta<M>>(
|
||||
override val name: NameToken,
|
||||
override val parent: FXMetaNode<M>
|
||||
) : FXMeta<M>() {
|
||||
|
||||
public val descriptorProperty = parent.descriptorProperty.objectBinding {
|
||||
it?.values?.get(name.body)
|
||||
}
|
||||
|
||||
/**
|
||||
* A descriptor that could be manually set to the node
|
||||
*/
|
||||
override val descriptor: ValueDescriptor? by descriptorProperty
|
||||
|
||||
//private val innerValueProperty = SimpleObjectProperty(value)
|
||||
|
||||
public val valueProperty = descriptorProperty.objectBinding { descriptor ->
|
||||
parent.node?.get(name).value ?: descriptor?.default
|
||||
}
|
||||
|
||||
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it?.get(name) != null }
|
||||
|
||||
public val value by valueProperty
|
||||
|
||||
override val descriptionProperty = descriptorProperty.stringBinding { it?.info ?: "" }
|
||||
}
|
||||
|
||||
public fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
||||
node?.remove(name.asName())
|
||||
children.invalidate()
|
||||
}
|
||||
|
||||
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
||||
return if (append && token.hasIndex()) {
|
||||
val name = token.asName()
|
||||
val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1
|
||||
val newName = name.withIndex(index.toString())
|
||||
set(newName, Meta.EMPTY)
|
||||
get(newName).node!!
|
||||
} else {
|
||||
this.set(token.asName(), Meta.EMPTY)
|
||||
//FIXME possible concurrency bug
|
||||
get(token).node!!
|
||||
}
|
||||
}
|
||||
|
||||
fun <M : MutableMeta<M>> FXMetaNode<out M>.getOrCreateNode(): M {
|
||||
val node = node
|
||||
return when {
|
||||
node != null -> node
|
||||
parent != null -> parent.getOrCreateNode().createEmptyNode(this.name, descriptor?.multiple == true).also {
|
||||
parent.children.invalidate()
|
||||
}
|
||||
else -> kotlin.error("Orphan empty node is not allowed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <M : MutableMeta<M>> FXMeta<M>.remove() {
|
||||
parent?.node?.remove(name.asName())
|
||||
}
|
||||
|
||||
fun <M : MutableMeta<M>> FXMetaNode<M>.addValue(key: String) {
|
||||
val parent = getOrCreateNode()
|
||||
if (descriptor?.multiple == true) {
|
||||
parent.append(key, Null)
|
||||
} else {
|
||||
parent[key] = Null
|
||||
}
|
||||
}
|
||||
|
||||
fun <M : MutableMeta<M>> FXMetaNode<M>.addNode(key: String) {
|
||||
val parent = getOrCreateNode()
|
||||
if (descriptor?.multiple == true) {
|
||||
parent.append(key, Meta.EMPTY)
|
||||
} else {
|
||||
parent[key] = Meta.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
fun <M : MutableMeta<M>> FXMetaValue<M>.set(value: Value?) {
|
||||
if (descriptor?.multiple == true) {
|
||||
parent.getOrCreateNode().append(this.name.body, value)
|
||||
} else {
|
||||
parent.getOrCreateNode()[this.name] = value
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.binding.BooleanBinding
|
||||
import javafx.beans.binding.ListBinding
|
||||
import javafx.beans.binding.ObjectBinding
|
||||
import javafx.collections.ObservableList
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.ObservableMeta
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A display for meta and descriptor
|
||||
*/
|
||||
public class FXMetaModel<M : Meta>(
|
||||
public val root: M,
|
||||
public val rootDescriptor: MetaDescriptor?,
|
||||
public val defaultRoot: Meta?,
|
||||
public val pathName: Name,
|
||||
public val title: String = pathName.lastOrNull()?.toString() ?: "Meta"
|
||||
) : Comparable<FXMetaModel<*>> {
|
||||
|
||||
private val existingNode = object: ObjectBinding<Meta?>() {
|
||||
override fun computeValue(): Meta? = root[pathName]
|
||||
}
|
||||
|
||||
private val defaultNode: Meta? get() = defaultRoot?.getMeta(pathName)
|
||||
|
||||
public val descriptor: MetaDescriptor? = rootDescriptor?.get(pathName)
|
||||
|
||||
public val children: ListBinding<FXMetaModel<M>> = object : ListBinding<FXMetaModel<M>>() {
|
||||
override fun computeValue(): ObservableList<FXMetaModel<M>> {
|
||||
val nodeKeys = existingNode.get()?.items?.keys?: emptySet()
|
||||
val defaultKeys = defaultNode?.items?.keys ?: emptySet()
|
||||
return (nodeKeys + defaultKeys).map {
|
||||
FXMetaModel(
|
||||
root,
|
||||
rootDescriptor,
|
||||
defaultRoot,
|
||||
pathName + it
|
||||
)
|
||||
}.filter(filter).asObservable()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
//add listener to the root node if possible
|
||||
if (root is ObservableMeta) {
|
||||
root.onChange(this) { changed ->
|
||||
if (changed.startsWith(pathName)) {
|
||||
if (pathName.length == changed.length) existingNode.invalidate()
|
||||
else if (changed.length == pathName.length + 1) children.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public val existsProperty: BooleanBinding = existingNode.isNotNull
|
||||
|
||||
public val exists: Boolean by existsProperty
|
||||
|
||||
public val valueProperty: Binding<Value?> = existingNode.objectBinding {
|
||||
existingNode.get()?.value ?: descriptor?.defaultValue
|
||||
}
|
||||
|
||||
override fun compareTo(other: FXMetaModel<*>): Int = if (this.exists == other.exists) {
|
||||
this.pathName.toString().compareTo(other.pathName.toString())
|
||||
} else {
|
||||
this.exists.compareTo(other.exists)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private val filter: (FXMetaModel<*>) -> Boolean = { cfg ->
|
||||
!(cfg.descriptor?.attributes?.get(MutableMetaEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
|
||||
}
|
||||
|
||||
public fun <M : Meta> root(
|
||||
node: M,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
defaultRoot: Meta? = null,
|
||||
rootName: String = "root"
|
||||
): FXMetaModel<M> = FXMetaModel(node, descriptor, defaultRoot, Name.EMPTY, title = rootName)
|
||||
}
|
||||
}
|
@ -16,45 +16,40 @@
|
||||
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.scene.control.TreeItem
|
||||
import javafx.scene.control.TreeSortMode
|
||||
import javafx.scene.control.TreeTableView
|
||||
import javafx.scene.layout.BorderPane
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.values.string
|
||||
import space.kscience.visionforge.dfIconView
|
||||
import tornadofx.*
|
||||
|
||||
class MetaViewer(val rootNode: FXMetaNode<*>, title: String = "Meta viewer") : Fragment(title,
|
||||
dfIconView
|
||||
) {
|
||||
constructor(meta: Meta, title: String = "Meta viewer"): this(
|
||||
FXMeta.root(
|
||||
meta
|
||||
),title = title)
|
||||
public class MetaViewer(
|
||||
private val rootNode: FXMetaModel<out Meta>,
|
||||
title: String = "Meta viewer"
|
||||
) : Fragment(title, dfIconView) {
|
||||
|
||||
override val root = borderpane {
|
||||
public constructor(meta: Meta, title: String = "Meta viewer") : this(
|
||||
FXMetaModel.root(meta), title = title
|
||||
)
|
||||
|
||||
override val root: BorderPane = borderpane {
|
||||
center {
|
||||
treetableview<FXMeta<*>> {
|
||||
treetableview<FXMetaModel<*>> {
|
||||
isShowRoot = false
|
||||
root = TreeItem(rootNode)
|
||||
populate {
|
||||
when (val fxMeta = it.value) {
|
||||
is FXMetaNode -> {
|
||||
fxMeta.children
|
||||
}
|
||||
is FXMetaValue -> null
|
||||
}
|
||||
val fxMeta = it.value
|
||||
fxMeta.children
|
||||
}
|
||||
root.isExpanded = true
|
||||
sortMode = TreeSortMode.ALL_DESCENDANTS
|
||||
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
|
||||
column("Name", FXMeta<*>::name)
|
||||
column<FXMeta<*>, String>("Value") { cellDataFeatures ->
|
||||
when (val item = cellDataFeatures.value.value) {
|
||||
is FXMetaValue -> item.valueProperty.stringBinding { it?.string ?: "" }
|
||||
is FXMetaNode -> SimpleStringProperty("[node]")
|
||||
}
|
||||
column("Name", FXMetaModel<*>::title)
|
||||
column<FXMetaModel<*>, String>("Value") { cellDataFeatures ->
|
||||
val item = cellDataFeatures.value.value
|
||||
item.valueProperty.stringBinding { it?.string ?: "" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Text
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.remove
|
||||
import space.kscience.visionforge.dfIconView
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A Configuration editor fragment
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class MutableMetaEditor(
|
||||
public val rootNode: FXMetaModel<MutableMeta>,
|
||||
//public val allowNew: Boolean = true,
|
||||
title: String = "Meta editor"
|
||||
) : Fragment(title = title, icon = dfIconView) {
|
||||
//TODO replace parameters by properties
|
||||
//
|
||||
// public constructor(
|
||||
// MutableMeta: MutableMeta,
|
||||
// descriptor: MetaDescriptor?,
|
||||
// title: String = "Configuration editor"
|
||||
// ) : this(FXMetaModel.root(MutableMeta, descriptor = descriptor), title = title)
|
||||
|
||||
override val root: BorderPane = borderpane {
|
||||
center = treetableview<FXMetaModel<MutableMeta>> {
|
||||
root = TreeItem(rootNode)
|
||||
root.isExpanded = true
|
||||
sortMode = TreeSortMode.ALL_DESCENDANTS
|
||||
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
|
||||
populate {
|
||||
it.value.children
|
||||
}
|
||||
column("Name", FXMetaModel<MutableMeta>::title) {
|
||||
setCellFactory {
|
||||
object : TextFieldTreeTableCell<FXMetaModel<MutableMeta>, String>() {
|
||||
override fun updateItem(item: String?, empty: Boolean) {
|
||||
super.updateItem(item, empty)
|
||||
contextMenu?.items?.removeIf { it.text == "Remove" }
|
||||
val content = treeTableRow.item
|
||||
if (!empty) {
|
||||
if (treeTableRow.item != null) {
|
||||
textFillProperty().bind(content.existsProperty.objectBinding {
|
||||
if (it == true) {
|
||||
Color.BLACK
|
||||
} else {
|
||||
Color.GRAY
|
||||
}
|
||||
})
|
||||
if (content.exists) {
|
||||
contextmenu {
|
||||
item("Remove") {
|
||||
action {
|
||||
content.root.remove(content.pathName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>> ->
|
||||
param.value.valueProperty()
|
||||
}.setCellFactory {
|
||||
ValueCell()
|
||||
}
|
||||
|
||||
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, String> ->
|
||||
SimpleStringProperty(param.value.value.descriptor?.info ?: "")
|
||||
}.setCellFactory { param: TreeTableColumn<FXMetaModel<MutableMeta>, String> ->
|
||||
val cell = TreeTableCell<FXMetaModel<MutableMeta>, String>()
|
||||
val text = Text()
|
||||
cell.graphic = text
|
||||
cell.prefHeight = Control.USE_COMPUTED_SIZE
|
||||
text.wrappingWidthProperty().bind(param.widthProperty())
|
||||
text.textProperty().bind(cell.itemProperty())
|
||||
cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNodeDialog(): String? {
|
||||
val dialog = TextInputDialog()
|
||||
dialog.title = "Node name selection"
|
||||
dialog.contentText = "Enter a name for new node: "
|
||||
dialog.headerText = null
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
return result.orElse(null)
|
||||
}
|
||||
|
||||
private fun showValueDialog(): String? {
|
||||
val dialog = TextInputDialog()
|
||||
dialog.title = "Value name selection"
|
||||
dialog.contentText = "Enter a name for new value: "
|
||||
dialog.headerText = null
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
return result.orElse(null)
|
||||
}
|
||||
|
||||
private inner class ValueCell : TreeTableCell<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>?>() {
|
||||
|
||||
public override fun updateItem(item: FXMetaModel<MutableMeta>?, empty: Boolean) {
|
||||
if (!empty) {
|
||||
if (item != null) {
|
||||
text = null
|
||||
val chooser = ValueChooser.build(
|
||||
Global,
|
||||
item.valueProperty,
|
||||
item.descriptor
|
||||
) { value ->
|
||||
item.root.setValue(item.pathName, value)
|
||||
}
|
||||
graphic = chooser.node
|
||||
|
||||
} else {
|
||||
text = null
|
||||
graphic = null
|
||||
}
|
||||
} else {
|
||||
text = null
|
||||
graphic = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* The tag not to display node or value in MutableMetaurator
|
||||
*/
|
||||
public const val NO_CONFIGURATOR_TAG: String = "nocfg"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import javafx.scene.control.TextField
|
||||
import javafx.scene.input.KeyCode
|
||||
import javafx.scene.input.KeyEvent
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.validate
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.*
|
||||
@ -85,7 +86,7 @@ public class TextValueChooser : ValueChooserBase<TextField>() {
|
||||
}
|
||||
|
||||
private fun validate(value: Value): Boolean {
|
||||
return descriptor?.isAllowedValue(value) ?: true
|
||||
return descriptor?.validate(value) ?: true
|
||||
}
|
||||
|
||||
// @Override
|
||||
@ -101,7 +102,7 @@ public class TextValueChooser : ValueChooserBase<TextField>() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object : ValueChooser.Factory {
|
||||
public companion object : ValueChooser.Factory {
|
||||
override val name: Name = "text".asName()
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
TextValueChooser()
|
||||
|
@ -13,11 +13,11 @@ import space.kscience.dataforge.values.Value
|
||||
* @param value Value after change
|
||||
* @param message Message on unsuccessful change
|
||||
*/
|
||||
class ValueCallbackResponse(val success: Boolean, val value: Value, val message: String)
|
||||
public class ValueCallbackResponse(public val success: Boolean, public val value: Value, public val message: String)
|
||||
|
||||
/**
|
||||
* A callback for some visual object trying to change some value
|
||||
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
||||
*/
|
||||
typealias ValueCallback = (Value) -> ValueCallbackResponse
|
||||
public typealias ValueCallback = (Value) -> ValueCallbackResponse
|
||||
|
||||
|
@ -10,10 +10,12 @@ import javafx.beans.value.ObservableValue
|
||||
import javafx.scene.Node
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.meta.descriptors.validate
|
||||
import space.kscience.dataforge.misc.Named
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.visionforge.widget
|
||||
@ -41,8 +43,8 @@ public interface ValueChooser {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val descriptorProperty: ObjectProperty<ValueDescriptor?>
|
||||
public var descriptor: ValueDescriptor?
|
||||
public val descriptorProperty: ObjectProperty<MetaDescriptor?>
|
||||
public var descriptor: MetaDescriptor?
|
||||
|
||||
public val valueProperty: ObjectProperty<Value?>
|
||||
public var value: Value?
|
||||
@ -62,7 +64,7 @@ public interface ValueChooser {
|
||||
|
||||
public fun setCallback(callback: ValueCallback)
|
||||
|
||||
@Type("space.kscience..fx.valueChooserFactory")
|
||||
@Type("space.kscience.dataforge.vis.fx.valueChooserFactory")
|
||||
public interface Factory : Named {
|
||||
public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser
|
||||
}
|
||||
@ -70,7 +72,7 @@ public interface ValueChooser {
|
||||
public companion object {
|
||||
|
||||
private fun findWidgetByType(context: Context, type: String): Factory? {
|
||||
return when (type.toName()) {
|
||||
return when (Name.parse(type)) {
|
||||
TextValueChooser.name -> TextValueChooser
|
||||
ColorValueChooser.name -> ColorValueChooser
|
||||
ComboBoxValueChooser.name -> ComboBoxValueChooser
|
||||
@ -78,7 +80,7 @@ public interface ValueChooser {
|
||||
}
|
||||
}
|
||||
|
||||
private fun build(context: Context, descriptor: ValueDescriptor?): ValueChooser {
|
||||
private fun build(context: Context, descriptor: MetaDescriptor?): ValueChooser {
|
||||
return if (descriptor == null) {
|
||||
TextValueChooser();
|
||||
} else {
|
||||
@ -92,7 +94,7 @@ public interface ValueChooser {
|
||||
descriptor.widget
|
||||
) ?: TextValueChooser()
|
||||
}
|
||||
descriptor.allowedValues.isNotEmpty() -> ComboBoxValueChooser()
|
||||
!descriptor.allowedValues.isNullOrEmpty() -> ComboBoxValueChooser()
|
||||
else -> TextValueChooser()
|
||||
}
|
||||
chooser.descriptor = descriptor
|
||||
@ -103,7 +105,7 @@ public interface ValueChooser {
|
||||
public fun build(
|
||||
context: Context,
|
||||
value: ObservableValue<Value?>,
|
||||
descriptor: ValueDescriptor? = null,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
setter: (Value) -> Unit,
|
||||
): ValueChooser {
|
||||
val chooser = build(context, descriptor)
|
||||
@ -112,7 +114,7 @@ public interface ValueChooser {
|
||||
chooser.setDisplayValue(it ?: Null)
|
||||
}
|
||||
chooser.setCallback { result ->
|
||||
if (descriptor?.isAllowedValue(result) != false) {
|
||||
if (descriptor?.validate(result) != false) {
|
||||
setter(result)
|
||||
ValueCallbackResponse(true, result, "OK")
|
||||
} else {
|
||||
|
@ -8,7 +8,7 @@ package space.kscience.visionforge.editor
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
import org.slf4j.LoggerFactory
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.values.Value
|
||||
import tornadofx.*
|
||||
@ -21,12 +21,10 @@ import tornadofx.*
|
||||
public abstract class ValueChooserBase<out T : Node> : ValueChooser {
|
||||
|
||||
override val node: T by lazy { buildNode() }
|
||||
final override val valueProperty: SimpleObjectProperty<Value> =
|
||||
SimpleObjectProperty<Value>(Null)
|
||||
final override val descriptorProperty: SimpleObjectProperty<ValueDescriptor> =
|
||||
SimpleObjectProperty<ValueDescriptor>()
|
||||
final override val valueProperty: SimpleObjectProperty<Value> = SimpleObjectProperty<Value>(Null)
|
||||
final override val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
|
||||
|
||||
override var descriptor: ValueDescriptor? by descriptorProperty
|
||||
override var descriptor: MetaDescriptor? by descriptorProperty
|
||||
override var value: Value? by valueProperty
|
||||
|
||||
public fun resetValue() {
|
||||
@ -38,7 +36,7 @@ public abstract class ValueChooserBase<out T : Node> : ValueChooser {
|
||||
* @return
|
||||
*/
|
||||
protected fun currentValue(): Value {
|
||||
return value ?: descriptor?.default ?: Null
|
||||
return value ?: descriptor?.defaultValue ?: Null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,61 @@
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.layout.VBox
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.ObservableMutableMeta
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.getStyle
|
||||
import space.kscience.visionforge.styles
|
||||
import tornadofx.*
|
||||
|
||||
public class VisionEditorFragment : Fragment() {
|
||||
|
||||
public val visionProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
|
||||
public var vision: Vision? by visionProperty
|
||||
public val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
|
||||
|
||||
private val configProperty: Binding<ObservableMutableMeta?> = visionProperty.objectBinding { vision ->
|
||||
vision?.meta
|
||||
}
|
||||
|
||||
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
|
||||
it?.let { meta ->
|
||||
val node:FXMetaModel<MutableMeta> = FXMetaModel(
|
||||
meta,
|
||||
vision?.descriptor,
|
||||
vision?.computeProperties(),
|
||||
Name.EMPTY,
|
||||
"Vision properties"
|
||||
)
|
||||
MutableMetaEditor(node).root
|
||||
}
|
||||
}
|
||||
|
||||
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding {
|
||||
VBox().apply {
|
||||
vision?.styles?.forEach { styleName ->
|
||||
val styleMeta = vision?.getStyle(styleName)
|
||||
if (styleMeta != null) {
|
||||
titledpane(styleName, node = MetaViewer(styleMeta).root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val root: Parent = vbox {
|
||||
titledpane("Properties", collapsible = false) {
|
||||
contentProperty().bind(configEditorProperty)
|
||||
}
|
||||
titledpane("Styles", collapsible = false) {
|
||||
visibleWhen(visionProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
|
||||
contentProperty().bind(styleBoxProperty)
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vis
|
||||
}
|
||||
|
||||
|
||||
public class VisualObjectTreeFragment : Fragment() {
|
||||
public class VisionTreeFragment : Fragment() {
|
||||
public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
|
||||
public var item: Vision? by itemProperty
|
||||
|
@ -1,74 +0,0 @@
|
||||
package space.kscience.visionforge.editor
|
||||
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.layout.VBox
|
||||
import space.kscience.dataforge.meta.Config
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MutableItemProvider
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.update
|
||||
import space.kscience.visionforge.*
|
||||
import tornadofx.*
|
||||
|
||||
public class VisualObjectEditorFragment(public val selector: (Vision) -> Meta) : Fragment() {
|
||||
|
||||
public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
|
||||
public var item: Vision? by itemProperty
|
||||
public val descriptorProperty: SimpleObjectProperty<NodeDescriptor> = SimpleObjectProperty<NodeDescriptor>()
|
||||
|
||||
public constructor(
|
||||
item: Vision?,
|
||||
descriptor: NodeDescriptor?,
|
||||
selector: (Vision) -> MutableItemProvider = { it.allProperties() },
|
||||
) : this({ it.describedProperties }) {
|
||||
this.item = item
|
||||
this.descriptorProperty.set(descriptor)
|
||||
}
|
||||
|
||||
private var currentConfig: Config? = null
|
||||
|
||||
private val configProperty: Binding<Config?> = itemProperty.objectBinding { vision ->
|
||||
if (vision == null) return@objectBinding null
|
||||
val meta = selector(vision)
|
||||
val config = Config().apply {
|
||||
update(meta)
|
||||
onChange(this@VisualObjectEditorFragment) { key, _, after ->
|
||||
vision.setProperty(key, after)
|
||||
}
|
||||
}
|
||||
//remember old config reference to cleanup listeners
|
||||
currentConfig?.removeListener(this)
|
||||
currentConfig = config
|
||||
config
|
||||
}
|
||||
|
||||
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
|
||||
it?.let {
|
||||
ConfigEditor(it, descriptorProperty.get()).root
|
||||
}
|
||||
}
|
||||
|
||||
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding {
|
||||
VBox().apply {
|
||||
item?.styles?.forEach { styleName ->
|
||||
val styleMeta = item?.getStyle(styleName)
|
||||
if (styleMeta != null) {
|
||||
titledpane(styleName, node = MetaViewer(styleMeta).root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val root: Parent = vbox {
|
||||
titledpane("Properties", collapsible = false) {
|
||||
contentProperty().bind(configEditorProperty)
|
||||
}
|
||||
titledpane("Styles", collapsible = false) {
|
||||
visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
|
||||
contentProperty().bind(styleBoxProperty)
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.visionforge.computePropertyNode
|
||||
import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE
|
||||
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
|
||||
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY
|
||||
@ -73,7 +74,7 @@ public class FX3DPlugin : AbstractPlugin() {
|
||||
is PolyLine -> PolyLine3D(
|
||||
obj.points.map { Point3D(it.x, it.y, it.z) },
|
||||
obj.thickness.toFloat(),
|
||||
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color()
|
||||
obj.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.color()
|
||||
).apply {
|
||||
this.meshView.cullFace = CullFace.FRONT
|
||||
}
|
||||
|
@ -3,11 +3,15 @@ package space.kscience.visionforge.solid
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Material
|
||||
import javafx.scene.paint.PhongMaterial
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.double
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.int
|
||||
import space.kscience.dataforge.values.string
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.solid.FXMaterials.GREY
|
||||
|
||||
public object FXMaterials {
|
||||
public val RED: PhongMaterial = PhongMaterial().apply {
|
||||
@ -26,46 +30,41 @@ public object FXMaterials {
|
||||
}
|
||||
|
||||
public val BLUE: PhongMaterial = PhongMaterial(Color.BLUE)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
* @param opacity default opacity
|
||||
*/
|
||||
public fun MetaItem.color(opacity: Double = 1.0): Color {
|
||||
return when (this) {
|
||||
is MetaItemValue -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.int
|
||||
val red = int and 0x00ff0000 shr 16
|
||||
val green = int and 0x0000ff00 shr 8
|
||||
val blue = int and 0x000000ff
|
||||
Color.rgb(red, green, blue, opacity)
|
||||
} else {
|
||||
Color.web(this.value.string)
|
||||
}
|
||||
is MetaItemNode -> {
|
||||
Color.rgb(
|
||||
node[Colors.RED_KEY]?.int ?: 0,
|
||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||
node[Colors.BLUE_KEY]?.int ?: 0,
|
||||
node[SolidMaterial.OPACITY_KEY]?.double ?: opacity
|
||||
)
|
||||
}
|
||||
public fun Meta.color(opacity: Double = 1.0): Color = value?.let {
|
||||
if (it.type == ValueType.NUMBER) {
|
||||
val int = it.int
|
||||
val red = int and 0x00ff0000 shr 16
|
||||
val green = int and 0x0000ff00 shr 8
|
||||
val blue = int and 0x000000ff
|
||||
Color.rgb(red, green, blue, opacity)
|
||||
} else {
|
||||
Color.web(it.string)
|
||||
}
|
||||
}
|
||||
} ?: Color.rgb(
|
||||
this[Colors.RED_KEY]?.int ?: 0,
|
||||
this[Colors.GREEN_KEY]?.int ?: 0,
|
||||
this[Colors.BLUE_KEY]?.int ?: 0,
|
||||
this[SolidMaterial.OPACITY_KEY]?.double ?: opacity
|
||||
)
|
||||
|
||||
/**
|
||||
* Infer FX material based on meta item
|
||||
*/
|
||||
public fun MetaItem?.material(): Material {
|
||||
return when (this) {
|
||||
null -> FXMaterials.GREY
|
||||
is MetaItemValue -> PhongMaterial(color())
|
||||
is MetaItemNode -> PhongMaterial().apply {
|
||||
val opacity = node[SolidMaterial.OPACITY_KEY].double ?: 1.0
|
||||
diffuseColor = node[SolidMaterial.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
||||
specularColor = node[SolidMaterial.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
|
||||
}
|
||||
public fun Meta?.material(): Material {
|
||||
if (this == null) return GREY
|
||||
return value?.let {
|
||||
PhongMaterial(color())
|
||||
} ?: PhongMaterial().apply {
|
||||
val opacity = get(SolidMaterial.OPACITY_KEY).double ?: 1.0
|
||||
diffuseColor = get(SolidMaterial.COLOR_KEY)?.color(opacity) ?: Color.DARKGREY
|
||||
specularColor = get(SolidMaterial.SPECULAR_COLOR_KEY)?.color(opacity) ?: Color.WHITE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,10 @@ package space.kscience.visionforge.solid
|
||||
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.cutFirst
|
||||
import space.kscience.dataforge.names.firstOrNull
|
||||
import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
import kotlin.reflect.KClass
|
||||
@ -14,9 +17,9 @@ public class FXReferenceFactory(public val plugin: FX3DPlugin) : FX3DFactory<Sol
|
||||
val prototype = obj.prototype
|
||||
val node = plugin.buildNode(prototype)
|
||||
|
||||
obj.onPropertyChange(plugin.context) { name->
|
||||
obj.onPropertyChange { name->
|
||||
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'")
|
||||
val propertyName = name.cutFirst()
|
||||
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
||||
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||
|
@ -5,20 +5,20 @@ import javafx.beans.binding.*
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computePropertyNode
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A caching binding collection for [Vision] properties
|
||||
*/
|
||||
public class VisualObjectFXBinding(private val fx: FX3DPlugin, public val obj: Vision) {
|
||||
private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>()
|
||||
public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vision) {
|
||||
private val bindings = HashMap<Name, ObjectBinding<Meta?>>()
|
||||
|
||||
init {
|
||||
obj.onPropertyChange(fx.context) { name ->
|
||||
obj.onPropertyChange { name ->
|
||||
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
|
||||
Platform.runLater {
|
||||
entry.value.invalidate()
|
||||
@ -33,28 +33,29 @@ public class VisualObjectFXBinding(private val fx: FX3DPlugin, public val obj: V
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun get(key: Name): ObjectBinding<MetaItem?> = bindings.getOrPut(key) {
|
||||
object : ObjectBinding<MetaItem?>() {
|
||||
override fun computeValue(): MetaItem? = obj.getProperty(key)
|
||||
public operator fun get(key: Name): ObjectBinding<Meta?> {
|
||||
return bindings.getOrPut(key) {
|
||||
object : ObjectBinding<Meta?>() {
|
||||
override fun computeValue(): Meta? = obj.computePropertyNode(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun get(key: String): ObjectBinding<TypedMetaItem<*>?> = get(key.toName())
|
||||
public operator fun get(key: String): ObjectBinding<Meta?> = get(Name.parse(key))
|
||||
}
|
||||
|
||||
public fun ObjectBinding<MetaItem?>.value(): Binding<Value?> = objectBinding { it.value }
|
||||
public fun ObjectBinding<MetaItem?>.string(): StringBinding = stringBinding { it.string }
|
||||
public fun ObjectBinding<MetaItem?>.number(): Binding<Number?> = objectBinding { it.number }
|
||||
public fun ObjectBinding<MetaItem?>.double(): Binding<Double?> = objectBinding { it.double }
|
||||
public fun ObjectBinding<MetaItem?>.float(): Binding<Float?> = objectBinding { it.float }
|
||||
public fun ObjectBinding<MetaItem?>.int(): Binding<Int?> = objectBinding { it.int }
|
||||
public fun ObjectBinding<MetaItem?>.long(): Binding<Long?> = objectBinding { it.long }
|
||||
public fun ObjectBinding<MetaItem?>.node(): Binding<Meta?> = objectBinding { it.node }
|
||||
public fun ObjectBinding<Meta?>.value(): Binding<Value?> = objectBinding { it?.value }
|
||||
public fun ObjectBinding<Meta?>.string(): StringBinding = stringBinding { it.string }
|
||||
public fun ObjectBinding<Meta?>.number(): Binding<Number?> = objectBinding { it.number }
|
||||
public fun ObjectBinding<Meta?>.double(): Binding<Double?> = objectBinding { it.double }
|
||||
public fun ObjectBinding<Meta?>.float(): Binding<Float?> = objectBinding { it.float }
|
||||
public fun ObjectBinding<Meta?>.int(): Binding<Int?> = objectBinding { it.int }
|
||||
public fun ObjectBinding<Meta?>.long(): Binding<Long?> = objectBinding { it.long }
|
||||
|
||||
public fun ObjectBinding<MetaItem?>.string(default: String): StringBinding = stringBinding { it.string ?: default }
|
||||
public fun ObjectBinding<MetaItem?>.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default }
|
||||
public fun ObjectBinding<MetaItem?>.float(default: Float): FloatBinding = floatBinding { it.float ?: default }
|
||||
public fun ObjectBinding<MetaItem?>.int(default: Int): IntegerBinding = integerBinding { it.int ?: default }
|
||||
public fun ObjectBinding<MetaItem?>.long(default: Long): LongBinding = longBinding { it.long ?: default }
|
||||
public fun ObjectBinding<Meta?>.string(default: String): StringBinding = stringBinding { it.string ?: default }
|
||||
public fun ObjectBinding<Meta?>.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default }
|
||||
public fun ObjectBinding<Meta?>.float(default: Float): FloatBinding = floatBinding { it.float ?: default }
|
||||
public fun ObjectBinding<Meta?>.int(default: Int): IntegerBinding = integerBinding { it.int ?: default }
|
||||
public fun ObjectBinding<Meta?>.long(default: Long): LongBinding = longBinding { it.long ?: default }
|
||||
|
||||
public fun <T> ObjectBinding<MetaItem?>.transform(transform: (MetaItem) -> T): Binding<T?> = objectBinding { it?.let(transform) }
|
||||
public fun <T> ObjectBinding<Meta?>.transform(transform: (Meta) -> T): Binding<T?> = objectBinding { it?.let(transform) }
|
||||
|
@ -1,12 +1,12 @@
|
||||
package space.kscience.visionforge.gdml
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.names.toName
|
||||
|
||||
import space.kscience.gdml.*
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.html.VisionOutput
|
||||
@ -41,8 +41,8 @@ public class GdmlTransformer {
|
||||
|
||||
internal val styleCache = HashMap<Name, Meta>()
|
||||
|
||||
public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
styleCache.getOrPut(name.toName()) {
|
||||
public fun Solid.registerAndUseStyle(name: String, builder: MutableMeta.() -> Unit) {
|
||||
styleCache.getOrPut(Name.parse(name)) {
|
||||
Meta(builder)
|
||||
}
|
||||
useStyle(name)
|
||||
@ -118,7 +118,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
|
||||
private val proto = SolidGroup()
|
||||
|
||||
private val solids = proto.group(solidsName) {
|
||||
setProperty("edges.enabled", false)
|
||||
setPropertyNode("edges.enabled", false)
|
||||
}
|
||||
|
||||
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
|
||||
@ -441,20 +441,6 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
|
||||
}
|
||||
final.useStyle(rootStyle)
|
||||
|
||||
//inline prototypes
|
||||
// referenceStore.forEach { (protoName, list) ->
|
||||
// val proxy = list.singleOrNull() ?: return@forEach
|
||||
// val parent = proxy.parent as? MutableVisionGroup ?: return@forEach
|
||||
// val token = parent.children.entries.find { it.value == proxy }?.key ?: error("Inconsistent reference cache")
|
||||
// val prototype = proto[protoName] as? Solid ?: error("Inconsistent reference cache")
|
||||
// prototype.parent = null
|
||||
// parent[token] = prototype
|
||||
// prototype.updateFrom(proxy)
|
||||
//
|
||||
// //FIXME update prototype
|
||||
// proto[protoName] = null
|
||||
// }
|
||||
|
||||
final.prototypes {
|
||||
proto.children.forEach { (token, item) ->
|
||||
item.parent = null
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.visionforge.gdml
|
||||
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.gdml.*
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.get
|
||||
@ -23,7 +23,7 @@ class TestCubes {
|
||||
fun testCubesDirect() {
|
||||
val vision = cubes.toVision()
|
||||
// println(Solids.encodeToString(vision))
|
||||
val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) as? Box
|
||||
val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box
|
||||
assertNotNull(smallBoxPrototype)
|
||||
assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
|
||||
val smallBoxVision = vision["composite-111.smallBox"]?.unref as? Box
|
||||
@ -46,7 +46,7 @@ class TestCubes {
|
||||
val vision = cubes.toVision()
|
||||
val serialized = Solids.encodeToString(vision)
|
||||
val deserialized = testContext.visionManager.decodeFromString(serialized) as SolidGroup
|
||||
val smallBox = deserialized.getPrototype("solids.smallBox".toName()) as? Box
|
||||
val smallBox = deserialized.getPrototype(Name.parse("solids.smallBox")) as? Box
|
||||
assertNotNull(smallBox)
|
||||
assertEquals(30.0, smallBox.xSize.toDouble())
|
||||
//println(testContext.visionManager.encodeToString(deserialized))
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.visionforge.gdml
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.gdml.Gdml
|
||||
import space.kscience.gdml.decodeFromStream
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
@ -23,7 +23,7 @@ class TestConvertor {
|
||||
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")!!
|
||||
val gdml = Gdml.decodeFromStream(stream)
|
||||
val vision = gdml.toVision()
|
||||
assertNotNull(vision.getPrototype("solids.box".toName()))
|
||||
assertNotNull(vision.getPrototype(Name.parse("solids.box")))
|
||||
println(Solids.encodeToString(vision))
|
||||
}
|
||||
|
||||
|
44
visionforge-markdown/build.gradle.kts
Normal file
44
visionforge-markdown/build.gradle.kts
Normal file
@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.gradle.mpp")
|
||||
}
|
||||
|
||||
val markdownVersion = "0.2.4"
|
||||
|
||||
kscience {
|
||||
useSerialization()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
js {
|
||||
//binaries.library()
|
||||
binaries.executable()
|
||||
browser {
|
||||
webpackTask {
|
||||
outputFileName = "js/visionforge-markdown.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
val processResourcesTaskName =
|
||||
compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.MAIN_COMPILATION_NAME]
|
||||
.processResourcesTaskName
|
||||
}
|
||||
|
||||
|
||||
val jsBrowserDistribution by tasks.getting
|
||||
|
||||
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||
dependsOn(jsBrowserDistribution)
|
||||
from(jsBrowserDistribution)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-core"))
|
||||
api("org.jetbrains:markdown:$markdownVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionBase
|
||||
|
||||
@Serializable
|
||||
@SerialName("vision.markup")
|
||||
public class VisionOfMarkup(
|
||||
public val format: String = COMMONMARK_FORMAT
|
||||
) : VisionBase() {
|
||||
|
||||
//FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix
|
||||
protected override var properties: MutableMeta? = null
|
||||
|
||||
//TODO add templates
|
||||
|
||||
public var content: String? by meta.string(CONTENT_PROPERTY_KEY)
|
||||
|
||||
public companion object {
|
||||
public val CONTENT_PROPERTY_KEY: Name = "content".asName()
|
||||
public const val COMMONMARK_FORMAT: String = "markdown.commonmark"
|
||||
public const val GFM_FORMAT: String = "markdown.gfm"
|
||||
}
|
||||
}
|
||||
|
||||
internal val markupSerializersModule = SerializersModule {
|
||||
polymorphic(Vision::class) {
|
||||
subclass(VisionOfMarkup.serializer())
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.unsafe
|
||||
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.html.HtmlGenerator
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
/**
|
||||
* Render markdown inside kotlinx-html tag
|
||||
*/
|
||||
public fun <T> TagConsumer<T>.markdown(
|
||||
flavour: MarkdownFlavourDescriptor = CommonMarkFlavourDescriptor(),
|
||||
block: () -> String
|
||||
): T {
|
||||
val src = block()
|
||||
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(src)
|
||||
return div("visionforge-markdown") {
|
||||
unsafe {
|
||||
+HtmlGenerator(src, parsedTree, flavour).generateHtml()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.w3c.dom.Element
|
||||
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.visionforge.*
|
||||
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT
|
||||
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
public val visionClient: VisionClient by require(VisionClient)
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
override val visionSerializersModule: SerializersModule get() = markupSerializersModule
|
||||
|
||||
override fun rateVision(vision: Vision): Int = when (vision) {
|
||||
is VisionOfMarkup -> ElementVisionRenderer.DEFAULT_RATING
|
||||
else -> ElementVisionRenderer.ZERO_RATING
|
||||
}
|
||||
|
||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||
require(vision is VisionOfMarkup) { "The vision is not a markup vision" }
|
||||
val div = document.createElement("div")
|
||||
val flavour = when (vision.format) {
|
||||
COMMONMARK_FORMAT -> CommonMarkFlavourDescriptor()
|
||||
GFM_FORMAT -> GFMFlavourDescriptor()
|
||||
//TODO add new formats via plugins
|
||||
else-> error("Format ${vision.format} not recognized")
|
||||
}
|
||||
vision.useProperty(VisionOfMarkup::content) {
|
||||
div.clear()
|
||||
div.append {
|
||||
markdown(flavour) { vision.content ?: "" }
|
||||
|
||||
}
|
||||
}
|
||||
element.append(div)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<MarkupPlugin> {
|
||||
override val tag: PluginTag = PluginTag("vision.markup", PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<MarkupPlugin> = MarkupPlugin::class
|
||||
override fun invoke(meta: Meta, context: Context): MarkupPlugin = MarkupPlugin()
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ plugins {
|
||||
id("ru.mipt.npm.gradle.mpp")
|
||||
}
|
||||
|
||||
val plotlyVersion = "0.4.3"
|
||||
val plotlyVersion = "0.5.0"
|
||||
|
||||
kscience {
|
||||
useSerialization()
|
||||
|
@ -2,22 +2,23 @@ package space.kscience.visionforge.plotly
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Config
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.plotly.Plot
|
||||
import space.kscience.plotly.Plotly
|
||||
import space.kscience.visionforge.VisionBase
|
||||
import space.kscience.visionforge.html.VisionOutput
|
||||
import space.kscience.visionforge.root
|
||||
|
||||
@Serializable
|
||||
@SerialName("vision.plotly")
|
||||
public class VisionOfPlotly private constructor() : VisionBase() {
|
||||
public constructor(plot: Plot) : this() {
|
||||
properties = plot.config
|
||||
}
|
||||
//FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix
|
||||
override var properties: MutableMeta? = null
|
||||
|
||||
public val plot: Plot get() = Plot(properties ?: Config())
|
||||
public constructor(plot: Plot) : this() {
|
||||
properties = plot.meta
|
||||
}
|
||||
public val plot: Plot get() = Plot(meta)
|
||||
}
|
||||
|
||||
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
|
||||
@ -25,6 +26,4 @@ public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
|
||||
@DFExperimental
|
||||
public inline fun VisionOutput.plotly(
|
||||
block: Plot.() -> Unit,
|
||||
): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block)).apply {
|
||||
root(this@plotly.manager)
|
||||
}
|
||||
): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user