Compare commits
5 commits
166bcc3fc2
...
8063e15421
Author | SHA1 | Date | |
---|---|---|---|
8063e15421 | |||
c9ce59d04f | |||
aa91345523 | |||
b5029e4594 | |||
35a6544e65 |
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.gradle/
|
||||
.idea/
|
||||
build/
|
||||
web/
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
*.db
|
2
LICENSE
2
LICENSE
|
@ -1,5 +1,5 @@
|
|||
MIT License
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Copyright (c) 2019 Lars Westermann
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
13
README.md
13
README.md
|
@ -1,3 +1,16 @@
|
|||
# portal
|
||||
|
||||
Webportal for everything and stuff
|
||||
|
||||
## Usage
|
||||
|
||||
The server can be started directly via:
|
||||
```bash
|
||||
./gradlew run
|
||||
```
|
||||
|
||||
Or create a shadow jar:
|
||||
```bash
|
||||
./gradlew jar
|
||||
java -jar build/libs/portal.jar
|
||||
```
|
||||
|
|
183
build.gradle
Normal file
183
build.gradle
Normal file
|
@ -0,0 +1,183 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'kotlin-multiplatform' version '1.3.20'
|
||||
id 'kotlinx-serialization' version '1.3.20'
|
||||
id "org.kravemir.gradle.sass" version "1.2.2"
|
||||
id "com.github.johnrengelman.shadow" version "4.0.4"
|
||||
}
|
||||
|
||||
group "de.kif"
|
||||
version "0.1.0"
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url "http://dl.bintray.com/kotlin/ktor" }
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
mavenCentral()
|
||||
}
|
||||
def ktor_version = '1.1.2'
|
||||
def serialization_version = '0.10.0'
|
||||
|
||||
kotlin {
|
||||
jvm() {
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += [
|
||||
"-Xuse-experimental=io.ktor.locations.KtorExperimentalLocationsAPI",
|
||||
"-Xuse-experimental=io.ktor.util.KtorExperimentalAPI"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
js() {
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
moduleKind = "umd"
|
||||
sourceMap = true
|
||||
metaInfo = true
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib-common')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-common')
|
||||
implementation kotlin('test-annotations-common')
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib-jdk8')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
|
||||
|
||||
implementation "io.ktor:ktor-server-netty:$ktor_version"
|
||||
implementation "io.ktor:ktor-auth:$ktor_version"
|
||||
implementation "io.ktor:ktor-locations:$ktor_version"
|
||||
//implementation "io.ktor:ktor-websockets:$ktor_version"
|
||||
|
||||
implementation "io.ktor:ktor-html-builder:$ktor_version"
|
||||
|
||||
implementation 'org.xerial:sqlite-jdbc:3.25.2'
|
||||
implementation 'org.jetbrains.exposed:exposed:0.12.2'
|
||||
|
||||
implementation 'org.mindrot:jbcrypt:0.4'
|
||||
|
||||
api 'io.github.microutils:kotlin-logging:1.6.23'
|
||||
api 'ch.qos.logback:logback-classic:1.2.3'
|
||||
api 'org.fusesource.jansi:jansi:1.8'
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation kotlin('test')
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib-js')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
|
||||
|
||||
implementation "de.westermann:KObserve-js:0.8.0"
|
||||
}
|
||||
}
|
||||
jsTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-js')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sass {
|
||||
main {
|
||||
srcDir = file("$projectDir/src/jsMain/resources/style")
|
||||
outDir = file("$buildDir/processedResources/js/main/style")
|
||||
|
||||
exclude = "**/*.css"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def webFolder = new File(project.buildDir, "../web")
|
||||
def jsCompilations = kotlin.targets.js.compilations
|
||||
|
||||
task populateWebFolder(dependsOn: [jsMainClasses, sass]) {
|
||||
doLast {
|
||||
copy {
|
||||
from jsCompilations.main.output
|
||||
from kotlin.sourceSets.jsMain.resources.srcDirs
|
||||
jsCompilations.test.runtimeDependencyFiles.each {
|
||||
if (it.exists() && !it.isDirectory()) {
|
||||
from zipTree(it.absolutePath).matching {
|
||||
include '*.js'
|
||||
exclude '*.meta.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
into webFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsJar.dependsOn(populateWebFolder)
|
||||
|
||||
def mainClassName = 'de.kif.backend.Main'
|
||||
|
||||
task run(type: JavaExec, dependsOn: [jvmMainClasses, jsJar]) {
|
||||
main = mainClassName
|
||||
classpath {
|
||||
[
|
||||
kotlin.targets.jvm.compilations.main.output.allOutputs.files,
|
||||
configurations.jvmRuntimeClasspath,
|
||||
]
|
||||
}
|
||||
args = []
|
||||
}
|
||||
|
||||
clean.doFirst {
|
||||
delete webFolder
|
||||
}
|
||||
|
||||
task jar(type: ShadowJar, dependsOn: [jvmMainClasses, jsMainClasses, sass]) {
|
||||
from kotlin.targets.jvm.compilations.main.output
|
||||
|
||||
from(kotlin.targets.js.compilations.main.output) {
|
||||
into "web"
|
||||
exclude '*.meta.js'
|
||||
}
|
||||
from(kotlin.sourceSets.jsMain.resources.srcDirs) {
|
||||
into "web"
|
||||
exclude '*.meta.js'
|
||||
}
|
||||
|
||||
configurations = [kotlin.targets.jvm.compilations.main.compileDependencyFiles]
|
||||
|
||||
baseName = rootProject.name
|
||||
classifier = null
|
||||
version = null
|
||||
|
||||
manifest {
|
||||
attributes 'Main-Class': mainClassName
|
||||
}
|
||||
}
|
1
gradle.properties
Normal file
1
gradle.properties
Normal file
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Tue Feb 05 15:31:59 CET 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
172
gradlew
vendored
Normal file
172
gradlew
vendored
Normal file
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## 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"'
|
||||
|
||||
# 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
|
||||
;;
|
||||
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, switch paths to Windows format before running java
|
||||
if $cygwin ; 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=$((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"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
@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 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"
|
||||
|
||||
@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 init
|
||||
|
||||
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 init
|
||||
|
||||
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
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
: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 %CMD_LINE_ARGS%
|
||||
|
||||
: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
|
13
settings.gradle
Normal file
13
settings.gradle
Normal file
|
@ -0,0 +1,13 @@
|
|||
pluginManagement {
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "kotlin-multiplatform") {
|
||||
useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
|
||||
}
|
||||
if (requested.id.id == "kotlinx-serialization") {
|
||||
useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rootProject.name = 'portal'
|
11
src/jsMain/kotlin/de/kif/frontend/calendar/Calendar.kt
Normal file
11
src/jsMain/kotlin/de/kif/frontend/calendar/Calendar.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package de.kif.frontend.calendar
|
||||
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
|
||||
class Calendar : ViewCollection<View>(createHtmlView()) {
|
||||
init {
|
||||
|
||||
}
|
||||
}
|
19
src/jsMain/kotlin/de/kif/frontend/main.kt
Normal file
19
src/jsMain/kotlin/de/kif/frontend/main.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import de.kif.frontend.calendar.Calendar
|
||||
import de.westermann.kwebview.components.boxView
|
||||
import de.westermann.kwebview.components.h1
|
||||
import de.westermann.kwebview.components.init
|
||||
|
||||
fun main() = init {
|
||||
clear()
|
||||
h1("Test")
|
||||
boxView {
|
||||
style {
|
||||
width = "600px"
|
||||
height = "400px"
|
||||
margin = "10px"
|
||||
}
|
||||
+Calendar()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Delegate to easily access html attributes.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class AttributeDelegate(
|
||||
private val paramName: String? = null
|
||||
) {
|
||||
|
||||
private fun getParamName(property: KProperty<*>): String = paramName ?: property.name.toLowerCase()
|
||||
|
||||
operator fun getValue(container: View, property: KProperty<*>) = container.html.getAttribute(getParamName(property))
|
||||
|
||||
operator fun setValue(container: View, property: KProperty<*>, value: String?) {
|
||||
if (value == null) {
|
||||
container.html.removeAttribute(getParamName(property))
|
||||
} else {
|
||||
container.html.setAttribute(getParamName(property), value.toString())
|
||||
}
|
||||
}
|
||||
}
|
49
src/jsMain/kotlin/de/westermann/kwebview/ClassDelegate.kt
Normal file
49
src/jsMain/kotlin/de/westermann/kwebview/ClassDelegate.kt
Normal file
|
@ -0,0 +1,49 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.basic.property
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Delegate to easily set css classes as boolean attributes.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class ClassDelegate(
|
||||
className: String? = null
|
||||
) {
|
||||
|
||||
private lateinit var container: View
|
||||
private lateinit var paramName: String
|
||||
private lateinit var classProperty: Property<Boolean>
|
||||
|
||||
operator fun getValue(container: View, property: KProperty<*>): Property<Boolean> {
|
||||
if (!this::container.isInitialized) {
|
||||
this.container = container
|
||||
}
|
||||
|
||||
if (!this::paramName.isInitialized) {
|
||||
var name = property.name.toDashCase()
|
||||
if (name.endsWith("-property")) {
|
||||
name = name.replace("-property", "")
|
||||
}
|
||||
paramName = name
|
||||
}
|
||||
|
||||
if (!this::classProperty.isInitialized) {
|
||||
classProperty = property(container.html.classList.contains(paramName))
|
||||
|
||||
classProperty.onChange {
|
||||
container.html.classList.toggle(paramName, classProperty.value)
|
||||
}
|
||||
}
|
||||
|
||||
return classProperty
|
||||
}
|
||||
|
||||
init {
|
||||
if (className != null) {
|
||||
this.paramName = className
|
||||
}
|
||||
}
|
||||
}
|
119
src/jsMain/kotlin/de/westermann/kwebview/ClassList.kt
Normal file
119
src/jsMain/kotlin/de/westermann/kwebview/ClassList.kt
Normal file
|
@ -0,0 +1,119 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.ListenerReference
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import org.w3c.dom.DOMTokenList
|
||||
|
||||
/**
|
||||
* Represents the css classes of an html element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class ClassList(
|
||||
private val list: DOMTokenList
|
||||
) : Iterable<String> {
|
||||
|
||||
private val bound: MutableMap<String, Bound> = mutableMapOf()
|
||||
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
fun add(clazz: String) {
|
||||
if (clazz in bound) {
|
||||
val p = bound[clazz] ?: return
|
||||
if (p.property is Property<Boolean>) {
|
||||
p.property.value = true
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
} else {
|
||||
list.add(clazz)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
operator fun plusAssign(clazz: String) = add(clazz)
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
fun remove(clazz: String) {
|
||||
if (clazz in bound) {
|
||||
val p = bound[clazz] ?: return
|
||||
if (p.property is Property<Boolean>) {
|
||||
p.property.value = false
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
} else {
|
||||
list.remove(clazz)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove css class.
|
||||
*/
|
||||
operator fun minusAssign(clazz: String) = remove(clazz)
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun get(clazz: String): Boolean = list.contains(clazz)
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun contains(clazz: String): Boolean = list.contains(clazz)
|
||||
|
||||
/**
|
||||
* Set css class present.
|
||||
*/
|
||||
operator fun set(clazz: String, present: Boolean) =
|
||||
if (present) {
|
||||
add(clazz)
|
||||
} else {
|
||||
remove(clazz)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle css class.
|
||||
*/
|
||||
fun toggle(clazz: String, force: Boolean? = null) = set(clazz, force ?: !contains(clazz))
|
||||
|
||||
fun bind(clazz: String, property: ReadOnlyProperty<Boolean>) {
|
||||
if (clazz in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
set(clazz, property.value)
|
||||
bound[clazz] = Bound(property,
|
||||
property.onChange.reference {
|
||||
list.toggle(clazz, property.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun unbind(clazz: String) {
|
||||
if (clazz !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
}
|
||||
|
||||
bound[clazz]?.reference?.remove()
|
||||
bound -= clazz
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<String> {
|
||||
return toString().split(" +".toRegex()).iterator()
|
||||
}
|
||||
|
||||
override fun toString(): String = list.value
|
||||
|
||||
private data class Bound(
|
||||
val property: ReadOnlyProperty<Boolean>,
|
||||
val reference: ListenerReference<Unit>?
|
||||
)
|
||||
}
|
128
src/jsMain/kotlin/de/westermann/kwebview/DataSet.kt
Normal file
128
src/jsMain/kotlin/de/westermann/kwebview/DataSet.kt
Normal file
|
@ -0,0 +1,128 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.ListenerReference
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import org.w3c.dom.DOMStringMap
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.set
|
||||
|
||||
/**
|
||||
* Represents the css classes of an html element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class DataSet(
|
||||
private val map: DOMStringMap
|
||||
) {
|
||||
|
||||
private val bound: MutableMap<String, Bound> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
operator fun plusAssign(entry: Pair<String, String>) {
|
||||
if (entry.first in bound) {
|
||||
bound[entry.first]?.set(entry.second)
|
||||
} else {
|
||||
map[entry.first] = entry.second
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove css class.
|
||||
*/
|
||||
operator fun minusAssign(key: String) {
|
||||
if (key in bound) {
|
||||
bound[key]?.set(null)
|
||||
} else {
|
||||
delete(map, key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun get(key: String): String? = map[key]
|
||||
|
||||
/**
|
||||
* Set css class present.
|
||||
*/
|
||||
operator fun set(key: String, value: String?) =
|
||||
if (value == null) {
|
||||
this -= key
|
||||
} else {
|
||||
this += key to value
|
||||
}
|
||||
|
||||
fun bind(key: String, property: ReadOnlyProperty<String>) {
|
||||
if (key in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
bound[key] = Bound(key, null, property)
|
||||
}
|
||||
|
||||
fun bind(key: String, property: ReadOnlyProperty<String?>) {
|
||||
if (key in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
bound[key] = Bound(key, property, null)
|
||||
}
|
||||
|
||||
fun unbind(key: String) {
|
||||
if (key !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
}
|
||||
|
||||
bound[key]?.reference?.remove()
|
||||
bound -= key
|
||||
}
|
||||
|
||||
private inner class Bound(
|
||||
val key: String,
|
||||
val propertyNullable: ReadOnlyProperty<String?>?,
|
||||
val property: ReadOnlyProperty<String>?
|
||||
) {
|
||||
|
||||
var reference: ListenerReference<Unit>? = null
|
||||
|
||||
fun set(value: String?) {
|
||||
if (propertyNullable != null && propertyNullable is Property) {
|
||||
propertyNullable.value = value
|
||||
} else if (property != null && property is Property && value != null) {
|
||||
property.value = value
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (propertyNullable != null) {
|
||||
reference = propertyNullable.onChange.reference {
|
||||
val value = propertyNullable.value
|
||||
if (value == null) {
|
||||
delete(map, key)
|
||||
} else {
|
||||
map[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
val value = propertyNullable.value
|
||||
if (value == null) {
|
||||
delete(map, key)
|
||||
} else {
|
||||
map[key] = value
|
||||
}
|
||||
} else if (property != null) {
|
||||
reference = property.onChange.reference {
|
||||
map[key] = property.value
|
||||
}
|
||||
|
||||
map[key] = property.value
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
62
src/jsMain/kotlin/de/westermann/kwebview/Dimension.kt
Normal file
62
src/jsMain/kotlin/de/westermann/kwebview/Dimension.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
data class Dimension(
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
) {
|
||||
|
||||
constructor(position: Point, size: Point = Point.ZERO) : this(position.x, position.y, size.x, size.y)
|
||||
|
||||
val position: Point
|
||||
get() = Point(left, top)
|
||||
|
||||
val size: Point
|
||||
get() = Point(width, height)
|
||||
|
||||
val right: Double
|
||||
get() = left + width
|
||||
|
||||
val bottom: Double
|
||||
get() = top + height
|
||||
|
||||
val edges: Set<Point>
|
||||
get() = setOf(
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
)
|
||||
|
||||
val normalized: Dimension
|
||||
get() {
|
||||
val l = min(left, right)
|
||||
val t = min(top, bottom)
|
||||
return Dimension(l, t, abs(width), abs(width))
|
||||
}
|
||||
|
||||
operator fun contains(other: Dimension): Boolean = !(other.left > right ||
|
||||
other.right < left ||
|
||||
other.top > bottom ||
|
||||
other.bottom < top)
|
||||
|
||||
|
||||
operator fun contains(other: Point): Boolean {
|
||||
val n = normalized
|
||||
return (n.left <= other.x && (n.left + width) >= other.x)
|
||||
&& (n.top <= other.y && (n.top + height) >= other.y)
|
||||
}
|
||||
|
||||
operator fun plus(point: Point) = copy(left + point.x, top + point.y)
|
||||
|
||||
companion object {
|
||||
val ZERO = Dimension(0.0, 0.0)
|
||||
}
|
||||
}
|
4
src/jsMain/kotlin/de/westermann/kwebview/KWebViewDsl.kt
Normal file
4
src/jsMain/kotlin/de/westermann/kwebview/KWebViewDsl.kt
Normal file
|
@ -0,0 +1,4 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
@DslMarker
|
||||
annotation class KWebViewDsl
|
50
src/jsMain/kotlin/de/westermann/kwebview/Point.kt
Normal file
50
src/jsMain/kotlin/de/westermann/kwebview/Point.kt
Normal file
|
@ -0,0 +1,50 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
data class Point(
|
||||
val x: Double,
|
||||
val y: Double
|
||||
) {
|
||||
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
|
||||
|
||||
operator fun plus(number: Int) = Point(x + number, y + number)
|
||||
operator fun plus(number: Double) = Point(x + number, y + number)
|
||||
operator fun plus(point: Point) = Point(x + point.x, y + point.y)
|
||||
|
||||
operator fun minus(number: Int) = Point(x - number, y - number)
|
||||
operator fun minus(number: Double) = Point(x - number, y - number)
|
||||
operator fun minus(point: Point) = Point(x - point.x, y - point.y)
|
||||
|
||||
operator fun times(number: Int) = Point(x * number, y * number)
|
||||
operator fun times(number: Double) = Point(x * number, y * number)
|
||||
operator fun times(point: Point) = Point(x * point.x, y * point.y)
|
||||
|
||||
operator fun div(number: Int) = Point(x / number, y / number)
|
||||
operator fun div(number: Double) = Point(x / number, y / number)
|
||||
operator fun div(point: Point) = Point(x / point.x, y / point.y)
|
||||
|
||||
operator fun unaryMinus(): Point = Point(-x, -y)
|
||||
|
||||
fun min(): Double = min(x, y)
|
||||
fun max(): Double = max(x, y)
|
||||
|
||||
val isZero: Boolean
|
||||
get() = x == 0.0 && y == 0.0
|
||||
|
||||
companion object {
|
||||
val ZERO = Point(0.0, 0.0)
|
||||
}
|
||||
|
||||
val asPx: String
|
||||
get() = "${x}px, ${y}px"
|
||||
|
||||
fun distance(): Double = sqrt(x * x + y * y)
|
||||
|
||||
infix fun distance(other: Point) = (this - other).distance()
|
||||
}
|
124
src/jsMain/kotlin/de/westermann/kwebview/View.kt
Normal file
124
src/jsMain/kotlin/de/westermann/kwebview/View.kt
Normal file
|
@ -0,0 +1,124 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.EventHandler
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.css.CSSStyleDeclaration
|
||||
import org.w3c.dom.events.FocusEvent
|
||||
import org.w3c.dom.events.KeyboardEvent
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import org.w3c.dom.events.WheelEvent
|
||||
|
||||
abstract class View(view: HTMLElement = createHtmlView()) {
|
||||
|
||||
open val html: HTMLElement = view.also { view ->
|
||||
this::class.simpleName?.let { name ->
|
||||
view.classList.add(name.toDashCase())
|
||||
}
|
||||
}
|
||||
|
||||
val classList = ClassList(view.classList)
|
||||
val dataset = DataSet(view.dataset)
|
||||
|
||||
var id by AttributeDelegate()
|
||||
|
||||
val clientLeft: Int
|
||||
get() = html.clientLeft
|
||||
val clientTop: Int
|
||||
get() = html.clientTop
|
||||
val clientWidth: Int
|
||||
get() = html.clientWidth
|
||||
val clientHeight: Int
|
||||
get() = html.clientHeight
|
||||
|
||||
val offsetLeft: Int
|
||||
get() = html.offsetLeft
|
||||
val offsetTop: Int
|
||||
get() = html.offsetTop
|
||||
val offsetWidth: Int
|
||||
get() = html.offsetWidth
|
||||
val offsetHeight: Int
|
||||
get() = html.offsetHeight
|
||||
|
||||
val offsetLeftTotal: Int
|
||||
get() {
|
||||
var element: HTMLElement? = html
|
||||
var offset = 0
|
||||
while (element != null) {
|
||||
offset += element.offsetLeft
|
||||
element = element.offsetParent as? HTMLElement
|
||||
}
|
||||
return offset
|
||||
}
|
||||
val offsetTopTotal: Int
|
||||
get() {
|
||||
var element: HTMLElement? = html
|
||||
var offset = 0
|
||||
while (element != null) {
|
||||
offset += element.offsetTop
|
||||
element = element.offsetParent as? HTMLElement
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
val dimension: Dimension
|
||||
get() = html.getBoundingClientRect().toDimension()
|
||||
|
||||
var title by AttributeDelegate()
|
||||
|
||||
val style = view.style
|
||||
fun style(block: CSSStyleDeclaration.() -> Unit) {
|
||||
block(style)
|
||||
}
|
||||
|
||||
fun focus() {
|
||||
html.focus()
|
||||
}
|
||||
|
||||
fun blur() {
|
||||
html.blur()
|
||||
}
|
||||
|
||||
fun click() {
|
||||
html.click()
|
||||
}
|
||||
|
||||
val onClick = EventHandler<MouseEvent>()
|
||||
val onDblClick = EventHandler<MouseEvent>()
|
||||
val onContext = EventHandler<MouseEvent>()
|
||||
|
||||
val onMouseDown = EventHandler<MouseEvent>()
|
||||
val onMouseMove = EventHandler<MouseEvent>()
|
||||
val onMouseUp = EventHandler<MouseEvent>()
|
||||
val onMouseEnter = EventHandler<MouseEvent>()
|
||||
val onMouseLeave = EventHandler<MouseEvent>()
|
||||
|
||||
val onWheel = EventHandler<WheelEvent>()
|
||||
|
||||
val onKeyDown = EventHandler<KeyboardEvent>()
|
||||
val onKeyPress = EventHandler<KeyboardEvent>()
|
||||
val onKeyUp = EventHandler<KeyboardEvent>()
|
||||
|
||||
val onFocus = EventHandler<FocusEvent>()
|
||||
val onBlur = EventHandler<FocusEvent>()
|
||||
|
||||
init {
|
||||
onClick.bind(view, "click")
|
||||
onDblClick.bind(view, "dblclick")
|
||||
onContext.bind(view, "contextmenu")
|
||||
|
||||
onMouseDown.bind(view, "mousedown")
|
||||
onMouseMove.bind(view, "mousemove")
|
||||
onMouseUp.bind(view, "mouseup")
|
||||
onMouseEnter.bind(view, "mouseenter")
|
||||
onMouseLeave.bind(view, "mouseleave")
|
||||
|
||||
onWheel.bind(view, "wheel")
|
||||
|
||||
onKeyDown.bind(view, "keydown")
|
||||
onKeyPress.bind(view, "keypress")
|
||||
onKeyUp.bind(view, "keyup")
|
||||
|
||||
onFocus.bind(view, "focus")
|
||||
onBlur.bind(view, "blur")
|
||||
}
|
||||
}
|
69
src/jsMain/kotlin/de/westermann/kwebview/ViewCollection.kt
Normal file
69
src/jsMain/kotlin/de/westermann/kwebview/ViewCollection.kt
Normal file
|
@ -0,0 +1,69 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlin.dom.clear
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Iterable<V> {
|
||||
|
||||
private val children: MutableList<V> = mutableListOf()
|
||||
|
||||
fun append(view: V) {
|
||||
children += view
|
||||
html.appendChild(view.html)
|
||||
}
|
||||
|
||||
operator fun plusAssign(view: V) = append(view)
|
||||
|
||||
fun prepand(view: V) {
|
||||
children.add(0, view)
|
||||
html.insertBefore(view.html, html.firstChild)
|
||||
}
|
||||
|
||||
fun remove(view: V) {
|
||||
if (children.contains(view)) {
|
||||
children -= view
|
||||
html.removeChild(view.html)
|
||||
}
|
||||
}
|
||||
|
||||
fun toForeground(view: V) {
|
||||
if (view in children && children.indexOf(view) < children.size - 1) {
|
||||
remove(view)
|
||||
append(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun toBackground(view: V) {
|
||||
if (view in children && children.indexOf(view) > 0) {
|
||||
remove(view)
|
||||
prepand(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun first(): V = children.first()
|
||||
fun last(): V = children.last()
|
||||
|
||||
operator fun minusAssign(view: V) = remove(view)
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = children.isEmpty()
|
||||
|
||||
fun clear() {
|
||||
children.clear()
|
||||
html.clear()
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<V> = children.iterator()
|
||||
|
||||
val size: Int
|
||||
get() = children.size
|
||||
|
||||
operator fun contains(view: V) = children.contains(view)
|
||||
|
||||
operator fun V.unaryPlus() {
|
||||
append(this)
|
||||
}
|
||||
}
|
57
src/jsMain/kotlin/de/westermann/kwebview/ViewForLabel.kt
Normal file
57
src/jsMain/kotlin/de/westermann/kwebview/ViewForLabel.kt
Normal file
|
@ -0,0 +1,57 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kwebview.components.Label
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
abstract class ViewForLabel : View(createHtmlView<HTMLInputElement>()) {
|
||||
override val html = super.html as HTMLInputElement
|
||||
|
||||
private var label: Label? = null
|
||||
|
||||
fun setLabel(label: Label) {
|
||||
if (this.label != null) {
|
||||
throw IllegalStateException("Label already set!")
|
||||
}
|
||||
|
||||
this.label = label
|
||||
|
||||
val id = id
|
||||
if (id?.isNotBlank() == true) {
|
||||
label.html.htmlFor = id
|
||||
} else {
|
||||
val newId = this::class.simpleName?.toDashCase() + "-" + generateId()
|
||||
this.id = newId
|
||||
label.html.htmlFor = newId
|
||||
}
|
||||
}
|
||||
|
||||
private var requiredInternal by AttributeDelegate("required")
|
||||
var required: Boolean
|
||||
get() = requiredInternal != null
|
||||
set(value) {
|
||||
requiredInternal = if (value) "required" else null
|
||||
}
|
||||
private var readonlyInternal by AttributeDelegate("readonly")
|
||||
var readonly: Boolean
|
||||
get() = readonlyInternal != null
|
||||
set(value) {
|
||||
readonlyInternal = if (value) "readonly" else null
|
||||
}
|
||||
|
||||
var tabindex by AttributeDelegate()
|
||||
fun preventTabStop() {
|
||||
tabindex = "-1"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun generateId(length: Int = 16): String {
|
||||
var str = ""
|
||||
while (str.length <= length) {
|
||||
str += abs(Random.nextLong()).toString(36)
|
||||
}
|
||||
return str.take(length)
|
||||
}
|
||||
}
|
||||
}
|
42
src/jsMain/kotlin/de/westermann/kwebview/components/Body.kt
Normal file
42
src/jsMain/kotlin/de/westermann/kwebview/components/Body.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.i18n
|
||||
import org.w3c.dom.DocumentReadyState
|
||||
import org.w3c.dom.HTMLBodyElement
|
||||
import org.w3c.dom.LOADING
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
|
||||
object Body : ViewCollection<View>(document.body
|
||||
?: throw NullPointerException("Access to body before body was loaded")) {
|
||||
override val html = super.html as HTMLBodyElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun init(language: String? = null, block: Body.() -> Unit) {
|
||||
var done = if (language == null) 1 else 2
|
||||
if (document.readyState == DocumentReadyState.LOADING) {
|
||||
window.onload = {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
if (language != null) {
|
||||
i18n.load(language) {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLDivElement
|
||||
|
||||
class BoxView() : ViewCollection<View>(createHtmlView<HTMLDivElement>()) {
|
||||
override val html = super.html as HTMLDivElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in BoxView>.boxView(vararg classes: String, init: BoxView.() -> Unit = {}): BoxView {
|
||||
val view = BoxView()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLButtonElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Button() : ViewCollection<View>(createHtmlView<HTMLButtonElement>()) {
|
||||
|
||||
constructor(text: String) : this() {
|
||||
this.text = text
|
||||
}
|
||||
|
||||
override val html = super.html as HTMLButtonElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(text: String = "", init: Button.() -> Unit = {}) =
|
||||
Button(text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(text: ReadOnlyProperty<String>, init: Button.() -> Unit = {}) =
|
||||
Button(text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(init: Button.() -> Unit = {}) =
|
||||
Button().also(this::append).also(init)
|
|
@ -0,0 +1,68 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.ValidationProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
|
||||
class Checkbox(
|
||||
initValue: Boolean = false
|
||||
) : ViewForLabel() {
|
||||
|
||||
fun bind(property: ReadOnlyProperty<Boolean>) {
|
||||
checkedProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<Boolean>) {
|
||||
checkedProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
checkedProperty.unbind()
|
||||
}
|
||||
|
||||
var checked: Boolean
|
||||
get() = html.checked
|
||||
set(value) {
|
||||
html.checked = value
|
||||
checkedProperty.invalidate()
|
||||
}
|
||||
|
||||
val checkedProperty: Property<Boolean> = property(this::checked)
|
||||
|
||||
init {
|
||||
checked = initValue
|
||||
html.type = "checkbox"
|
||||
|
||||
var lastValue = checked
|
||||
val changeListener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
val value = checked
|
||||
if (value != checkedProperty.value || value != lastValue) {
|
||||
lastValue = value
|
||||
checkedProperty.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.addEventListener("change", changeListener)
|
||||
html.addEventListener("keyup", changeListener)
|
||||
html.addEventListener("keypress", changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: Boolean = false, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: ReadOnlyProperty<Boolean>, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value.value).also(this::append).also { it.bind(value) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: Property<Boolean>, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value.value).also(this::append).also { it.bind(value) }.also(init)
|
|
@ -0,0 +1,105 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
|
||||
class FilterList<T, V : View>(
|
||||
val property: ReadOnlyProperty<T>,
|
||||
val filter: Filter<T, V>
|
||||
) : ViewCollection<V>(createHtmlView()) {
|
||||
|
||||
private val content: MutableMap<T, V> = mutableMapOf()
|
||||
|
||||
fun update() {
|
||||
val list = filter.filter(property.value)
|
||||
var missing = list
|
||||
|
||||
for ((element, view) in content.entries) {
|
||||
if (element in list) {
|
||||
missing -= element
|
||||
} else {
|
||||
if (contains(view)) {
|
||||
remove(view)
|
||||
}
|
||||
if (!filter.useCache) {
|
||||
content -= element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (element in missing) {
|
||||
val view = filter.render(element)
|
||||
append(view)
|
||||
if (property is Property<T>) {
|
||||
view.onClick {
|
||||
property.value = element
|
||||
}
|
||||
}
|
||||
content[element] = view
|
||||
}
|
||||
|
||||
clear()
|
||||
|
||||
for (element in list) {
|
||||
append(content[element]!!)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
update()
|
||||
|
||||
property.onChange {
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Filter<T, V : View> {
|
||||
fun filter(partial: T): List<T>
|
||||
fun render(element: T): V
|
||||
|
||||
val useCache: Boolean
|
||||
}
|
||||
|
||||
class StringFilter(
|
||||
private val dataSet: List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String): List<String> {
|
||||
val lower = partial.trim().toLowerCase()
|
||||
return dataSet.filter {
|
||||
it.toLowerCase().contains(lower)
|
||||
}.sortedBy { it.length + it.toLowerCase().indexOf(partial) * 2 }
|
||||
}
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = true
|
||||
}
|
||||
|
||||
class StaticStringFilter(
|
||||
private val dataSet: List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String) = dataSet
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = true
|
||||
}
|
||||
|
||||
|
||||
class DynamicStringFilter(
|
||||
private val filter: (partial: String) -> List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String) = filter.invoke(partial)
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = false
|
||||
}
|
||||
|
||||
|
||||
fun <T, V : View> ViewCollection<in FilterList<T, V>>.filterList(property: ReadOnlyProperty<T>, filter: Filter<T, V>, init: FilterList<T, V>.() -> Unit = {}) =
|
||||
FilterList(property, filter).also(this::append).also(init)
|
|
@ -0,0 +1,96 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLHeadingElement
|
||||
|
||||
class Heading(
|
||||
val type: Type,
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLHeadingElement>(type.tagName)) {
|
||||
|
||||
override val html = super.html as HTMLHeadingElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
}
|
||||
|
||||
enum class Type(val tagName: String) {
|
||||
H1("h1"),
|
||||
H2("h2"),
|
||||
H3("h3"),
|
||||
H4("h4"),
|
||||
H5("h5"),
|
||||
H6("h6")
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h1(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H1, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h1(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H1, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h2(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H2, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h2(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H2, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h3(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H3, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h3(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H3, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h4(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H4, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h4(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H4, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h5(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H5, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h5(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H5, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h6(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H6, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h6(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H6, text.value).also(this::append).also { it.bind(text) }.also(init)
|
|
@ -0,0 +1,46 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLImageElement
|
||||
|
||||
class ImageView(
|
||||
src: String
|
||||
) : View(createHtmlView<HTMLImageElement>("img")) {
|
||||
|
||||
override val html = super.html as HTMLImageElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
sourceProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
sourceProperty.unbind()
|
||||
}
|
||||
|
||||
var source: String
|
||||
get() = html.src
|
||||
set(value) {
|
||||
html.src = value
|
||||
sourceProperty.invalidate()
|
||||
}
|
||||
|
||||
val sourceProperty: Property<String> = property(this::source)
|
||||
|
||||
|
||||
var alt by AttributeDelegate("alt")
|
||||
|
||||
init {
|
||||
source = src
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in ImageView>.imageView(src: String = "", init: ImageView.() -> Unit = {}) =
|
||||
ImageView(src).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in ImageView>.imageView(src: ReadOnlyProperty<String>, init: ImageView.() -> Unit = {}) =
|
||||
ImageView(src.value).also(this::append).also { it.bind(src) }.also(init)
|
154
src/jsMain/kotlin/de/westermann/kwebview/components/InputView.kt
Normal file
154
src/jsMain/kotlin/de/westermann/kwebview/components/InputView.kt
Normal file
|
@ -0,0 +1,154 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.ValidationProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kobserve.not
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.KeyboardEvent
|
||||
|
||||
class InputView(
|
||||
type: InputType,
|
||||
initValue: String = ""
|
||||
) : ViewForLabel() {
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
valueProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<String>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun bind(property: ValidationProperty<String>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
invalidProperty.bind(!property.validProperty)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
valueProperty.unbind()
|
||||
if (invalidProperty.isBound) {
|
||||
invalidProperty.unbind()
|
||||
}
|
||||
}
|
||||
|
||||
var value: String
|
||||
get() = html.value
|
||||
set(value) {
|
||||
html.value = value
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
|
||||
val valueProperty: Property<String> = property(this::value)
|
||||
|
||||
var placeholder: String
|
||||
get() = html.placeholder
|
||||
set(value) {
|
||||
html.placeholder = value
|
||||
placeholderProperty.invalidate()
|
||||
}
|
||||
|
||||
val placeholderProperty: Property<String> = property(this::placeholder)
|
||||
|
||||
val invalidProperty by ClassDelegate("invalid")
|
||||
var invalid by invalidProperty
|
||||
|
||||
private var typeInternal by AttributeDelegate("type")
|
||||
var type: InputType?
|
||||
get() = typeInternal?.let(InputType.Companion::find)
|
||||
set(value) {
|
||||
typeInternal = value?.html
|
||||
}
|
||||
private var minInternal by AttributeDelegate("min")
|
||||
var min: Double?
|
||||
get() = minInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
minInternal = value?.toString()
|
||||
}
|
||||
private var maxInternal by AttributeDelegate("max")
|
||||
var max: Double?
|
||||
get() = maxInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
maxInternal = value?.toString()
|
||||
}
|
||||
private var stepInternal by AttributeDelegate("step")
|
||||
var step: Double?
|
||||
get() = stepInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
stepInternal = value?.toString()
|
||||
}
|
||||
|
||||
init {
|
||||
value = initValue
|
||||
this.type = type
|
||||
|
||||
var lastValue = value
|
||||
val changeListener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
val value = value
|
||||
if (value != valueProperty.value || value != lastValue) {
|
||||
lastValue = value
|
||||
valueProperty.value = value
|
||||
}
|
||||
|
||||
(event as? KeyboardEvent)?.let { e ->
|
||||
when (e.keyCode) {
|
||||
13, 27 -> blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.addEventListener("change", changeListener)
|
||||
html.addEventListener("keyup", changeListener)
|
||||
html.addEventListener("keypress", changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
enum class InputType(val html: String) {
|
||||
TEXT("text"),
|
||||
NUMBER("number"),
|
||||
PASSWORD("password");
|
||||
|
||||
companion object {
|
||||
fun find(html: String): InputType? = values().find { it.html == html }
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: String = "", init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: Property<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: String = "", init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: Property<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
51
src/jsMain/kotlin/de/westermann/kwebview/components/Label.kt
Normal file
51
src/jsMain/kotlin/de/westermann/kwebview/components/Label.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLLabelElement
|
||||
|
||||
/**
|
||||
* Represents a html label element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Label(
|
||||
inputElement: ViewForLabel,
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLLabelElement>()) {
|
||||
|
||||
override val html = super.html as HTMLLabelElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
|
||||
inputElement.setLabel(this)
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Label>.label(inputElement: ViewForLabel, text: String = "", init: Label.() -> Unit = {}) =
|
||||
Label(inputElement, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Label>.label(inputElement: ViewForLabel, text: ReadOnlyProperty<String>, init: Label.() -> Unit = {}) =
|
||||
Label(inputElement, text.value).also(this::append).also { it.bind(text) }.also(init)
|
44
src/jsMain/kotlin/de/westermann/kwebview/components/Link.kt
Normal file
44
src/jsMain/kotlin/de/westermann/kwebview/components/Link.kt
Normal file
|
@ -0,0 +1,44 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLAnchorElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Link(target: String) : ViewCollection<View>(createHtmlView<HTMLAnchorElement>("a")) {
|
||||
|
||||
override val html = super.html as HTMLAnchorElement
|
||||
|
||||
var text: String?
|
||||
get() = html.textContent
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
}
|
||||
|
||||
var target: String
|
||||
get() = html.href
|
||||
set(value) {
|
||||
html.href = value
|
||||
}
|
||||
|
||||
init {
|
||||
this.target = target
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Link>.link(target: String, text: String? = null, init: Link.() -> Unit = {}): Link {
|
||||
val view = Link(target)
|
||||
if (text != null) {
|
||||
view.text = text
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLOptionElement
|
||||
|
||||
class OptionView<T>(val value: T) : View(createHtmlView<HTMLOptionElement>()) {
|
||||
|
||||
override val html = super.html as HTMLOptionElement
|
||||
|
||||
var htmlValue: String
|
||||
get() = html.value
|
||||
set(value) {
|
||||
html.value = value
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.text
|
||||
set(value) {
|
||||
html.text = value
|
||||
}
|
||||
|
||||
val index: Int
|
||||
get() = html.index
|
||||
|
||||
var selected: Boolean
|
||||
get() = html.selected
|
||||
set(value) {
|
||||
html.selected = value
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.AttributeDelegate
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
|
||||
class SelectView<T : Any>(
|
||||
dataSet: List<T>,
|
||||
private val initValue: T,
|
||||
val transform: (T) -> String = { it.toString() }
|
||||
) : ViewCollection<OptionView<T>>(createHtmlView<HTMLSelectElement>()) {
|
||||
|
||||
override val html = super.html as HTMLSelectElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<T>) {
|
||||
valueProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<T>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
valueProperty.unbind()
|
||||
}
|
||||
|
||||
var dataSet: List<T> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
clear()
|
||||
|
||||
value.forEachIndexed { index, v ->
|
||||
+OptionView(v).also { option ->
|
||||
option.text = transform(v)
|
||||
option.htmlValue = index.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index: Int
|
||||
get() = html.selectedIndex
|
||||
set(value) {
|
||||
val invalidate = html.selectedIndex != value
|
||||
html.selectedIndex = value
|
||||
if (invalidate) {
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
var value: T
|
||||
get() = dataSet.getOrNull(index) ?: initValue
|
||||
set(value) {
|
||||
index = dataSet.indexOf(value)
|
||||
}
|
||||
val valueProperty = property(this::value)
|
||||
|
||||
private var readonlyInternal by AttributeDelegate("readonly")
|
||||
var readonly: Boolean
|
||||
get() = readonlyInternal != null
|
||||
set(value) {
|
||||
readonlyInternal = if (value) "readonly" else null
|
||||
}
|
||||
|
||||
var tabindex by AttributeDelegate()
|
||||
fun preventTabStop() {
|
||||
tabindex = "-1"
|
||||
}
|
||||
|
||||
init {
|
||||
this.dataSet = dataSet
|
||||
this.value = initValue
|
||||
|
||||
html.addEventListener("change", object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, initValue: T, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, initValue, transform).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, property: ReadOnlyProperty<T>, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, property: Property<T>, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(initValue: T, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), initValue, transform).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(property: ReadOnlyProperty<T>, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(property: Property<T>, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
22
src/jsMain/kotlin/de/westermann/kwebview/components/Table.kt
Normal file
22
src/jsMain/kotlin/de/westermann/kwebview/components/Table.kt
Normal file
|
@ -0,0 +1,22 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableElement
|
||||
|
||||
class Table() : ViewCollection<View>(createHtmlView<HTMLTableElement>()) {
|
||||
override val html = super.html as HTMLTableElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Table>.table(vararg classes: String, init: Table.() -> Unit = {}): Table {
|
||||
val view = Table()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableCaptionElement
|
||||
|
||||
class TableCaption() : ViewCollection<View>(createHtmlView<HTMLTableCaptionElement>("caption")) {
|
||||
override val html = super.html as HTMLTableCaptionElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCaption>.caption(init: TableCaption.() -> Unit = {}): TableCaption {
|
||||
val view = TableCaption()
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLTableCellElement
|
||||
|
||||
class TableCell(val isHead: Boolean) :
|
||||
ViewCollection<View>(createHtmlView<HTMLTableCellElement>(if (isHead) "th" else "td")) {
|
||||
override val html = super.html as HTMLTableCellElement
|
||||
|
||||
private var colSpanInternal by AttributeDelegate("colspan")
|
||||
var colSpan: Int?
|
||||
get() = colSpanInternal?.toIntOrNull()
|
||||
set(value) {
|
||||
colSpanInternal = value?.toString()
|
||||
}
|
||||
|
||||
private var rowSpanInternal by AttributeDelegate("rowspan")
|
||||
var rowSpan: Int?
|
||||
get() = rowSpanInternal?.toIntOrNull()
|
||||
set(value) {
|
||||
rowSpanInternal = value?.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCell>.cell(colSpan: Int? = null, init: TableCell.() -> Unit = {}): TableCell {
|
||||
val view = TableCell(false)
|
||||
view.colSpan = colSpan
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCell>.head(colSpan: Int? = null, init: TableCell.() -> Unit = {}): TableCell {
|
||||
val view = TableCell(true)
|
||||
view.colSpan = colSpan
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableRowElement
|
||||
|
||||
class TableRow() : ViewCollection<TableCell>(createHtmlView<HTMLTableRowElement>("tr")) {
|
||||
override val html = super.html as HTMLTableRowElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableRow>.row(vararg classes: String, init: TableRow.() -> Unit = {}): TableRow {
|
||||
val view = TableRow()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableSectionElement
|
||||
|
||||
class TableSection(val type: Type) : ViewCollection<TableRow>(createHtmlView<HTMLTableSectionElement>(type.tagName)) {
|
||||
override val html = super.html as HTMLTableSectionElement
|
||||
|
||||
enum class Type(val tagName: String) {
|
||||
THEAD("thead"),
|
||||
TBODY("tbody"),
|
||||
TFOOT("tfoot")
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.thead(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.THEAD)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.tbody(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.TBODY)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.tfoot(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.TFOOT)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLSpanElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class TextView(
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLSpanElement>()) {
|
||||
|
||||
override val html = super.html as HTMLSpanElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TextView>.textView(text: String = "", init: TextView.() -> Unit = {}) =
|
||||
TextView(text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TextView>.textView(text: ReadOnlyProperty<String>, init: TextView.() -> Unit = {}) =
|
||||
TextView(text.value).also(this::append).also { it.bind(text) }.also(init)
|
79
src/jsMain/kotlin/de/westermann/kwebview/extensions.kt
Normal file
79
src/jsMain/kotlin/de/westermann/kwebview/extensions.kt
Normal file
|
@ -0,0 +1,79 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.EventHandler
|
||||
import org.w3c.dom.DOMRect
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
|
||||
inline fun <reified V : HTMLElement> createHtmlView(tag: String? = null): V {
|
||||
var tagName: String
|
||||
if (tag != null) {
|
||||
tagName = tag
|
||||
} else {
|
||||
tagName = V::class.js.name.toLowerCase().replace("html([a-z]*)element".toRegex(), "$1")
|
||||
if (tagName.isBlank()) {
|
||||
tagName = "div"
|
||||
}
|
||||
}
|
||||
return document.createElement(tagName) as V
|
||||
}
|
||||
|
||||
fun String.toDashCase() = replace("([a-z])([A-Z])".toRegex(), "$1-$2").toLowerCase()
|
||||
|
||||
inline fun <reified T> EventHandler<T>.bind(element: HTMLElement, event: String) {
|
||||
val listener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
this@bind.emit(event as T)
|
||||
}
|
||||
}
|
||||
var isAttached = false
|
||||
|
||||
val updateState = {
|
||||
if (isEmpty() && isAttached) {
|
||||
element.removeEventListener(event, listener)
|
||||
isAttached = false
|
||||
} else if (!isEmpty() && !isAttached) {
|
||||
element.addEventListener(event, listener)
|
||||
isAttached = true
|
||||
}
|
||||
}
|
||||
|
||||
onAttach = updateState
|
||||
onDetach = updateState
|
||||
updateState()
|
||||
}
|
||||
|
||||
fun MouseEvent.toPoint(): Point = Point(clientX, clientY)
|
||||
fun DOMRect.toDimension(): Dimension = Dimension(x, y, width, height)
|
||||
|
||||
fun Number.format(digits: Int): String = this.asDynamic().toFixed(digits)
|
||||
|
||||
external fun delete(p: dynamic): Boolean = definedExternally
|
||||
|
||||
fun delete(thing: dynamic, key: String) {
|
||||
delete(thing[key])
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply current dom changes and recalculate all sizes. Executes the given block afterwards.
|
||||
*
|
||||
* @param timeout Optionally set a timeout for this call. Defaults to 1.
|
||||
* @param block Callback
|
||||
*/
|
||||
fun async(timeout: Int = 1, block: () -> Unit) {
|
||||
if (timeout < 1) throw IllegalArgumentException("Timeout must be greater than 0!")
|
||||
window.setTimeout(block, timeout)
|
||||
}
|
||||
|
||||
fun interval(timeout: Int, block: () -> Unit): Int {
|
||||
if (timeout < 1) throw IllegalArgumentException("Timeout must be greater than 0!")
|
||||
return window.setInterval(block, timeout)
|
||||
}
|
||||
|
||||
fun clearInterval(id: Int) {
|
||||
window.clearInterval(id)
|
||||
}
|
147
src/jsMain/kotlin/de/westermann/kwebview/i18n.kt
Normal file
147
src/jsMain/kotlin/de/westermann/kwebview/i18n.kt
Normal file
|
@ -0,0 +1,147 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.browser.window
|
||||
|
||||
@Suppress("ClassName")
|
||||
object i18n {
|
||||
private val data: MutableMap<String, Locale> = mutableMapOf()
|
||||
|
||||
private var fallbackLocale: Locale? = null
|
||||
private var locale: Locale? = null
|
||||
|
||||
fun register(id: String, name: String, path: String, fallback: Boolean = false) {
|
||||
val locale = Locale(id, name, path, fallback)
|
||||
|
||||
if (fallback) {
|
||||
if (fallbackLocale != null) {
|
||||
throw IllegalStateException("Fallback locale is already set!")
|
||||
}
|
||||
|
||||
fallbackLocale = locale
|
||||
}
|
||||
|
||||
data[id] = locale
|
||||
|
||||
window.fetch(path).then {
|
||||
it.json()
|
||||
}.then {
|
||||
locale.json = it
|
||||
locale.isLoaded = true
|
||||
}.catch {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
|
||||
val isReady: Boolean
|
||||
get() = data.values.all { it.isLoaded }
|
||||
|
||||
fun load(id: String, block: () -> Unit) {
|
||||
fun ready() {
|
||||
if (isReady) {
|
||||
locale = data[id]
|
||||
block()
|
||||
} else {
|
||||
async(50) { ready() }
|
||||
}
|
||||
}
|
||||
ready()
|
||||
}
|
||||
|
||||
private fun findKey(locale: Locale, key: String): dynamic {
|
||||
val keys = key.split(".")
|
||||
|
||||
var result = locale.json
|
||||
for (k in keys) {
|
||||
if (result.hasOwnProperty(k) as Boolean) {
|
||||
result = result[k]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findKey(key: String): dynamic {
|
||||
var result: dynamic = undefined
|
||||
|
||||
if (locale != null) {
|
||||
result = findKey(locale!!, key)
|
||||
}
|
||||
|
||||
if (result == undefined) {
|
||||
if (fallbackLocale != null) {
|
||||
result = findKey(fallbackLocale!!, key)
|
||||
}
|
||||
}
|
||||
|
||||
if (result == undefined) {
|
||||
throw InternationalizationError("Cannot find key '$key'!")
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun replace(str: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
val unnamed = arguments.filter { it.first == null }.map { it.second }
|
||||
val named = arguments.mapNotNull { it.first?.to(it.second) }
|
||||
|
||||
var s = str
|
||||
|
||||
for ((key, replacement) in named) {
|
||||
s = s.replace("{$key}", replacement?.toString() ?: "null")
|
||||
}
|
||||
|
||||
for (replacement in unnamed) {
|
||||
if (s.contains("{}")) {
|
||||
s = s.replaceFirst("{}", replacement?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
fun t(key: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
return replace(findKey(key).toString(), arguments)
|
||||
}
|
||||
|
||||
fun t(count: Number, key: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
val json = findKey(key)
|
||||
if (count == 0 && json.hasOwnProperty("zero") as Boolean) {
|
||||
return replace(json.zero.toString(), arguments)
|
||||
} else if (count == 1 && json.hasOwnProperty("one") as Boolean) {
|
||||
return replace(json.one.toString(), arguments)
|
||||
}
|
||||
|
||||
return if (json.hasOwnProperty("many") as Boolean)
|
||||
replace(json.many.toString(), arguments)
|
||||
else {
|
||||
replace(json.toString(), arguments)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Locale(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val path: String,
|
||||
val fallback: Boolean
|
||||
) {
|
||||
var isLoaded = false
|
||||
var json = js("{}")
|
||||
}
|
||||
}
|
||||
|
||||
class InternationalizationError(message: String? = null) : Error(message)
|
||||
|
||||
fun t(key: String) = i18n.t(key, emptyList())
|
||||
|
||||
fun t(key: String, vararg arguments: Any?) = i18n.t(key, arguments.map { null to it })
|
||||
|
||||
fun t(key: String, vararg arguments: Pair<String?, Any?>) = i18n.t(key, arguments.asList())
|
||||
|
||||
fun t(count: Number, key: String) = i18n.t(count, key, emptyList())
|
||||
|
||||
fun t(count: Number, key: String, vararg arguments: Any?) = i18n.t(count, key, arguments.map { null to it })
|
||||
|
||||
fun t(count: Number, key: String, vararg arguments: Pair<String?, Any?>) = i18n.t(count, key, arguments.asList())
|
BIN
src/jsMain/resources/external/font/Montserrat-Black.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Black.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Black.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Black.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Black.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Black.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BlackItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Bold.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Bold.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Bold.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Bold.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Bold.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Bold.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-BoldItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBold.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraBoldItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLight.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ExtraLightItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Italic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Italic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Italic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Italic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Italic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Italic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Light.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Light.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Light.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Light.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Light.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Light.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-LightItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Medium.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Medium.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Medium.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Medium.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Medium.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Medium.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-MediumItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Regular.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Regular.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Regular.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Regular.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Regular.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Regular.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBold.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-SemiBoldItalic.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Thin.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Thin.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Thin.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Thin.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-Thin.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-Thin.woff2
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/Montserrat-ThinItalic.woff2
vendored
Normal file
Binary file not shown.
399
src/jsMain/resources/external/font/Montserrat.css
vendored
Normal file
399
src/jsMain/resources/external/font/Montserrat.css
vendored
Normal file
|
@ -0,0 +1,399 @@
|
|||
/** =================== MONTSERRAT =================== **/
|
||||
|
||||
/** Montserrat Thin **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Thin.eot");
|
||||
src: url("Montserrat-Thin.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Thin.woff2") format("woff2"),
|
||||
url("Montserrat-Thin.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Thin-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-ThinItalic.eot");
|
||||
src: url("Montserrat-ThinItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-ThinItalic.woff2") format("woff2"),
|
||||
url("Montserrat-ThinItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat ExtraLight **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-ExtraLight.eot");
|
||||
src: url("Montserrat-ExtraLight.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-ExtraLight.woff2") format("woff2"),
|
||||
url("Montserrat-ExtraLight.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat ExtraLight-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-ExtraLightItalic.eot");
|
||||
src: url("Montserrat-ExtraLightItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-ExtraLightItalic.woff2") format("woff2"),
|
||||
url("Montserrat-ExtraLightItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Light **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Light.eot");
|
||||
src: url("Montserrat-Light.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Light.woff2") format("woff2"),
|
||||
url("Montserrat-Light.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Light-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-LightItalic.eot");
|
||||
src: url("Montserrat-LightItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-LightItalic.woff2") format("woff2"),
|
||||
url("Montserrat-LightItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Regular **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Regular.eot");
|
||||
src: url("Montserrat-Regular.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Regular.woff2") format("woff2"),
|
||||
url("Montserrat-Regular.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Regular-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-Italic.eot");
|
||||
src: url("Montserrat-Italic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Italic.woff2") format("woff2"),
|
||||
url("Montserrat-Italic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Medium **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Medium.eot");
|
||||
src: url("Montserrat-Medium.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Medium.woff2") format("woff2"),
|
||||
url("Montserrat-Medium.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Medium-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-MediumItalic.eot");
|
||||
src: url("Montserrat-MediumItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-MediumItalic.woff2") format("woff2"),
|
||||
url("Montserrat-MediumItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat SemiBold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-SemiBold.eot");
|
||||
src: url("Montserrat-SemiBold.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-SemiBold.woff2") format("woff2"),
|
||||
url("Montserrat-SemiBold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat SemiBold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-SemiBoldItalic.eot");
|
||||
src: url("Montserrat-SemiBoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-SemiBoldItalic.woff2") format("woff2"),
|
||||
url("Montserrat-SemiBoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Bold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Bold.eot");
|
||||
src: url("Montserrat-Bold.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Bold.woff2") format("woff2"),
|
||||
url("Montserrat-Bold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Bold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-BoldItalic.eot");
|
||||
src: url("Montserrat-BoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-BoldItalic.woff2") format("woff2"),
|
||||
url("Montserrat-BoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat ExtraBold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-ExtraBold.eot");
|
||||
src: url("Montserrat-ExtraBold.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-ExtraBold.woff2") format("woff2"),
|
||||
url("Montserrat-ExtraBold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat ExtraBold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 800;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-ExtraBoldItalic.eot");
|
||||
src: url("Montserrat-ExtraBoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-ExtraBoldItalic.woff2") format("woff2"),
|
||||
url("Montserrat-ExtraBoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Black **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
src: url("Montserrat-Black.eot");
|
||||
src: url("Montserrat-Black.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-Black.woff2") format("woff2"),
|
||||
url("Montserrat-Black.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Black-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
src: url("Montserrat-BlackItalic.eot");
|
||||
src: url("Montserrat-BlackItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("Montserrat-BlackItalic.woff2") format("woff2"),
|
||||
url("Montserrat-BlackItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** =================== MONTSERRAT ALTERNATES =================== **/
|
||||
|
||||
/** Montserrat Alternates Thin **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Thin.eot");
|
||||
src: url("MontserratAlternates-Thin.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Thin.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Thin.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Thin-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-ThinItalic.eot");
|
||||
src: url("MontserratAlternates-ThinItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-ThinItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-ThinItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates ExtraLight **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-ExtraLight.eot");
|
||||
src: url("MontserratAlternates-ExtraLight.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-ExtraLight.woff2") format("woff2"),
|
||||
url("MontserratAlternates-ExtraLight.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates ExtraLight-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-ExtraLightItalic.eot");
|
||||
src: url("MontserratAlternates-ExtraLightItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-ExtraLightItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-ExtraLightItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Light **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Light.eot");
|
||||
src: url("MontserratAlternates-Light.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Light.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Light.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Light-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-LightItalic.eot");
|
||||
src: url("MontserratAlternates-LightItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-LightItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-LightItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Regular **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Regular.eot");
|
||||
src: url("MontserratAlternates-Regular.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Regular.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Regular.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Regular-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-Italic.eot");
|
||||
src: url("MontserratAlternates-Italic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Italic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Italic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Medium **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Medium.eot");
|
||||
src: url("MontserratAlternates-Medium.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Medium.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Medium.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Medium-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-MediumItalic.eot");
|
||||
src: url("MontserratAlternates-MediumItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-MediumItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-MediumItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates SemiBold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-SemiBold.eot");
|
||||
src: url("MontserratAlternates-SemiBold.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-SemiBold.woff2") format("woff2"),
|
||||
url("MontserratAlternates-SemiBold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates SemiBold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-SemiBoldItalic.eot");
|
||||
src: url("MontserratAlternates-SemiBoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-SemiBoldItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-SemiBoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Bold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Bold.eot");
|
||||
src: url("MontserratAlternates-Bold.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Bold.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Bold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Bold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-BoldItalic.eot");
|
||||
src: url("MontserratAlternates-BoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-BoldItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-BoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates ExtraBold **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-ExtraBold.eot");
|
||||
src: url("MontserratAlternates-ExtraBold.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-ExtraBold.woff2") format("woff2"),
|
||||
url("MontserratAlternates-ExtraBold.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates ExtraBold-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 800;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-ExtraBoldItalic.eot");
|
||||
src: url("MontserratAlternates-ExtraBoldItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-ExtraBoldItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-ExtraBoldItalic.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Black **/
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
src: url("MontserratAlternates-Black.eot");
|
||||
src: url("MontserratAlternates-Black.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-Black.woff2") format("woff2"),
|
||||
url("MontserratAlternates-Black.woff") format("woff");
|
||||
}
|
||||
|
||||
/** Montserrat Alternates Black-Italic **/
|
||||
@font-face {
|
||||
font-family: "Montserrat";
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
src: url("MontserratAlternates-BlackItalic.eot");
|
||||
src: url("MontserratAlternates-BlackItalic.eot?#iefix") format('embedded-opentype'),
|
||||
url("MontserratAlternates-BlackItalic.woff2") format("woff2"),
|
||||
url("MontserratAlternates-BlackItalic.woff") format("woff");
|
||||
}
|
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.eot
vendored
Normal file
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.eot
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.woff
vendored
Normal file
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.woff
vendored
Normal file
Binary file not shown.
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.woff2
vendored
Normal file
BIN
src/jsMain/resources/external/font/MontserratAlternates-Black.woff2
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue