More or less working muon monitor

This commit is contained in:
Alexander Nozik 2023-12-27 12:01:55 +03:00
parent 72ead21ef0
commit 659b9c3525
37 changed files with 370 additions and 364 deletions

View File

@ -1,50 +0,0 @@
@file:JsModule("react-file-drop")
@file:JsNonModule
package drop
import org.w3c.dom.DragEvent
import org.w3c.files.FileList
import react.Component
import react.Props
import react.State
sealed external class DropEffects {
@JsName("copy")
object Copy : DropEffects
@JsName("move")
object Move : DropEffects
@JsName("link")
object Link : DropEffects
@JsName("none")
object None : DropEffects
}
external interface FileDropProps : Props {
var className: String?
var targetClassName: String?
var draggingOverFrameClassName: String?
var draggingOverTargetClassName: String?
// var frame?: Exclude<HTMLElementTagNameMap[keyof HTMLElementTagNameMap], HTMLElement> | HTMLDocument;
var onFrameDragEnter: ((event: DragEvent) -> Unit)?
var onFrameDragLeave: ((event: DragEvent) -> Unit)?
var onFrameDrop: ((event: DragEvent) -> Unit)?
// var onDragOver: ReactDragEventHandler<HTMLDivElement>?
// var onDragLeave: ReactDragEventHandler<HTMLDivElement>?
var onDrop: ((files: FileList?, event: dynamic) -> Unit)?//event:DragEvent<HTMLDivElement>)
var dropEffect: DropEffects?
}
external interface FileDropState : State {
var draggingOverFrame: Boolean
var draggingOverTarget: Boolean
}
external class FileDrop : Component<FileDropProps, FileDropState> {
override fun render(): dynamic
}

View File

@ -0,0 +1,87 @@
@file:OptIn(ExperimentalComposeWebApi::class)
package space.kscience.visionforge.gdml.demo
import androidx.compose.runtime.*
import org.jetbrains.compose.web.ExperimentalComposeWebApi
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.name
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.I
import org.jetbrains.compose.web.dom.Input
import org.jetbrains.compose.web.dom.Text
import org.w3c.files.FileList
//https://codepen.io/zahedkamal87/pen/PobNNwE
@Composable
fun FileDrop(
title: String = "Drop files or Click here to select files to upload.",
onFileDrop: (FileList) -> Unit,
) {
var dragOver by remember { mutableStateOf(false) }
Div({
id("dropzone")
style {
border(
width = 0.2.cssRem,
style = LineStyle.Dashed,
color = Color("#6583fe")
)
padding(2.cssRem)
borderRadius(0.25.cssRem)
backgroundColor(Color("#fff"))
textAlign("center")
fontSize(1.5.cssRem)
transitions {
all {
delay(0.25.s)
timingFunction(AnimationTimingFunction.EaseInOut)
properties("background-color")
}
}
cursor("pointer")
}
listOf("drag", "dragstart", "dragend", "dragenter").forEach {
addEventListener(it) { event ->
event.preventDefault()
event.stopPropagation()
}
}
onDragOver { event ->
event.preventDefault()
event.stopPropagation()
dragOver = true
}
onDragLeave { event ->
event.preventDefault()
event.stopPropagation()
dragOver = false
}
onDrop { event ->
event.preventDefault()
event.stopPropagation()
dragOver = false
event.dataTransfer?.files?.let {
onFileDrop(it)
}
}
}) {
I({ classes("bi", "bi-cloud-upload", "dropzone-icon") })
Text(title)
Input(type = InputType.File, attrs = {
style {
display(DisplayStyle.None)
}
classes("dropzone-input")
name("files")
})
}
}
//
//dropzone.addEventListener("click", function(e) {
// dropzone_input.click();
//});

View File

