Initial commit
commit
63f6a55de1
|
@ -0,0 +1,4 @@
|
||||||
|
.gradle/
|
||||||
|
.idea/
|
||||||
|
build/
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
kotlin.code.style=official
|
||||||
|
kotlin.js.generate.executable.default=false
|
Binary file not shown.
|
@ -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
|
|
@ -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" "$@"
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
rootProject.name = "DConfig-Web"
|
||||||
|
|
|
@ -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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
|
data class NameChangeEvent(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
)
|
|
@ -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)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
|
data class ValueChangeEvent(
|
||||||
|
val id: String,
|
||||||
|
val value: JsonElement,
|
||||||
|
)
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
|
@ -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,
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
enum class ConfigValueType {
|
||||||
|
STRING,
|
||||||
|
MAP,
|
||||||
|
LIST,
|
||||||
|
OBJECT,
|
||||||
|
BOOLEAN,
|
||||||
|
INTEGER,
|
||||||
|
FLOAT
|
||||||
|
}
|
|
@ -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
|
@ -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>
|
Loading…
Reference in New Issue