Initial commit

main
kalle 2023-04-09 00:12:04 +02:00
commit 63f6a55de1
34 changed files with 10530 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.gradle/
.idea/
build/

33
build.gradle.kts Normal file
View File

@ -0,0 +1,33 @@
plugins {
kotlin("js") version "1.6.10"
kotlin("plugin.serialization") version "1.6.10"
}
group = "nl.kallestruik"
version = "1.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:17.0.2-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:17.0.2-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlin-wrappers:kotlin-styled:5.3.1-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-router-dom:5.2.0-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlin-wrappers:kotlin-redux:4.1.0-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-redux:7.2.4-pre.240-kotlin-1.5.30")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
}
kotlin {
js(IR) {
binaries.executable()
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
}
}

2
gradle.properties Normal file
View File

@ -0,0 +1,2 @@
kotlin.code.style=official
kotlin.js.generate.executable.default=false

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

3520
kotlin-js-store/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

3
settings.gradle.kts Normal file
View File

@ -0,0 +1,3 @@
rootProject.name = "DConfig-Web"

26
src/main/kotlin/Client.kt Normal file
View File

@ -0,0 +1,26 @@
import components.jsonSchemaRoot
import react.dom.render
import kotlinx.browser.document
import kotlinx.browser.window
fun main() {
//TODO:
// - Parse and render description
// - Check constraints?
// - Defaults?
// - Make things look nice
window.onload = {
render(document.getElementById("root")) {
jsonSchemaRoot("/arena.schema.json")
// child(SchemaPart::class) {
// attrs {
// source = "/arena.schema.json"
// name = "ROOT"
// required = true
// }
// }
}
}
}

View File

@ -0,0 +1,6 @@
import kotlinx.serialization.json.JsonElement
data class NameChangeEvent(
val id: String,
val name: String,
)

View File

@ -0,0 +1,219 @@
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.w3c.dom.HTMLInputElement
import org.w3c.xhr.XMLHttpRequest
import react.*
import react.dom.option
import schema.JsonSchema
import schema.JsonType
import styled.*
external interface SchemaPartProps : Props {
var source: String?
var name: String
var schema: JsonSchema?
var required: Boolean
}
data class SchemaPartState(
var source: String?,
var name: String,
var schema: JsonSchema?,
var required: Boolean,
var arrayItems: MutableList<JsonSchema> = mutableListOf(),
var mapItems: MutableMap<String, JsonSchema> = mutableMapOf(),
var nextMapItem: String= "",
): State
class SchemaPart(props: SchemaPartProps) : RComponent<SchemaPartProps, SchemaPartState>(props) {
init {
this.state = SchemaPartState(
source = props.source,
name = props.name,
schema = props.schema,
required = props.required,
)
val json = Json {
ignoreUnknownKeys = true
prettyPrint = true
}
val xmlHttp = XMLHttpRequest()
if (state.source != null) {
xmlHttp.open("GET", "${props.source}")
xmlHttp.onload = {
val schema: JsonSchema = json.decodeFromString(xmlHttp.responseText)
setState {
this.schema = schema
}
}
xmlHttp.send()
}
}
override fun RBuilder.render() {
if (state.name == "\$schema")
return
styledDiv {
css {
+SchemaPartStyles.schemaPart
}
if (state.schema?.type == JsonType.NUMBER) {
styledDiv {
if (state.required)
+"*"
+state.name
styledInput(InputType.number) {
}
}
return@styledDiv
}
if (state.schema?.type == JsonType.STRING) {
styledDiv {
if (state.required)
+"*"
+state.name
if (state.schema?.enum?.isNotEmpty() == true) {
styledSelect {
for (opt in state.schema!!.enum) {
option("", opt)
}
}
} else {
styledInput(InputType.text) {
}
}
}
return@styledDiv
}
if (state.schema?.type == JsonType.BOOLEAN) {
styledDiv {
if (state.required)
+"*"
+state.name
styledInput(InputType.checkBox) {
}
}
return@styledDiv
}
styledSpan {
css {
+SchemaPartStyles.schemaName
}
if (state.required)
+"*"
+state.name
}
styledDiv {
var type = state.schema?.type
if (type == null || type == JsonType.UNKNOWN)
type = state.schema?.conditionIf?.type
if (!(type == JsonType.OBJECT || type == JsonType.ARRAY))
+"Type: $type"
}
if (state.schema != null) {
// Object properties
for (property in state.schema!!.properties) {
child(SchemaPart::class) {
attrs {
name = property.key
schema = property.value
required = state.schema?.required?.contains(property.key) ?: false
}
}
}
// Array entries
if (state.schema?.type == JsonType.ARRAY) {
styledButton {
+"+"
attrs.onClickFunction = {
setState {
arrayItems.add(state.schema!!.items!!)
}
}
}
}
// Object map elements
if (state.schema?.additionalProperties != null) {
styledInput {
attrs.onChangeFunction = {
state.nextMapItem = (it.target as HTMLInputElement).value
}
}
styledButton {
+"+"
attrs.onClickFunction = {
setState {
mapItems[state.nextMapItem] = state.schema!!.additionalProperties!!
}
}
}
}
}
for (child in state.arrayItems) {
child(SchemaPart::class) {
attrs {
name = "Item"
schema = child
}
}
}
for (child in state.mapItems) {
child(SchemaPart::class) {
attrs {
name = child.key
schema = child.value
}
}
}
}
// styledDiv {
// css {
// +WelcomeStyles.textContainer
// }
// +"Hello, ${state.name}"
// }
// styledInput {
// css {
// +WelcomeStyles.textInput
// }
// attrs {
// type = InputType.text
// value = state.name
// onChangeFunction = { event ->
// setState(
// WelcomeState(name = (event.target as HTMLInputElement).value)
// )
// }
// }
// }
}
}