@ -69,8 +69,8 @@ fun GDMLApp(solids: Solids, initialVision: Solid?, selected: Name? = null) {
H2 {
Text("Drag and drop .gdml or .json VisionForge files here")
}
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
FileDrop("(drag file here)") { files ->
val file = files[0]
if (file != null) {
readFileAsync(file)
}

View File

@ -1,30 +0,0 @@
package space.kscience.visionforge.gdml.demo
import drop.FileDrop
import kotlinx.css.*
import org.w3c.files.FileList
import react.RBuilder
import styled.css
import styled.styledDiv
//TODO move styles to inline
fun RBuilder.fileDrop(title: String, action: (files: FileList?) -> Unit) {
styledDiv {
css {
border = Border(style = BorderStyle.dashed, width = 1.px, color = Color.orange)
flexGrow = 0.0
alignContent = Align.center
}
child(FileDrop::class) {
attrs {
onDrop = { files, _ ->
console.info("loaded $files")
action(files)
}
}
+title
}
}
}

View File

@ -1,3 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -1,3 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -1,5 +1,6 @@
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose)
application
}
@ -14,11 +15,22 @@ kscience {
fullStack(
"muon-monitor.js",
jvmConfig = { withJava() },
jsConfig = { useCommonJs() }
// jsConfig = { useCommonJs() },
browserConfig = {
webpackTask{
cssSupport{
enabled = true
}
scssSupport{
enabled = true
}
}
}
)
commonMain {
implementation(projects.visionforgeSolid)
implementation(projects.visionforgeComposeHtml)
}
jvmMain {
implementation("org.apache.commons:commons-math3:3.6.1")

View File

@ -4,12 +4,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import app.softwork.bootstrapcompose.Button
import app.softwork.bootstrapcompose.ButtonGroup
import app.softwork.bootstrapcompose.Container
import kotlinx.browser.window
import kotlinx.coroutines.await
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.P
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text
@ -17,8 +18,6 @@ import org.w3c.fetch.RequestInit
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.compose.FlexColumn
import space.kscience.visionforge.compose.FlexRow
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.edges
@ -51,16 +50,21 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
val events = remember { mutableStateListOf<Event>() }
Div({
style {
height(100.vh - 12.pt)
Container(fluid = true,
attrs = {
style {
height(100.vh - 12.pt)
}
}
}) {
ThreeView(solids, root, selected, mmOptions) {
Tab("Events") {
FlexColumn {
FlexRow {
) {
ThreeView(
solids = solids,
solid = root,
initialSelected = selected,
options = mmOptions,
sidebarTabs = {
Tab("Events") {
ButtonGroup {
Button("Next") {
solids.context.launch {
val event = window.fetch(
@ -84,23 +88,24 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
model.reset()
}
}
}
events.forEach { event ->
P {
Span {
Text(event.id.toString())
}
Text(" : ")
Span({
style {
color(Color.blue)
events.forEach { event ->
P {
Span {
Text(event.id.toString())
}
Text(" : ")
Span({
style {
color(Color.blue)
}
}) {
Text(event.hits.toString())
}
}) {
Text(event.hits.toString())
}
}
}
}
}
)
}
}

View File

@ -1,11 +1,13 @@
package ru.mipt.npm.muon.monitor
import org.jetbrains.compose.web.css.Style
import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.Document
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.compose.VisionForgeStyles
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication
@ -22,8 +24,8 @@ private class MMDemoApp : Application {
val model = Model(visionManager)
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
renderComposable(element) {
renderComposable("app") {
Style(VisionForgeStyles)
MMApp(context.request(Solids), model)
}
}

View File

@ -1,3 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -16,6 +16,12 @@ kotlin {
js(IR) {
browser {
webpackTask {
cssSupport{
enabled = true
}
scssSupport{
enabled = true
}
mainOutputFileName.set("js/visionforge-playground.js")
}
}

View File

@ -1,23 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
const path = require('path');
config.module.rules.push(...ringConfig.module.rules)
config.module.rules.push(
{
test: /\.css$/,
exclude: [
path.resolve(__dirname, "../../node_modules/@jetbrains/ring-ui")
],
use: [
{
loader: 'style-loader',
options: {}
},
{
loader: 'css-loader',
options: {}
}
]
}
)

View File

@ -41,11 +41,11 @@ dependencyResolutionManagement {
include(
// ":ui",
":ui:react",
":ui:ring",
// ":ui:react",
// ":ui:ring",
// ":ui:material",
":ui:bootstrap",
":visionforge-compose",
// ":ui:bootstrap",
":visionforge-compose-html",
":visionforge-core",
":visionforge-solid",
// ":visionforge-fx",

View File

@ -1,10 +1,9 @@
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose)
}
kscience{
kscience {
jvm()
js()
// wasm()
@ -13,22 +12,22 @@ kscience{
kotlin {
// android()
sourceSets {
commonMain{
dependencies{
commonMain {
dependencies {
api(projects.visionforgeCore)
api(compose.runtime)
}
}
val jvmMain by getting {
dependencies {
api(compose.runtime)
api(compose.foundation)
api(compose.material)
api(compose.preview)
}
}
val jsMain by getting{
val jsMain by getting {
dependencies {
api(compose.html.core)
api("app.softwork:bootstrap-compose:0.1.15")

View File

@ -0,0 +1,88 @@
package space.kscience.visionforge.compose
import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Card
import app.softwork.bootstrapcompose.NavbarLink
import app.softwork.bootstrapcompose.Styling
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLDivElement
public class ComposeTab(
public val key: String,
public val title: ContentBuilder<HTMLAnchorElement>,
public val disabled: Boolean,
public val content: ContentBuilder<HTMLDivElement>,
)
@Composable
public fun Tabs(
tabs: List<ComposeTab>,
activeKey: String,
styling: (Styling.() -> Unit)? = null,
attrs: AttrBuilderContext<HTMLDivElement>? = null,
) {
var active by remember(activeKey) { mutableStateOf(activeKey) }
val activeTab by derivedStateOf { tabs.find { it.key == active } }
Card(
styling,
attrs,
header = {
Ul({ classes("nav", "nav-tabs", "card-header-tabs") }) {
tabs.forEach { tab ->
Li({
classes("nav-item")
}) {
NavbarLink(
active = active == tab.key,
disabled = tab.disabled,
attrs = {
onClick { event ->
event.preventDefault()
active = tab.key
}
}
) {
tab.title.invoke(this)
}
}
}
}
}
) {
activeTab?.content?.invoke(this)
}
}
public class TabsBuilder {
internal val tabs: MutableList<ComposeTab> = mutableListOf()
@Composable
public fun Tab(
key: String,
label: ContentBuilder<HTMLAnchorElement> = { Text(key) },
disabled: Boolean = false,
content: ContentBuilder<HTMLDivElement>,
) {
tabs.add(ComposeTab(key, label, disabled, content))
}
public fun addTab(tab: ComposeTab) {
tabs.add(tab)
}
}
@Composable
public fun Tabs(
activeKey: String? = null,
styling: (Styling.() -> Unit)? = null,
attrs: AttrBuilderContext<HTMLDivElement>? = null,
builder: @Composable TabsBuilder.() -> Unit,
) {
val result = TabsBuilder().apply { builder() }
Tabs(result.tabs, activeKey ?: result.tabs.firstOrNull()?.key ?: "", styling, attrs)
}

View File

@ -5,7 +5,7 @@ import org.jetbrains.compose.web.css.*
@OptIn(ExperimentalComposeWebApi::class)
public object TreeStyles : StyleSheet() {
public object TreeStyles : StyleSheet(VisionForgeStyles) {
/**
* Remove default bullets
*/
@ -46,13 +46,11 @@ public object TreeStyles : StyleSheet() {
public val treeItem: String by style {
alignItems(AlignItems.Center)
paddingLeft(10.px)
border {
left {
width(1.px)
color(Color.lightgray)
style = LineStyle.Dashed
}
}
property("border-left", CSSBorder().apply{
width(1.px)
color(Color.lightgray)
style = LineStyle.Dashed
})
}
public val treeLabel: String by style {
@ -75,7 +73,7 @@ public object TreeStyles : StyleSheet() {
alignSelf(AlignSelf.Stretch)
marginAll(1.px, 5.px)
backgroundColor(Color.white)
border{
border {
style(LineStyle.Solid)
}
borderRadius(2.px)
@ -84,7 +82,7 @@ public object TreeStyles : StyleSheet() {
cursor("pointer")
disabled {
cursor("auto")
border{
border {
style(LineStyle.Dashed)
}
color(Color.lightgray)

View File

@ -0,0 +1,7 @@
package space.kscience.visionforge.compose
import org.jetbrains.compose.web.css.StyleSheet
public object VisionForgeStyles: StyleSheet() {
}

View File

@ -1,102 +0,0 @@
package space.kscience.visionforge.compose
import androidx.compose.runtime.*
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLLIElement
public class ComposeTab(
public val key: String,
public val title: String,
public val content: ContentBuilder<HTMLDivElement>,
public val disabled: Boolean,
public val titleExt: ContentBuilder<HTMLLIElement>,
)
@Composable
public fun Tabs(tabs: List<ComposeTab>, activeKey: String) {
var active by remember(activeKey) { mutableStateOf(activeKey) }
Div({ classes("card", "text-center") }) {
Div({ classes("card-header") }) {
Ul({ classes("nav", "nav-tabs", "card-header-tabs") }) {
tabs.forEach { tab ->
Li({
classes("nav-item")
}) {
A(attrs = {
classes("nav-link")
if (active == tab.key) {
classes("active")
}
if (tab.disabled) {
classes("disabled")
}
onClick {
active = tab.key
}
}) {
Text(tab.title)
}
tab.titleExt.invoke(this)
}
}
}
}
tabs.find { it.key == active }?.let { tab ->
Div({ classes("card-body") }) {
tab.content.invoke(this)
}
}
}
}
public class TabBuilder internal constructor(public val key: String) {
private var title: String = key
public var disabled: Boolean = false
private var content: ContentBuilder<HTMLDivElement> = {}
private var titleExt: ContentBuilder<HTMLLIElement> = {}
@Composable
public fun Content(content: ContentBuilder<HTMLDivElement>) {
this.content = content
}
@Composable
public fun Title(title: String, titleExt: ContentBuilder<HTMLLIElement> = {}) {
this.title = title
this.titleExt = titleExt
}
internal fun build(): ComposeTab = ComposeTab(
key,
title,
content,
disabled,
titleExt
)
}
public class TabsBuilder {
public var active: String = ""
internal val tabs: MutableList<ComposeTab> = mutableListOf()
@Composable
public fun Tab(key: String, builder: @Composable TabBuilder.() -> Unit) {
tabs.add(TabBuilder(key).apply { builder() }.build())
}
public fun addTab(tab: ComposeTab) {
tabs.add(tab)
}
}
@Composable
public fun Tabs(builder: @Composable TabsBuilder.() -> Unit) {
val result = TabsBuilder().apply { builder() }
Tabs(result.tabs, result.active)
}

View File

@ -7,6 +7,16 @@ description = "Jupyter api artifact including all common modules"
kscience {
fullStack(
"js/visionforge-jupyter-common.js",
browserConfig = {
webpackTask {
cssSupport{
enabled = true
}
scssSupport {
enabled = true
}
}
}
)
dependencies {
api(projects.visionforgeSolid)

View File

@ -1,24 +0,0 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
const path = require('path');
config.module.rules.push(...ringConfig.module.rules)
config.module.rules.push(
{
test: /\.css$/,
exclude: [
path.resolve(__dirname, "../../node_modules/@jetbrains/ring-ui")
],
use: [
{
loader: 'style-loader',
options: {}
},
{
loader: 'css-loader',
options: {}
}
]
}
)

View File

@ -7,7 +7,7 @@ import space.kscience.dataforge.meta.boolean
public class Canvas3DUIScheme : Scheme() {
public var enabled: Boolean by boolean{true}
public var enabled: Boolean by boolean { true }
public companion object : SchemeSpec<Canvas3DUIScheme>(::Canvas3DUIScheme)
}

View File

@ -7,8 +7,14 @@ val tablesVersion = "0.3.0"
kscience {
jvm()
js {
useCommonJs()
binaries.library()
browser {
webpackTask{
scssSupport {
enabled = true
}
}
}
}
useSerialization()
@ -17,8 +23,8 @@ kscience {
api("space.kscience:tables-kt:${tablesVersion}")
}
jsMain {
implementation(npm("tabulator-tables", "5.5.2"))
implementation(npm("@types/tabulator-tables", "5.5.3"))
api(npm("tabulator-tables", "5.5.2"))
api(npm("@types/tabulator-tables", "5.5.3"))
}
}

View File

@ -5,6 +5,7 @@
"NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING")
@file:JsModule("tabulator-tables")
@file:JsNonModule
package tabulator

View File

@ -14,7 +14,7 @@ kscience {
commonMain {
api(projects.visionforgeSolid)
api(projects.visionforgeCompose)
api(projects.visionforgeComposeHtml)
}
jsMain {

View File

@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.*

View File

@ -1,6 +1,9 @@
package space.kscience.visionforge.solid.three.compose
import androidx.compose.runtime.Composable
import app.softwork.bootstrapcompose.Column
import app.softwork.bootstrapcompose.Layout.Height
import app.softwork.bootstrapcompose.Row
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.Button
import org.jetbrains.compose.web.dom.Text
@ -18,8 +21,8 @@ internal fun CanvasControls(
vision: Vision?,
options: Canvas3DOptions,
) {
FlexColumn {
FlexRow({
Column {
Row(attrs = {
style {
border {
width(1.px)
@ -64,8 +67,11 @@ public fun ThreeControls(
onSelect: (Name?) -> Unit,
tabBuilder: @Composable TabsBuilder.() -> Unit = {},
) {
Tabs {
active = "Tree"
Tabs(
styling = {
Layout.height = Height.Full
}
) {
vision?.let { vision ->
Tab("Tree") {
CardTitle("Vision tree")

View File

@ -2,6 +2,10 @@ package space.kscience.visionforge.solid.three.compose
import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Card
import app.softwork.bootstrapcompose.Column
import app.softwork.bootstrapcompose.Layout.Height
import app.softwork.bootstrapcompose.Layout.Width
import app.softwork.bootstrapcompose.Row
import kotlinx.dom.clear
import org.jetbrains.compose.web.ExperimentalComposeWebApi
import org.jetbrains.compose.web.css.*
@ -90,24 +94,27 @@ public fun ThreeView(
if (optionsSnapshot.controls.enabled) {
FlexRow({
style {
height(100.percent)
width(100.percent)
flexWrap(FlexWrap.Wrap)
alignItems(AlignItems.Stretch)
alignContent(AlignContent.Stretch)
}
}) {
FlexColumn({
style {
height(100.percent)
minWidth(600.px)
flex(10, 1, 600.px)
position(Position.Relative)
Row(
styling = {
Layout {
width = Width.Full
height = Height.Full
}
}) {
}
) {
Column(
styling = {
Layout {
height = Height.Full
}
},
attrs = {
style {
position(Position.Relative)
minWidth(600.px)
}
}
) {
if (solid == null) {
Div({
style {
@ -143,61 +150,68 @@ public fun ThreeView(
}
selectedVision?.let { vision ->
Div({
style {
position(Position.Absolute)
top(5.px)
right(5.px)
width(450.px)
Card(
attrs = {
style {
position(Position.Absolute)
top(5.px)
right(5.px)
width(450.px)
}
},
headerAttrs = {
// border = true
},
header = {
NameCrumbs(selected) { selected = it }
},
footer = {
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
P {
B { Text("Styles: ") }
Text(styles.joinToString(separator = ", "))
}
}
}
}) {
Card(
headerAttrs = {
// border = true
) {
PropertyEditor(
scope = solids.context,
meta = vision.properties.root(),
getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
},
header = {
NameCrumbs(selected) { selected = it }
}
) {
PropertyEditor(
scope = solids.context,
meta = vision.properties.root(),
getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
},
updates = vision.properties.changes,
rootDescriptor = vision.descriptor
)
}
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
P {
B { Text("Styles: ") }
Text(styles.joinToString(separator = ", "))
}
}
updates = vision.properties.changes,
rootDescriptor = vision.descriptor
)
}
}
}
}
FlexColumn({
style {
paddingAll(4.px)
minWidth(400.px)
height(100.percent)
overflowY("auto")
flex(1, 10, 300.px)
Column(
auto = true,
styling = {
Layout {
height = Height.Full
}
},
attrs = {
style {
paddingAll(4.px)
minWidth(400.px)
height(100.percent)
overflowY("auto")
}
}
) {
ThreeControls(solid, optionsSnapshot, selected, onSelect = { selected = it }, tabBuilder = sidebarTabs)
}
}) {
ThreeControls(solid, optionsSnapshot, selected, onSelect = { selected = it }, tabBuilder = sidebarTabs)
}
} else {
SimpleThreeView(solids.context, optionsSnapshot, solid, selected)

View File

@ -10,7 +10,7 @@ kscience {
commonMain {
api(projects.visionforgeSolid)
api(projects.visionforgeCompose)
api(projects.visionforgeComposeHtml)
}
jvmMain{
@ -19,6 +19,8 @@ kscience {
jsMain{
api(projects.visionforgeThreejs)
implementation(npm("file-saver","2.0.5"))
implementation(npm("@types/file-saver", "2.0.7"))
compileOnly(npm("webpack-bundle-analyzer","4.5.0"))
}
}