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