View File

@ -0,0 +1,17 @@
import kotlinx.css.*
import styled.StyleSheet
object SchemaPartStyles : StyleSheet("SchemaPartStyles", isStatic = true) {
val schemaPart by css {
margin(0.px, 0.px, 10.px, 20.px)
padding(5.px)
}
val schemaName by css {
padding(5.px)
backgroundColor = rgba(0, 0, 0, 0.2)
}
}

107
src/main/kotlin/Styles.kt Normal file
View File

@ -0,0 +1,107 @@
import kotlinx.css.*
import kotlinx.css.properties.border
import styled.CssHolder
import styled.StyleSheet
object Styles : StyleSheet("Styles", isStatic = true) {
val inputContainer by css {
display = Display.inlineBlock
position = Position.relative
textAlign = TextAlign.left
width = LinearDimension.fitContent
paddingTop = 10.px
margin(5.px, 10.px, 5.px, 10.px)
input {
display = Display.block
border(2.px, BorderStyle.solid, Color.white, 5.px)
padding(10.px)
backgroundColor = rgb(21, 21, 21)
color = Color.white
fontWeight = FontWeight.normal
focus {
outline = Outline.none
}
}
select {
display = Display.block
border(2.px, BorderStyle.solid, Color.white, 5.px)
padding(10.px)
backgroundColor = rgb(21, 21, 21)
color = Color.white
fontWeight = FontWeight.normal
focus {
outline = Outline.none
}
}
label {
position = Position.absolute
textTransform = TextTransform.capitalize
top = (-5).px
left = 10.px
padding(0.px, 2.px)
backgroundColor = rgb(21, 21, 21)
color = Color.white
}
focusWithin {
color = hex(0xCD23F8)
input {
borderColor = hex(0xCD23F8)
}
select {
borderColor = hex(0xCD23F8)
}
}
}
val jsonContainer by css {
margin(10.px, 20.px, 10.px, 20.px)
}
val objectHeader by css {
display = Display.flex
alignContent = Align.center
alignItems = Align.center
justifyContent = JustifyContent.spaceBetween
backgroundColor = rgb(32, 32, 32)
padding(5.px)
}
val objectContainer by css {
borderWidth = 2.px
borderStyle = BorderStyle.solid
margin(10.px)
}
val descriptionContainer by css {
display = Display.flex
margin(10.px)
backgroundColor = rgb(6, 25, 80)
}
val descriptionIconContainer by css {
display = Display.inlineFlex
alignItems = Align.center
alignContent = Align.center
backgroundColor = hex(0x114683)
boxSizing = BoxSizing.borderBox
padding(15.px)
}
val descriptionText by css {
display = Display.inlineBlock
padding(10.px)
alignSelf = Align.center
}
val descriptionLine by css {
margin(0.px)
}
}

View File

@ -0,0 +1,6 @@
import kotlinx.serialization.json.JsonElement
data class ValueChangeEvent(
val id: String,
val value: JsonElement,
)

View File

@ -0,0 +1,47 @@
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement
import react.Props
import react.RBuilder
import react.RComponent
import react.State
import react.dom.attrs
import styled.css
import styled.styledDiv
import styled.styledInput
external interface WelcomeProps : Props {
var name: String
}
data class WelcomeState(val name: String) : State
class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(props) {
init {
state = WelcomeState(props.name)
}
override fun RBuilder.render() {
styledDiv {
css {
+WelcomeStyles.textContainer
}
+"Hello, ${state.name}"
}
styledInput {
css {
+WelcomeStyles.textInput
}
attrs {
type = InputType.text
value = state.name
onChangeFunction = { event ->
setState(
WelcomeState(name = (event.target as HTMLInputElement).value)
)
}
}
}
}
}

View File

@ -0,0 +1,17 @@
import kotlinx.css.*
import styled.StyleSheet
object WelcomeStyles : StyleSheet("WelcomeStyles", isStatic = true) {
val textContainer by css {
padding(5.px)
backgroundColor = rgb(8, 97, 22)
color = rgb(56, 246, 137)
}
val textInput by css {
margin(vertical = 5.px)
fontSize = 14.px
}
}

View File

@ -0,0 +1,67 @@
package components
import Styles
import kotlinx.html.classes
import react.Props
import react.RBuilder
import react.RComponent
import react.State
import react.dom.attrs
import styled.css
import styled.styledDiv
import styled.styledI
import styled.styledP
external interface DescriptionBlockProps: Props {
var description: List<String>
}
class DescriptionBlock(props: DescriptionBlockProps): RComponent<DescriptionBlockProps, State>() {
override fun RBuilder.render() {
if (props.description.isEmpty())
return
styledDiv {
css {
+Styles.descriptionContainer
}
styledDiv {
css {
+Styles.descriptionIconContainer
}
styledI {
attrs {
classes = setOf("ri-information-line", "ri-xl")
}
}
}
styledDiv {
css {
+Styles.descriptionText
}
for (line in props.description) {
styledP {
css {
+Styles.descriptionLine
}
+line
}
}
}
}
}
}
fun RBuilder.descriptionBlock(description: List<String>?) {
child(DescriptionBlock::class) {
attrs {
this.description = description?: listOf()
}
}
}

View File

@ -0,0 +1,159 @@
package components
import ValueChangeEvent
import kotlinx.css.*
import kotlinx.css.properties.border
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.buildJsonArray
import react.*
import react.dom.attrs
import schema.ConfigSchema
import styled.*
import kotlin.random.Random
external interface JsonArrayProps: Props {
var schema: ConfigSchema
var name: String
var data: kotlinx.serialization.json.JsonArray?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var level: Int
}
data class JsonArrayState(
var entries: MutableMap<Int, ConfigSchema> = mutableMapOf(),
var data: kotlinx.serialization.json.JsonArray?,
var realData: MutableMap<Int, JsonElement> = mutableMapOf(),
var showChildren: Boolean = true,
var nextId: Int = 0,
): State
class JsonArray(props: JsonArrayProps): RComponent<JsonArrayProps, JsonArrayState>() {
init {
this.state = JsonArrayState(
data = props.data,
)
props.data?.forEachIndexed { index, element ->
state.entries[index] = props.schema.items!!
state.realData[index] = element
}
}
override fun RBuilder.render() {
styledDiv {
css {
borderColor = hsl((props.level * 40) % 256, 100, 15)
+Styles.objectContainer
}
styledDiv {
css {
+Styles.objectHeader
}
styledI {
attrs {
classes = if (state.showChildren) {
setOf("ri-arrow-down-s-line", "ri-xl")
} else {
setOf("ri-arrow-right-s-line", "ri-xl")
}
onClickFunction = {
setState {
showChildren = !showChildren
}
}
}
}
//ri-delete-bin-7-line
if (props.name != "") {
styledLabel {
css {
textTransform = TextTransform.capitalize
}
+props.name
}
}
styledI {
css {
color = hex(0x4caf50)
alignSelf = Align.center
}
attrs {
classes = setOf("ri-add-line", "ri-xl")
onClickFunction = {
setState {
entries[state.nextId] = props.schema.items!!
nextId++
showChildren = true
}
}
}
}
}
styledDiv {
css {
if (!state.showChildren)
display = Display.none
}
descriptionBlock(props.schema.description)
state.entries.forEach {
jsonArrayEntry(
schema = it.value,
data = state.realData[it.key],
id = it.key.toString(),
handleChange = ::handleChange,
onDelete = ::handleDelete,
level = props.level + 1,
)
}
}
}
}
fun handleChange(event: ValueChangeEvent) {
state.realData[event.id.toInt()] = event.value
state.data = buildJsonArray {
state.realData.forEach {
add(it.value)
}
}
props.handleChange(ValueChangeEvent(props.id, state.data!!))
}
fun handleDelete(id: String) {
state.realData.remove(id.toInt())
state.data = buildJsonArray {
state.realData.forEach {
add(it.value)
}
}
setState {
entries.remove(id.toInt())
}
props.handleChange(ValueChangeEvent(props.id, state.data!!))
}
}
fun RBuilder.jsonArray(schema: ConfigSchema, name: String, data: kotlinx.serialization.json.JsonArray?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, level: Int) {
child(JsonArray::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
this.level = level
}
}
}

View File

@ -0,0 +1,129 @@
package components
import ValueChangeEvent
import kotlinx.css.*
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.JsonElement
import react.*
import react.dom.attrs
import schema.ConfigSchema
import schema.ConfigValueType
import schema.JsonType
import styled.*
external interface JsonArrayEntryProps: Props {
var name: String
var schema: ConfigSchema
var data: JsonElement?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var onDelete: (id: String) -> Unit
var level: Int
}
data class JsonArrayEntryState(
var data: JsonElement?,
var showChildren: Boolean = true,
): State
class JsonArrayEntry(props: JsonArrayEntryProps): RComponent<JsonArrayEntryProps, JsonArrayEntryState>() {
init {
this.state = JsonArrayEntryState(
data = props.data,
)
}
override fun RBuilder.render() {
styledDiv {
css {
margin(0.px, 0.px, 10.px, 20.px)
padding(5.px)
display = Display.flex
alignContent = Align.center
alignItems = Align.center
}
if (props.schema.type != ConfigValueType.OBJECT)
styledI {
css {
color = Color.red
}
attrs {
classes = setOf("ri-delete-bin-7-line ri-xl")
onClickFunction = {
props.onDelete(props.id)
}
}
}
styledDiv {
css {
display = Display.inlineBlock
if (props.schema.type == ConfigValueType.OBJECT) {
borderColor = hsl((props.level * 40) % 256, 100, 15)
+Styles.objectContainer
}
}
if (props.schema.type == ConfigValueType.OBJECT)
styledDiv {
css {
+Styles.objectHeader
}
styledI {
attrs {
classes = if (state.showChildren) {
setOf("ri-arrow-down-s-line", "ri-xl")
} else {
setOf("ri-arrow-right-s-line", "ri-xl")
}
onClickFunction = {
setState {
showChildren = !showChildren
}
}
}
}
styledI {
css {
color = Color.red
}
attrs {
classes = setOf("ri-delete-bin-7-line")
onClickFunction = {
props.onDelete(props.id)
}
}
}
}
if (state.showChildren)
jsonSchema(
schema = props.schema,
name = "",
data = state.data,
id = props.id,
handleChange = props.handleChange,
level = props.level,
)
}
}
}
}
fun RBuilder.jsonArrayEntry(schema: ConfigSchema, data: JsonElement?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, onDelete: (id: String) -> Unit, level: Int) {
child(JsonArrayEntry::class) {
attrs {
this.schema = schema
this.data = data
this.id = id
this.handleChange = handleChange
this.onDelete = onDelete
this.level = level
}
}
}

View File

@ -0,0 +1,85 @@
package components
import ValueChangeEvent
import kotlinx.css.portal
import kotlinx.html.InputType
import kotlinx.html.id
import kotlinx.html.js.onChangeFunction
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import org.w3c.dom.HTMLInputElement
import react.*
import react.dom.attrs
import schema.ConfigSchema
import schema.JsonType
import styled.css
import styled.styledDiv
import styled.styledInput
import styled.styledLabel
external interface JsonBooleanProps: Props {
var schema: ConfigSchema
var name: String
var data: JsonPrimitive?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
}
data class JsonBooleanState(
var value: Boolean = false,
var data: JsonPrimitive?,
): State
class JsonBoolean(props: JsonBooleanProps): RComponent<JsonBooleanProps, JsonBooleanState>() {
init {
val default = props.schema.default?.booleanOrNull ?: false
this.state = JsonBooleanState(
value = props.data?.booleanOrNull ?: default,
data = props.data,
)
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(state.value)))
}
override fun RBuilder.render() {
styledDiv {
styledInput {
attrs {
id = "${props.id}-input"
type = InputType.checkBox
checked = state.value
onChangeFunction = {
val value = (it.target as HTMLInputElement).checked
setState {
this.value = value
}
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(value)))
}
}
}
if (props.name != "") {
styledLabel {
+props.name
attrs {
htmlFor = "${props.id}-input"
}
}
}
}
}
}
fun RBuilder.jsonBoolean(schema: ConfigSchema, name: String, data: JsonPrimitive?, id: String, handleChange: (event: ValueChangeEvent) -> Unit) {
child(JsonBoolean::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
}
}
}

View File

@ -0,0 +1,219 @@
package components
import NameChangeEvent
import ValueChangeEvent
import kotlinx.css.*
import kotlinx.html.InputType
import kotlinx.html.classes
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.buildJsonObject
import org.w3c.dom.HTMLInputElement
import react.*
import react.dom.attrs
import schema.ConfigSchema
import styled.*
import kotlin.random.Random
external interface JsonMapProps: Props {
var schema: ConfigSchema
var name: String
var data: kotlinx.serialization.json.JsonObject?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var level: Int
}
data class JsonMapState(
var entries: MutableMap<String, ConfigSchema> = mutableMapOf(),
var names: MutableMap<String, String> = mutableMapOf(),
var data: kotlinx.serialization.json.JsonObject?,
var showChildren: Boolean = true,
): State
class JsonMap(props: JsonMapProps): RComponent<JsonMapProps, JsonMapState>() {
init {
this.state = JsonMapState(
data = props.data,
)
val random = Random(Random.nextInt())
state.data = buildJsonObject {
props.data?.forEach { entry ->
val index = random.nextInt().toString()
state.entries[index] = props.schema.items!!
state.names[index] = entry.key
put(index, entry.value)
}
}
}
override fun RBuilder.render() {
styledDiv {
css {
borderColor = hsl((props.level * 40) % 256, 100, 15)
+Styles.objectContainer
}
styledDiv {
css {
+Styles.objectHeader
}
styledI {
attrs {
classes = if (state.showChildren) {
setOf("ri-arrow-down-s-line", "ri-xl")
} else {
setOf("ri-arrow-right-s-line", "ri-xl")
}
onClickFunction = {
setState {
showChildren = !showChildren
}
}
}
}
if (props.name != "") {
styledLabel {
css {
textTransform = TextTransform.capitalize
marginLeft = 10.px
}
+props.name
}
}
styledI {
css {
color = hex(0x4caf50)
marginLeft = 10.px
alignSelf = Align.center
}
attrs {
classes = setOf("ri-add-line", "ri-xl")
onClickFunction = {
val id = Random.nextInt().toString()
setState {
entries[id] = props.schema.items!!
names[id] = ""
}
}
}
}
}
styledDiv {
css {
if (!state.showChildren)
display = Display.none
}
descriptionBlock(props.schema.description)
for (entry in state.entries) {
jsonMapEntry(
schema = entry.value,
key = state.names[entry.key] ?: "",
data = state.data?.get(entry.key),
id = entry.key,
handleChange = ::handleChange,
onNameChange = ::handleNameChange,
onDelete = ::handleDelete,
allowedKeys = props.schema.constraints.allowedKeys?.filter { it.intersect(state.names.values.toSet()).isEmpty() },
usePredefinedKeys = !props.schema.constraints.allowedKeys.isNullOrEmpty(),
level = props.level,
)
}
}
}
}
fun handleChange(event: ValueChangeEvent) {
state.data = buildJsonObject {
if (state.data != null)
for (entry in state.data!!) {
put(entry.key, entry.value)
}
put(event.id, event.value)
}
val newData = buildJsonObject {
if (state.data != null) {
for (entry in state.data!!) {
if (entry.key in state.names)
put(state.names[entry.key]!!, entry.value)
}
}
}
props.handleChange(ValueChangeEvent(props.id, newData))
}
fun handleNameChange(event: NameChangeEvent) {
setState {
names[event.id] = event.name
}
val newData = buildJsonObject {
if (state.data != null) {
for (entry in state.data!!) {
if (entry.key == event.id) {
put(event.name, entry.value)
continue
}
if (entry.key in state.names)
put(state.names[entry.key]!!, entry.value)
}
}
}
props.handleChange(ValueChangeEvent(props.id, newData))
}
fun handleDelete(id: String) {
setState {
data = buildJsonObject {
if (state.data != null)
for (entry in state.data!!) {
if (entry.key == id)
continue
put(entry.key, entry.value)
}
}
names.remove(id)
entries.remove(id)
}
val newData = buildJsonObject {
if (state.data != null) {
for (entry in state.data!!) {
if (entry.key == id) {
continue
}
if (entry.key in state.names)
put(state.names[entry.key]!!, entry.value)
}
}
}
props.handleChange(ValueChangeEvent(props.id, newData))
}
}
fun RBuilder.jsonMap(schema: ConfigSchema, name: String, data: kotlinx.serialization.json.JsonObject?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, level: Int) {
child(JsonMap::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
this.level = level
}
}
}

View File

@ -0,0 +1,175 @@
package components
import NameChangeEvent
import ValueChangeEvent
import kotlinx.css.*
import kotlinx.html.InputType
import kotlinx.html.classes
import kotlinx.html.id
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import react.*
import react.dom.attrs
import react.dom.option
import schema.ConfigSchema
import styled.*
import kotlin.random.Random
external interface JsonMapEntryProps: Props {
var name: String
var schema: ConfigSchema
var data: JsonElement?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var onNameChange: (event: NameChangeEvent) -> Unit
var onDelete: (id: String) -> Unit
var allowedKeys: List<List<String>>?
var usePredefinedKeys: Boolean
var level: Int
}
data class JsonMapEntryState(
var data: JsonElement?,
var showChildren: Boolean = true,
var name: String,
): State
class JsonMapEntry(props: JsonMapEntryProps): RComponent<JsonMapEntryProps, JsonMapEntryState>() {
init {
this.state = JsonMapEntryState(
data = props.data,
name = props.name,
)
if (!props.allowedKeys.isNullOrEmpty() && props.name.isBlank()) {
val name = props.allowedKeys?.getOrNull(0)?.getOrNull(0) ?: ""
console.log("Defaulting name to $name")
this.state.name = name
props.onNameChange(NameChangeEvent(props.id, name))
}
}
override fun RBuilder.render() {
styledDiv {
css {
borderColor = hsl((props.level * 40) % 256, 100, 15)
+Styles.objectContainer
}
styledDiv {
css {
+Styles.objectHeader
}
styledI {
attrs {
classes = if (state.showChildren) {
setOf("ri-arrow-down-s-line", "ri-xl")
} else {
setOf("ri-arrow-right-s-line", "ri-xl")
}
onClickFunction = {
setState {
showChildren = !showChildren
}
}
}
}
if (props.usePredefinedKeys) {
styledSelect {
if (state.name.isNotBlank()){
option("", state.name)
}
for (optGroup in props.allowedKeys!!) {
for (opt in optGroup)
option("", opt)
}
attrs {
value = state.name
onChangeFunction = {
val value = (it.target as HTMLSelectElement).value
setState {
this.name = value
}
props.onNameChange(NameChangeEvent(props.id, value))
}
}
}
} else {
styledInput {
attrs {
type = InputType.text
value = state.name
onChangeFunction = {
val value = (it.target as HTMLInputElement).value
setState {
this.name = value
}
props.onNameChange(NameChangeEvent(props.id, value))
}
}
}
}
styledI {
css {
color = Color.red
marginLeft = 10.px
alignSelf = Align.center
}
attrs {
classes = setOf("ri-delete-bin-7-line")
onClickFunction = {
props.onDelete(props.id)
}
}
}
}
styledDiv {
css {
if (!state.showChildren)
display = Display.none
}
jsonSchema(
schema = props.schema,
name = "",
data = state.data,
id = props.id,
handleChange = props.handleChange,
level = props.level
)
}
}
}
}
fun RBuilder.jsonMapEntry(schema: ConfigSchema, key: String, data: JsonElement?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, onNameChange: (event: NameChangeEvent) -> Unit, onDelete: (id: String) -> Unit, allowedKeys: List<List<String>>?, usePredefinedKeys: Boolean, level: Int) {
child(JsonMapEntry::class) {
attrs {
this.schema = schema
this.name = key
this.data = data
this.id = id
this.handleChange = handleChange
this.onNameChange = onNameChange
this.onDelete = onDelete
this.allowedKeys = allowedKeys
this.usePredefinedKeys = usePredefinedKeys
this.level = level
}
}
}

View File

@ -0,0 +1,95 @@
package components
import ValueChangeEvent
import kotlinx.css.portal
import kotlinx.html.InputType
import kotlinx.html.id
import kotlinx.html.js.onChangeFunction
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.floatOrNull
import kotlinx.serialization.json.intOrNull
import org.w3c.dom.HTMLInputElement
import react.*
import react.dom.attrs
import schema.ConfigSchema
import schema.JsonType
import styled.css
import styled.styledDiv
import styled.styledInput
import styled.styledLabel
external interface JsonNumberProps: Props {
var schema: ConfigSchema
var name: String
var data: JsonPrimitive?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
}
data class JsonNumberState(
var value: Double = 0.0,
var data: JsonPrimitive?,
): State
class JsonNumber(props: JsonNumberProps): RComponent<JsonNumberProps, JsonNumberState>() {
init {
val default = props.schema.default?.doubleOrNull ?: 0.0
this.state = JsonNumberState(
value = props.data?.content?.toDoubleOrNull() ?: default,
data = props.data,
)
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(state.value)))
}
override fun RBuilder.render() {
styledDiv {
css {
+Styles.inputContainer
}
if (props.name != "") {
styledLabel {
+props.name
attrs {
htmlFor = "${props.id}-input"
}
}
}
styledInput {
attrs {
id = "${props.id}-input"
type = InputType.number
value = state.value.toString()
onChangeFunction = {
val value = (it.target as HTMLInputElement).value.toDoubleOrNull()
if (value != null) {
setState {
this.value = value
this.data = JsonPrimitive(value)
}
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(value)))
}
}
}
}
}
}
}
fun RBuilder.jsonNumber(schema: ConfigSchema, name: String, data: JsonPrimitive?, id: String, handleChange: (event: ValueChangeEvent) -> Unit) {
child(JsonNumber::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
}
}
}

View File

@ -0,0 +1,126 @@
package components
import ValueChangeEvent
import kotlinx.css.*
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.buildJsonObject
import react.*
import react.dom.attrs
import schema.ConfigSchema
import styled.*
external interface JsonObjectProps: Props {
var schema: ConfigSchema
var name: String
var data: kotlinx.serialization.json.JsonObject?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var level: Int
}
data class JsonObjectState(
var data: kotlinx.serialization.json.JsonObject?,
var showChildren: Boolean = true,
): State
class JsonObject(props: JsonObjectProps): RComponent<JsonObjectProps, JsonObjectState>() {
init {
this.state = JsonObjectState(
data = props.data,
)
}
override fun RBuilder.render() {
styledDiv {
css {
if (props.name != "") {
borderColor = hsl((props.level * 40) % 256, 100, 15)
+Styles.objectContainer
}
}
if (props.name != "") {
styledDiv {
css {
+Styles.objectHeader
}
styledI {
attrs {
classes = if (state.showChildren) {
setOf("ri-arrow-down-s-line", "ri-xl")
} else {
setOf("ri-arrow-right-s-line", "ri-xl")
}
onClickFunction = {
setState {
showChildren = !showChildren
}
}
}
}
styledLabel {
css {
textTransform = TextTransform.capitalize
}
+props.name
}
styledDiv {
css {
width = 24.px
}
}
}
}
styledDiv {
css {
if (!state.showChildren)
display = Display.none
}
descriptionBlock(props.schema.description)
for (property in props.schema.children!!) {
jsonSchema(
schema = property.value,
name = property.key,
data = state.data?.get(property.key),
id = property.key,
handleChange = ::handleChange,
level = props.level + 1,
)
}
}
}
}
fun handleChange(event: ValueChangeEvent) {
state.data = buildJsonObject {
if (state.data != null)
for (entry in state.data!!) {
put(entry.key, entry.value)
}
put(event.id, event.value)
}
props.handleChange(ValueChangeEvent(props.id, state.data!!))
}
}
fun RBuilder.jsonObject(schema: ConfigSchema, name: String, data: kotlinx.serialization.json.JsonObject?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, level: Int) {
child(JsonObject::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
this.level = level
}
}
}

View File

@ -0,0 +1,59 @@
package components
import ValueChangeEvent
import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import react.Props
import react.RBuilder
import react.RComponent
import react.State
import schema.ConfigSchema
import schema.ConfigValueType
external interface ConfigSchemaComponentProps: Props {
var schema: ConfigSchema
var name: String
var data: JsonElement?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
var level: Int
}
data class ConfigSchemaComponentState(
var data: JsonElement?,
): State
class ConfigSchemaComponent(props: ConfigSchemaComponentProps): RComponent<ConfigSchemaComponentProps, ConfigSchemaComponentState>() {
init {
this.state = ConfigSchemaComponentState(
data = props.data,
)
}
override fun RBuilder.render() {
when (props.schema.type) {
ConfigValueType.STRING -> jsonString(props.schema, props.name, state.data as JsonPrimitive?, props.id, props.handleChange)
ConfigValueType.BOOLEAN -> jsonBoolean(props.schema, props.name, state.data as JsonPrimitive?, props.id, props.handleChange)
ConfigValueType.OBJECT -> jsonObject(props.schema, props.name, state.data as JsonObject?, props.id, props.handleChange, props.level)
ConfigValueType.LIST -> jsonArray(props.schema, props.name, state.data as JsonArray?, props.id, props.handleChange, props.level)
ConfigValueType.MAP -> jsonMap(props.schema, props.name, state.data as JsonObject?, props.id, props.handleChange, props.level)
ConfigValueType.INTEGER -> jsonNumber(props.schema, props.name, state.data as JsonPrimitive?, props.id, props.handleChange)
ConfigValueType.FLOAT -> jsonNumber(props.schema, props.name, state.data as JsonPrimitive?, props.id, props.handleChange)
}
}
}
fun RBuilder.jsonSchema(schema: ConfigSchema, name: String, data: JsonElement?, id: String, handleChange: (event: ValueChangeEvent) -> Unit, level: Int = 0, attributes: ConfigSchemaComponentProps.() -> Unit = {}) {
child(ConfigSchemaComponent::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
this.level = level
attributes(this)
}
}
}

View File

@ -0,0 +1,137 @@
package components
import ValueChangeEvent
import kotlinx.browser.window
import kotlinx.css.*
import kotlinx.html.InputType
import kotlinx.html.classes
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.url.URL
import org.w3c.dom.url.URLSearchParams
import org.w3c.files.Blob
import org.w3c.files.File
import org.w3c.files.FileReader
import org.w3c.files.get
import org.w3c.xhr.XMLHttpRequest
import react.*
import react.dom.attrs
import schema.ConfigSchema
import styled.css
import styled.styledDiv
import styled.styledI
import styled.styledInput
external interface JsonSchemaRootProps: Props {
var source: String
}
data class JsonSchemaRootState(
var schema: ConfigSchema? = null,
var data: JsonObject = JsonObject(mapOf()),
var showJson: Boolean = false,
): State
class JsonSchemaRoot(props: JsonSchemaRootProps): RComponent<JsonSchemaRootProps, JsonSchemaRootState>() {
init {
this.state = JsonSchemaRootState()
val json = Json {
ignoreUnknownKeys = true
prettyPrint = true
}
val xmlHttp = XMLHttpRequest()
xmlHttp.open("GET", props.source)
xmlHttp.onload = {
val schema: ConfigSchema = json.decodeFromString(xmlHttp.responseText)
setState {
this.schema = schema
}
}
xmlHttp.send()
}
override fun RBuilder.render() {
styledDiv {
css {
backgroundColor = Color.black
width = 100.pct
fontSize = 2.rem
}
styledI {
css {
}
attrs {
classes = setOf("ri-folder-open-line")
}
styledInput {
attrs {
type = InputType.file
onChangeFunction = {
val reader = FileReader()
reader.onloadend = {
setState {
data = Json.decodeFromString(reader.result as String)
}
}
reader.readAsText((it.target as HTMLInputElement).files!![0]!!)
}
}
}
}
styledI {
css {
}
attrs {
classes = setOf("ri-save-3-line")
onClickFunction = {
setState {
showJson = !showJson
}
val encoded = Json.encodeToString(state.data)
console.log(encoded)
// //TODO: Send to the server and have to server respond with the data and a
// // Content-Disposition: attachment header. This should open a save dialog
//// window.location.assign("data:application/json;charset=utf-8,$encoded")
// js("window.open(\"data:application/json;charset=utf-8,\" + encodeURI(encoded), \"_blank\")")
}
}
}
}
if (state.showJson) {
styledDiv {
+Json.encodeToString(state.data)
}
}
if (state.schema != null)
jsonSchema(state.schema!!, "", state.data, "", ::handleChange) {
key = state.data.toString()
}
}
fun handleChange(event: ValueChangeEvent) {
state.data = event.value as JsonObject
}
}
fun RBuilder.jsonSchemaRoot(source: String) {
child(JsonSchemaRoot::class) {
attrs {
this.source = source
}
}
}

View File

@ -0,0 +1,111 @@
package components
import ValueChangeEvent
import kotlinx.css.portal
import kotlinx.html.InputType
import kotlinx.html.id
import kotlinx.html.js.onChangeFunction
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import react.*
import react.dom.attrs
import react.dom.option
import schema.ConfigSchema
import schema.JsonType
import styled.*
external interface JsonStringProps: Props {
var schema: ConfigSchema
var name: String
var data: JsonPrimitive?
var id: String
var handleChange: (event: ValueChangeEvent) -> Unit
}
data class JsonStringState(
var value: String = "",
var data: JsonPrimitive?,
): State
class JsonString(props: JsonStringProps): RComponent<JsonStringProps, JsonStringState>() {
init {
val default: String = props.schema.default?.contentOrNull ?: ""
this.state = JsonStringState(
value = props.data?.content ?: default,
data = props.data,
)
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(state.value)))
}
override fun RBuilder.render() {
styledDiv {
css {
+Styles.inputContainer
}
if (props.name != "") {
styledLabel {
+props.name
attrs {
htmlFor = "${props.id}-input"
}
}
}
if (!props.schema.constraints.enum.isNullOrEmpty()) {
styledSelect {
for (opt in props.schema.constraints.enum!!) {
option("", opt)
}
attrs {
id = "${props.id}-input"
value = state.value
onChangeFunction = {
val value = (it.target as HTMLSelectElement).value
setState {
this.value = value
this.data = JsonPrimitive(value)
}
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(value)))
}
}
}
} else {
styledInput {
attrs {
id = "${props.id}-input"
type = InputType.text
value = state.value
onChangeFunction = {
val value = (it.target as HTMLInputElement).value
setState {
this.value = value
}
props.handleChange(ValueChangeEvent(props.id, JsonPrimitive(value)))
}
}
}
}
}
}
}
fun RBuilder.jsonString(schema: ConfigSchema, name: String, data: JsonPrimitive?, id: String, handleChange: (event: ValueChangeEvent) -> Unit) {
child(JsonString::class) {
attrs {
this.schema = schema
this.name = name
this.data = data
this.id = id
this.handleChange = handleChange
}
}
}

View File

@ -0,0 +1,14 @@
package schema
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class ConfigSchema(
val type: ConfigValueType,
val description: List<String>? = null,
val children: Map<String, ConfigSchema>? = null,
val items: ConfigSchema? = null,
val default: JsonPrimitive? = null,
val constraints: ConfigValueConstraints = ConfigValueConstraints(),
)

View File

@ -0,0 +1,14 @@
package schema
import kotlinx.serialization.Serializable
@Serializable
data class ConfigValueConstraints(
val enum: List<String>? = null,
val allowedKeys: List<List<String>>? = null,
val maxLength: Int? = null,
val maxValueInt: Long? = null,
val minValueInt: Long? = null,
val maxValueFloat: Double? = null,
val minValueFloat: Double? = null,
)

View File

@ -0,0 +1,11 @@
package schema
enum class ConfigValueType {
STRING,
MAP,
LIST,
OBJECT,
BOOLEAN,
INTEGER,
FLOAT
}

View File

@ -0,0 +1,34 @@
package schema
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class JsonSchema(
@SerialName("\$schema")
val schema: String = "",
val type: JsonType = JsonType.UNKNOWN,
val properties: Map<String, JsonSchema> = mapOf(),
val additionalProperties: JsonSchema? = null,
val definitions: Map<String, JsonObject> = mapOf(),
val required: List<String> = listOf(),
val items: JsonSchema? = null,
val description: String = "",
val enum: List<String> = listOf(),
@SerialName("if") val conditionIf: JsonSchema? = null,
@SerialName("then") val conditionThen: JsonSchema? = null,
@SerialName("else") val conditionElse: JsonSchema? = null,
)
@Serializable
enum class JsonType {
@SerialName("unknown") UNKNOWN,
@SerialName("string") STRING,
@SerialName("number") NUMBER,
@SerialName("boolean") BOOLEAN,
@SerialName("object") OBJECT,
@SerialName("array") ARRAY,
@SerialName("null") NULL,
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS Client</title>
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
</head>
<body style="background-color: #151515;font-family: sans-serif; color: white; margin: 0">
<script src="DConfig-Web.js"></script>
<div id="root"></div>
</body>
</html>