diff options
author | Robert Jaros <rjaros@finn.pl> | 2018-01-31 00:10:58 +0100 |
---|---|---|
committer | Robert Jaros <rjaros@finn.pl> | 2018-01-31 00:10:58 +0100 |
commit | facf7991ed52cb21852dd6175a3817e94598c2dc (patch) | |
tree | aa4364769bbe0c3d637af8061761ef32c2234c37 | |
parent | a3514f0ea18313d37b9701e055f97f7ad81f76c4 (diff) | |
download | kvision-facf7991ed52cb21852dd6175a3817e94598c2dc.tar.gz kvision-facf7991ed52cb21852dd6175a3817e94598c2dc.tar.bz2 kvision-facf7991ed52cb21852dd6175a3817e94598c2dc.zip |
TodoMVC example
30 files changed, 1986 insertions, 0 deletions
diff --git a/examples/todomvc/.gitignore b/examples/todomvc/.gitignore new file mode 100644 index 00000000..4d615936 --- /dev/null +++ b/examples/todomvc/.gitignore @@ -0,0 +1,4 @@ +.*/ +build/ +out/ +*.iml
\ No newline at end of file diff --git a/examples/todomvc/build.gradle b/examples/todomvc/build.gradle new file mode 100644 index 00000000..3087c146 --- /dev/null +++ b/examples/todomvc/build.gradle @@ -0,0 +1,94 @@ +buildscript { + ext.kotlin_version = '1.2.21' + ext.production = (findProperty('prod') ?: 'false') == 'true' + ext.npmdeps = new URL("file:///home/rjaros/git/kvision/npm.dependencies").getText() + + repositories { + jcenter() + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + maven { url "https://plugins.gradle.org/m2/" } + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.26" + classpath "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0.RC6-2" + } +} + +apply plugin: 'kotlin2js' +apply plugin: 'org.jetbrains.kotlin.frontend' +apply plugin: "io.gitlab.arturbosch.detekt" + +repositories { + jcenter() + maven { url = 'https://dl.bintray.com/gbaldeck/kotlin' } + maven { url = 'https://dl.bintray.com/rjaros/kotlin' } + maven { + url "file:///home/rjaros/kotlin/mvn/" + } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" // for now only compile configuration is supported + compile "pl.treksoft:kvision:0.0.1" +} + +kotlinFrontend { + npm { + npmdeps.eachLine { line -> + def (name, version) = line.tokenize(" ") + dependency(name, version) + } + dependency("todomvc-app-css", "^2.0.0") + dependency("todomvc-common", "^1.0.0") + devDependency("karma") + } + + webpackBundle { + bundleName = "main" + contentPath = file('src/main/web') + } + + define "PRODUCTION", production + +} + +detekt { + version = "1.0.0.RC6-2" + profile("main") { + input = "$projectDir/src/main/kotlin" + config = "$projectDir/detekt.yml" + filters = ".*test.*,.*/resources/.*,.*/tmp/.*" + } +} + +compileKotlin2Js { + kotlinOptions.metaInfo = true + kotlinOptions.outputFile = "$project.buildDir.path/js/${project.name}.js" + kotlinOptions.sourceMap = !production + kotlinOptions.moduleKind = 'commonjs' +} + +compileTestKotlin2Js { + kotlinOptions.metaInfo = true + kotlinOptions.outputFile = "$project.buildDir.path/js-tests/${project.name}-tests.js" + kotlinOptions.sourceMap = !production + kotlinOptions.moduleKind = 'commonjs' +} + +task copyResources(type: Copy) { + from "src/main/resources" + into file(buildDir.path + "/js") +} + +task copyResourcesForTests(type: Copy) { + from "src/main/resources" + into file(buildDir.path + "/js-tests/") +} + +afterEvaluate { + tasks.getByName("webpack-bundle") { dependsOn(copyResources) } + tasks.getByName("webpack-run") { dependsOn(copyResources, copyResourcesForTests) } +} diff --git a/examples/todomvc/detekt.yml b/examples/todomvc/detekt.yml new file mode 100644 index 00000000..a6fdea75 --- /dev/null +++ b/examples/todomvc/detekt.yml @@ -0,0 +1,292 @@ +autoCorrect: true +failFast: false + +build: + warningThreshold: 5 + failThreshold: 10 + weights: + complexity: 2 + formatting: 1 + LongParameterList: 1 + comments: 1 + +processors: + active: true + exclude: + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + # - 'BuildFailureReport' + +output-reports: + active: true + exclude: + # - 'PlainOutputReport' + # - 'XmlOutputReport' + +potential-bugs: + active: true + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: false + EqualsWithHashCodeExist: + active: true + WrongEqualsTypeParameter: + active: false + ExplicitGarbageCollectionCall: + active: true + UnreachableCode: + active: true + LateinitUsage: + active: false + UnsafeCallOnNullableType: + active: false + UnsafeCast: + active: false + UselessPostfixExpression: + active: false + +performance: + active: true + ForEachOnRange: + active: true + SpreadOperator: + active: true + UnnecessaryTemporaryInstantiation: + active: true + +exceptions: + active: true + TooGenericExceptionCatched: + active: true + exceptions: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - IndexOutOfBoundsException + - NullPointerException + - RuntimeException + TooGenericExceptionThrown: + active: true + exceptions: + - Throwable + - ThrowError + - ThrowException + - ThrowNullPointerException + - ThrowRuntimeException + - ThrowThrowable + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +complexity: + active: true + LongMethod: + threshold: 20 + LongParameterList: + threshold: 5 + LargeClass: + threshold: 150 + ComplexMethod: + threshold: 10 + TooManyFunctions: + threshold: 10 + ComplexCondition: + threshold: 3 + LabeledExpression: + active: false + StringLiteralDuplication: + active: false + threshold: 2 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + +code-smell: + active: true + FeatureEnvy: + threshold: 0.5 + weight: 0.45 + base: 0.5 + +formatting: + active: true + useTabs: true + Indentation: + active: false + indentSize: 4 + ConsecutiveBlankLines: + active: true + autoCorrect: true + MultipleSpaces: + active: true + autoCorrect: true + SpacingAfterComma: + active: true + autoCorrect: true + SpacingAfterKeyword: + active: true + autoCorrect: true + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundCurlyBraces: + active: true + autoCorrect: true + SpacingAroundOperator: + active: true + autoCorrect: true + TrailingSpaces: + active: true + autoCorrect: true + UnusedImports: + active: true + autoCorrect: true + OptionalSemicolon: + active: true + autoCorrect: true + OptionalUnit: + active: true + autoCorrect: true + ExpressionBodySyntax: + active: false + autoCorrect: false + ExpressionBodySyntaxLineBreaks: + active: false + autoCorrect: false + OptionalReturnKeyword: + active: true + autoCorrect: false + +style: + active: true + ReturnCount: + active: true + max: 2 + NewLineAtEndOfFile: + active: true + OptionalAbstractKeyword: + active: true + OptionalWhenBraces: + active: false + EqualsNullCall: + active: false + ForbiddenComment: + active: true + values: 'TODO:,FIXME:,STOPSHIP:' + ForbiddenImport: + active: false + imports: '' + ModifierOrder: + active: true + MagicNumber: + active: true + ignoreNumbers: '-1,0,1,2' + ignoreHashCodeFunction: false + ignorePropertyDeclaration: false + ignoreAnnotation: false + WildcardImport: + active: true + SafeCast: + active: true + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: false + excludeImportStatements: false + PackageNaming: + active: true + packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$' + ClassNaming: + active: true + classPattern: '[A-Z$][a-zA-Z$]*' + EnumNaming: + active: true + enumEntryPattern: '^[A-Z$][a-zA-Z_$]*$' + FunctionNaming : + active: true + functionPattern: '^[a-z$][a-zA-Z$0-9]*$' + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + VariableNaming : + active: true + variablePattern: '^(_)?[a-z$][a-zA-Z$0-9]*$' + ConstantNaming : + active: true + constantPattern: '^([A-Z_]*|serialVersionUID)$' + VariableMaxLength: + active: false + maximumVariableNameLength: 30 + VariableMinLength: + active: false + minimumVariableNameLength: 3 + ProtectedMemberInFinalClass: + active: false + UnnecessaryParentheses: + active: false + +comments: + active: true + CommentOverPrivateMethod: + active: true + CommentOverPrivateProperty: + active: true + UndocumentedPublicClass: + active: false + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + +# *experimental feature* +# Migration rules can be defined in the same config file or a new one +migration: + active: true + imports: + # your.package.Class: new.package.or.Class + # for example: + # io.gitlab.arturbosch.detekt.api.Rule: io.gitlab.arturbosch.detekt.rule.Rule diff --git a/examples/todomvc/gradle.properties b/examples/todomvc/gradle.properties new file mode 100644 index 00000000..4ac81290 --- /dev/null +++ b/examples/todomvc/gradle.properties @@ -0,0 +1,2 @@ +#org.gradle.jvmargs=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder +#org.gradle.debug=true diff --git a/examples/todomvc/gradle/wrapper/gradle-wrapper.jar b/examples/todomvc/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 00000000..09f1fecb --- /dev/null +++ b/examples/todomvc/gradle/wrapper/gradle-wrapper.jar diff --git a/examples/todomvc/gradle/wrapper/gradle-wrapper.properties b/examples/todomvc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..9f53b3e5 --- /dev/null +++ b/examples/todomvc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jan 22 09:38:31 CET 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip diff --git a/examples/todomvc/gradlew b/examples/todomvc/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/examples/todomvc/gradlew @@ -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="" + +# 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" "$@" diff --git a/examples/todomvc/gradlew.bat b/examples/todomvc/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/examples/todomvc/gradlew.bat @@ -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= + +@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 diff --git a/examples/todomvc/package.json.d/project.info b/examples/todomvc/package.json.d/project.info new file mode 100644 index 00000000..3c4d58fe --- /dev/null +++ b/examples/todomvc/package.json.d/project.info @@ -0,0 +1,3 @@ +{ + "description": "KVision TodoMVC" +} diff --git a/examples/todomvc/refresh.sh b/examples/todomvc/refresh.sh new file mode 100755 index 00000000..2568701d --- /dev/null +++ b/examples/todomvc/refresh.sh @@ -0,0 +1,26 @@ +./gradlew stop +rm -rf build/bundle +rm -rf build/js +rm -rf build/js-tests +rm -rf build/reports +rm -rf build/resources +rm -rf build/tmp +rm -rf build/logs +rm -rf build/package.json +rm -rf build/package-lock.json +rm -rf build/.modules.with.kotlin.txt +rm -rf build/.modules.with.types.txt +rm -rf build/.npmrc +rm -rf build/.unpack.txt +rm -rf build/kotlin +rm -rf build/kotlin-build +rm -rf build/*.js +rm -rf build/node_modules/kotlin +rm -rf build/node_modules/kotlin-test +rm -rf build/node_modules/kvision +rm -rf build/node_modules/navigo-kotlin +rm -rf build/node_modules/jquery-kotlin +rm -rf build/node_modules/snabbdom-kotlin +rm -rf build/node_modules/kotlin-observable-js +rm -rf build/node_modules_imported +./gradlew -t run diff --git a/examples/todomvc/settings.gradle b/examples/todomvc/settings.gradle new file mode 100644 index 00000000..00861ee1 --- /dev/null +++ b/examples/todomvc/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'todomvc' diff --git a/examples/todomvc/src/main/kotlin/com/example/Main.kt b/examples/todomvc/src/main/kotlin/com/example/Main.kt new file mode 100644 index 00000000..d8894a4c --- /dev/null +++ b/examples/todomvc/src/main/kotlin/com/example/Main.kt @@ -0,0 +1,40 @@ +package com.example + +import pl.treksoft.kvision.ApplicationBase +import pl.treksoft.kvision.core.KVManager +import pl.treksoft.kvision.module +import kotlin.browser.document + +fun main(args: Array<String>) { + var application: ApplicationBase? = null + + val state: dynamic = module.hot?.let { hot -> + hot.accept() + + hot.dispose { data -> + data.appState = application?.dispose() + KVManager.shutdown() + application = null + } + + hot.data + } + + if (document.body != null) { + KVManager.start() + application = start(state) + } else { + KVManager.init() + application = null + document.addEventListener("DOMContentLoaded", { application = start(state) }) + } +} + +fun start(state: dynamic): ApplicationBase? { + if (document.getElementById("todomvc") == null) return null + val application = Todomvc() + @Suppress("UnsafeCastFromDynamic") + application.start(state?.appState ?: emptyMap()) + return application +} + diff --git a/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt b/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt new file mode 100644 index 00000000..76d90716 --- /dev/null +++ b/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt @@ -0,0 +1,242 @@ +package com.example + +import com.lightningkite.kotlin.observable.list.observableListOf +import org.w3c.dom.get +import org.w3c.dom.set +import pl.treksoft.kvision.ApplicationBase +import pl.treksoft.kvision.core.Root +import pl.treksoft.kvision.data.DataComponent +import pl.treksoft.kvision.data.DataContainer +import pl.treksoft.kvision.form.FieldLabel +import pl.treksoft.kvision.form.check.CHECKINPUTTYPE +import pl.treksoft.kvision.form.check.CheckInput +import pl.treksoft.kvision.form.text.TextInput +import pl.treksoft.kvision.html.Button +import pl.treksoft.kvision.html.LIST +import pl.treksoft.kvision.html.Link +import pl.treksoft.kvision.html.ListTag +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.routing.routing +import kotlin.browser.localStorage + +const val ENTER_KEY = 13 +const val ESCAPE_KEY = 27 + +class Todo(completed: Boolean, title: String, hidden: Boolean) : DataComponent() { + var completed: Boolean by obs(completed) + var title: String by obs(title) + var hidden: Boolean by obs(hidden) +} + +enum class LISTMODE { + ALL, + ACTIVE, + COMPLETED +} + +class Todomvc : ApplicationBase() { + + private val model = observableListOf<Todo>() + + private val checkAllInput = CheckInput(classes = setOf("toggle-all")).apply { + id = "toggle-all" + onClick { + val value = this.value + model.forEach { it.completed = value } + } + } + private val allLink = Link("All", "#!/", classes = setOf("selected")) + private val activeLink = Link("Active", "#!/active") + private val completedLink = Link("Completed", "#!/completed") + private val clearCompletedButton = Button("Clear completed", classes = setOf("clear-completed")).onClick { + model.filter { it.completed }.forEach { model.remove(it) } + } + + private val countTag = Tag(TAG.STRONG, "0") + private val itemsLeftTag = Tag(TAG.SPAN, " items left", classes = setOf("todo-count")).apply { + add(countTag) + } + private var mode: LISTMODE = LISTMODE.ALL + + private val header = genHeader() + private val main = genMain() + private val footer = genFooter() + + override fun start(state: Map<String, Any>) { + val root = Root("todomvc") + val section = Tag(TAG.SECTION, classes = setOf("todoapp")) + section.add(this.header) + section.add(this.main) + section.add(this.footer) + root.add(section) + loadModel() + checkModel() + routing.on("/", { _ -> all() }) + .on("/active", { _ -> active() }) + .on("/completed", { _ -> completed() }) + .resolve() + } + + private fun loadModel() { + localStorage.get("todos-kvision")?.let { + JSON.parse<Array<dynamic>>(it).map { Todo(it.completed, it.title, false) }.forEach { + model.add(it) + } + } + } + + private fun saveModel() { + val jsonString = model.map { + val array = listOf("title" to it.title, "completed" to it.completed).toTypedArray() + JSON.stringify(kotlin.js.json(*array)) + }.toString() + localStorage.set("todos-kvision", jsonString) + } + + private fun checkModel() { + val countActive = model.filter { !it.completed }.size + val countCompleted = model.filter { it.completed }.size + this.main.visible = model.isNotEmpty() + this.footer.visible = model.isNotEmpty() + this.countTag.text = countActive.toString() + this.itemsLeftTag.text = when (countActive) { + 1 -> " item left" + else -> " items left" + } + this.checkAllInput.value = (countActive == 0) + this.clearCompletedButton.visible = countCompleted > 0 + saveModel() + } + + private fun all() { + this.mode = LISTMODE.ALL + this.allLink.addCssClass("selected") + this.activeLink.removeCssClass("selected") + this.completedLink.removeCssClass("selected") + this.model.forEach { it.hidden = false } + } + + private fun active() { + this.mode = LISTMODE.ACTIVE + this.allLink.removeCssClass("selected") + this.activeLink.addCssClass("selected") + this.completedLink.removeCssClass("selected") + this.model.forEach { it.hidden = it.completed } + } + + private fun completed() { + this.mode = LISTMODE.COMPLETED + this.allLink.removeCssClass("selected") + this.activeLink.removeCssClass("selected") + this.completedLink.addCssClass("selected") + this.model.forEach { it.hidden = !it.completed } + } + + private fun genHeader(): Tag { + return Tag(TAG.HEADER, classes = setOf("header")).apply { + add(Tag(TAG.H1, "todos")) + add(TextInput(classes = setOf("new-todo")).apply { + placeholder = "What needs to be done?" + autofocus = true + setEventListener<TextInput> { + keydown = { e -> + if (e.keyCode == ENTER_KEY) { + addTodo(self.value) + self.value = null + } + } + } + }) + } + } + + private fun addTodo(value: String?) { + val v = value?.trim() ?: "" + if (v.isNotEmpty()) { + model.add(Todo(false, v, mode == LISTMODE.COMPLETED)) + } + } + + private fun editTodo(index: Int, value: String?) { + val v = value?.trim() ?: "" + if (v.isNotEmpty()) { + model[index].title = v + } else { + model.removeAt(index) + } + } + + private fun genMain(): Tag { + return Tag(TAG.SECTION, classes = setOf("main")).apply { + add(checkAllInput) + add(FieldLabel("toggle-all", "Mark all as complete")) + add(DataContainer(model, { index -> + val li = Tag(TAG.LI) + li.apply { + if (model[index].completed) addCssClass("completed") + if (model[index].hidden) addCssClass("hidden") + val edit = TextInput(classes = setOf("edit")) + val view = Tag(TAG.DIV, classes = setOf("view")).apply { + add(CheckInput( + CHECKINPUTTYPE.CHECKBOX, model[index].completed, classes = setOf("toggle") + ).onClick { + model[index].completed = this.value + model[index].hidden = + mode == LISTMODE.ACTIVE && this.value || mode == LISTMODE.COMPLETED && !this.value + }) + add(Tag(TAG.LABEL, model[index].title).apply { + setEventListener<Tag> { + dblclick = { + li.getElementJQuery()?.addClass("editing") + edit.value = model[index].title + edit.getElementJQuery()?.focus() + } + } + }) + add(Button("", classes = setOf("destroy")).onClick { + model.removeAt(index) + }) + } + edit.setEventListener<TextInput> { + blur = { + if (li.getElementJQuery()?.hasClass("editing") == true) { + li.getElementJQuery()?.removeClass("editing") + editTodo(index, self.value) + } + } + keydown = { e -> + if (e.keyCode == ENTER_KEY) { + li.getElementJQuery()?.removeClass("editing") + editTodo(index, self.value) + } + if (e.keyCode == ESCAPE_KEY) { + li.getElementJQuery()?.removeClass("editing") + } + } + } + add(view) + add(edit) + } + }, Tag(TAG.UL, classes = setOf("todo-list"))).onUpdate { + checkModel() + }) + } + } + + private fun genFooter(): Tag { + return Tag(TAG.FOOTER, classes = setOf("footer")).apply { + add(itemsLeftTag) + add(ListTag(LIST.UL, classes = setOf("filters")).apply { + add(allLink) + add(activeLink) + add(completedLink) + }) + add(clearCompletedButton) + } + } + + override fun dispose(): Map<String, Any> { + return mapOf() + } +} diff --git a/examples/todomvc/src/main/web/index.html b/examples/todomvc/src/main/web/index.html new file mode 100644 index 00000000..e5ba8812 --- /dev/null +++ b/examples/todomvc/src/main/web/index.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>KVision • TodoMVC</title> + <link rel="stylesheet" href="node_modules/todomvc-common/base.css"> + <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css"> +</head> +<body> +<div id="todomvc"></div> +<footer class="info"> + <p>Double-click to edit a todo</p> + <p>Created by <a href="https://github.com/rjaros">Robert Jaros</a></p> + <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> +</footer> +<!-- Scripts here. Don't remove ↓ --> +<script src="node_modules/todomvc-common/base.js"></script> +<script>var KV_NO_BOOTSTRAP_CSS = true;</script> +<script src="main.bundle.js"></script> +</body> +</html> diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-app-css/index.css b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/index.css new file mode 100644 index 00000000..d8be205a --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/index.css @@ -0,0 +1,376 @@ +html, +body { + margin: 0; + padding: 0; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + -webkit-appearance: none; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + +:focus { + outline: 0; +} + +.hidden { + display: none; +} + +.todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +.todoapp input::-webkit-input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp input::-moz-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp input::input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -155px; + width: 100%; + font-size: 100px; + font-weight: 100; + text-align: center; + color: rgba(175, 47, 47, 0.15); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +.new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + font-weight: inherit; + line-height: 1.4em; + border: 0; + color: inherit; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +.main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +.toggle-all { + text-align: center; + border: none; /* Mobile Safari */ + opacity: 0; + position: absolute; +} + +.toggle-all + label { + width: 60px; + height: 34px; + font-size: 0; + position: absolute; + top: -52px; + left: -13px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.toggle-all + label:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked + label:before { + color: #737373; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +.todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +.todo-list li:last-child { + border-bottom: none; +} + +.todo-list li.editing { + border-bottom: none; + padding: 0; +} + +.todo-list li.editing .edit { + display: block; + width: 506px; + padding: 12px 16px; + margin: 0 0 0 43px; +} + +.todo-list li.editing .view { + display: none; +} + +.todo-list li .toggle { + text-align: center; + width: 40px; + /* auto, since non-WebKit browsers doesn't support input styling */ + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; /* Mobile Safari */ + -webkit-appearance: none; + appearance: none; +} + +.todo-list li .toggle { + opacity: 0; +} + +.todo-list li .toggle + label { + /* + Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 + IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ + */ + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); + background-repeat: no-repeat; + background-position: center left; +} + +.todo-list li .toggle:checked + label { + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); +} + +.todo-list li label { + word-break: break-all; + padding: 15px 15px 15px 60px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list li.completed label { + color: #d9d9d9; + text-decoration: line-through; +} + +.todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; +} + +.todo-list li .destroy:hover { + color: #af5b5e; +} + +.todo-list li .destroy:after { + content: '×'; +} + +.todo-list li:hover .destroy { + display: block; +} + +.todo-list li .edit { + display: none; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-count { + float: left; + text-align: left; +} + +.todo-count strong { + font-weight: 300; +} + +.filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.filters li { + display: inline; +} + +.filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.filters li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +.filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +.clear-completed, +html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; +} + +.clear-completed:hover { + text-decoration: underline; +} + +.info { + margin: 65px auto 0; + color: #bfbfbf; + font-size: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +.info p { + line-height: 1; +} + +.info a { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +.info a:hover { + text-decoration: underline; +} + +/* + Hack to remove background from Mobile Safari. + Can't use it globally since it destroys checkboxes in Firefox +*/ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .toggle-all, + .todo-list li .toggle { + background: none; + } + + .todo-list li .toggle { + height: 40px; + } +} + +@media (max-width: 430px) { + .footer { + height: 50px; + } + + .filters { + bottom: 10px; + } +} diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-app-css/package.json b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/package.json new file mode 100644 index 00000000..72831e7b --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/package.json @@ -0,0 +1,58 @@ +{ + "_from": "todomvc-app-css@^2.0.0", + "_id": "todomvc-app-css@2.1.0", + "_inBundle": false, + "_integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU=", + "_location": "/todomvc-app-css", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "todomvc-app-css@^2.0.0", + "name": "todomvc-app-css", + "escapedName": "todomvc-app-css", + "rawSpec": "^2.0.0", + "saveSpec": null, + "fetchSpec": "^2.0.0" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz", + "_shasum": "b6f2716d339afa2e5f799347d2a48b05396242a5", + "_spec": "todomvc-app-css@^2.0.0", + "_where": "/home/rjaros/git/kvision/examples/todomvc/src/main/web", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "bugs": { + "url": "https://github.com/tastejs/todomvc-app-css/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "CSS for TodoMVC apps", + "files": [ + "index.css" + ], + "homepage": "https://github.com/tastejs/todomvc-app-css#readme", + "keywords": [ + "todomvc", + "tastejs", + "app", + "todo", + "template", + "css", + "style", + "stylesheet" + ], + "license": "CC-BY-4.0", + "name": "todomvc-app-css", + "repository": { + "type": "git", + "url": "git+https://github.com/tastejs/todomvc-app-css.git" + }, + "style": "index.css", + "version": "2.1.0" +} diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-app-css/readme.md b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/readme.md new file mode 100644 index 00000000..6ddbebf0 --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-app-css/readme.md @@ -0,0 +1,28 @@ +# todomvc-app-css + +> CSS for TodoMVC apps + +![](screenshot.png) + + +## Install + + +``` +$ npm install --save todomvc-app-css +``` + + +## Getting started + +```html +<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css"> +``` + +See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). + + + +## License + +<a rel="license" href="http://creativecommons.org/licenses/by/4.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by/4.0/80x15.png" /></a><br />This <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/InteractiveResource" rel="dct:type">work</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="http://sindresorhus.com" property="cc:attributionName" rel="cc:attributionURL">Sindre Sorhus</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/deed.en_US">Creative Commons Attribution 4.0 International License</a>. diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-common/base.css b/examples/todomvc/src/main/web/node_modules/todomvc-common/base.css new file mode 100644 index 00000000..da65968a --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-common/base.css @@ -0,0 +1,141 @@ +hr { + margin: 20px 0; + border: 0; + border-top: 1px dashed #c5c5c5; + border-bottom: 1px dashed #f7f7f7; +} + +.learn a { + font-weight: normal; + text-decoration: none; + color: #b83f45; +} + +.learn a:hover { + text-decoration: underline; + color: #787e7e; +} + +.learn h3, +.learn h4, +.learn h5 { + margin: 10px 0; + font-weight: 500; + line-height: 1.2; + color: #000; +} + +.learn h3 { + font-size: 24px; +} + +.learn h4 { + font-size: 18px; +} + +.learn h5 { + margin-bottom: 0; + font-size: 14px; +} + +.learn ul { + padding: 0; + margin: 0 0 30px 25px; +} + +.learn li { + line-height: 20px; +} + +.learn p { + font-size: 15px; + font-weight: 300; + line-height: 1.3; + margin-top: 0; + margin-bottom: 0; +} + +#issue-count { + display: none; +} + +.quote { + border: none; + margin: 20px 0 60px 0; +} + +.quote p { + font-style: italic; +} + +.quote p:before { + content: '“'; + font-size: 50px; + opacity: .15; + position: absolute; + top: -20px; + left: 3px; +} + +.quote p:after { + content: '”'; + font-size: 50px; + opacity: .15; + position: absolute; + bottom: -42px; + right: 3px; +} + +.quote footer { + position: absolute; + bottom: -40px; + right: 0; +} + +.quote footer img { + border-radius: 3px; +} + +.quote footer a { + margin-left: 5px; + vertical-align: middle; +} + +.speech-bubble { + position: relative; + padding: 10px; + background: rgba(0, 0, 0, .04); + border-radius: 5px; +} + +.speech-bubble:after { + content: ''; + position: absolute; + top: 100%; + right: 30px; + border: 13px solid transparent; + border-top-color: rgba(0, 0, 0, .04); +} + +.learn-bar > .learn { + position: absolute; + width: 272px; + top: 8px; + left: -300px; + padding: 10px; + border-radius: 5px; + background-color: rgba(255, 255, 255, .6); + transition-property: left; + transition-duration: 500ms; +} + +@media (min-width: 899px) { + .learn-bar { + width: auto; + padding-left: 300px; + } + + .learn-bar > .learn { + left: 8px; + } +} diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-common/base.js b/examples/todomvc/src/main/web/node_modules/todomvc-common/base.js new file mode 100644 index 00000000..a56b5aac --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-common/base.js @@ -0,0 +1,249 @@ +/* global _ */ +(function () { + 'use strict'; + + /* jshint ignore:start */ + // Underscore's Template Module + // Courtesy of underscorejs.org + var _ = (function (_) { + _.defaults = function (object) { + if (!object) { + return object; + } + for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { + var iterable = arguments[argsIndex]; + if (iterable) { + for (var key in iterable) { + if (object[key] == null) { + object[key] = iterable[key]; + } + } + } + } + return object; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + return _; + })({}); + + if (location.hostname === 'todomvc.com') { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + ga('create', 'UA-31081062-1', 'auto'); + ga('send', 'pageview'); + } + /* jshint ignore:end */ + + function redirect() { + if (location.hostname === 'tastejs.github.io') { + location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); + } + } + + function findRoot() { + var base = location.href.indexOf('examples/'); + return location.href.substr(0, base); + } + + function getFile(file, callback) { + if (!location.host) { + return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); + } + + var xhr = new XMLHttpRequest(); + + xhr.open('GET', findRoot() + file, true); + xhr.send(); + + xhr.onload = function () { + if (xhr.status === 200 && callback) { + callback(xhr.responseText); + } + }; + } + + function Learn(learnJSON, config) { + if (!(this instanceof Learn)) { + return new Learn(learnJSON, config); + } + + var template, framework; + + if (typeof learnJSON !== 'object') { + try { + learnJSON = JSON.parse(learnJSON); + } catch (e) { + return; + } + } + + if (config) { + template = config.template; + framework = config.framework; + } + + if (!template && learnJSON.templates) { + template = learnJSON.templates.todomvc; + } + + if (!framework && document.querySelector('[data-framework]')) { + framework = document.querySelector('[data-framework]').dataset.framework; + } + + this.template = template; + + if (learnJSON.backend) { + this.frameworkJSON = learnJSON.backend; + this.frameworkJSON.issueLabel = framework; + this.append({ + backend: true + }); + } else if (learnJSON[framework]) { + this.frameworkJSON = learnJSON[framework]; + this.frameworkJSON.issueLabel = framework; + this.append(); + } + + this.fetchIssueCount(); + } + + Learn.prototype.append = function (opts) { + var aside = document.createElement('aside'); + aside.innerHTML = _.template(this.template, this.frameworkJSON); + aside.className = 'learn'; + + if (opts && opts.backend) { + // Remove demo link + var sourceLinks = aside.querySelector('.source-links'); + var heading = sourceLinks.firstElementChild; + var sourceLink = sourceLinks.lastElementChild; + // Correct link path + var href = sourceLink.getAttribute('href'); + sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); + sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; + } else { + // Localize demo links + var demoLinks = aside.querySelectorAll('.demo-link'); + Array.prototype.forEach.call(demoLinks, function (demoLink) { + if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { + demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); + } + }); + } + + document.body.className = (document.body.className + ' learn-bar').trim(); + document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); + }; + + Learn.prototype.fetchIssueCount = function () { + var issueLink = document.getElementById('issue-count-link'); + if (issueLink) { + var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onload = function (e) { + var parsedResponse = JSON.parse(e.target.responseText); + if (parsedResponse instanceof Array) { + var count = parsedResponse.length; + if (count !== 0) { + issueLink.innerHTML = 'This app has ' + count + ' open issues'; + document.getElementById('issue-count').style.display = 'inline'; + } + } + }; + xhr.send(); + } + }; + + redirect(); + getFile('learn.json', Learn); +})(); diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-common/package.json b/examples/todomvc/src/main/web/node_modules/todomvc-common/package.json new file mode 100644 index 00000000..a267618e --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-common/package.json @@ -0,0 +1,54 @@ +{ + "_from": "todomvc-common@^1.0.0", + "_id": "todomvc-common@1.0.4", + "_inBundle": false, + "_integrity": "sha512-AA0Z4exovEqubhbZCrzzn9roVT4zvOncS319p2zIc4CsNe5B9TLL7Sei1NIV6d+WrgR5rOi+y0I9Y6GE7xgNOw==", + "_location": "/todomvc-common", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "todomvc-common@^1.0.0", + "name": "todomvc-common", + "escapedName": "todomvc-common", + "rawSpec": "^1.0.0", + "saveSpec": null, + "fetchSpec": "^1.0.0" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.4.tgz", + "_shasum": "23099af886c2f0525bfd4537e078f12d0074309e", + "_spec": "todomvc-common@^1.0.0", + "_where": "/home/rjaros/git/kvision/examples/todomvc/src/main/web", + "author": { + "name": "TasteJS" + }, + "bugs": { + "url": "https://github.com/tastejs/todomvc-common/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "Common TodoMVC utilities used by our apps", + "files": [ + "base.js", + "base.css" + ], + "homepage": "https://github.com/tastejs/todomvc-common#readme", + "keywords": [ + "todomvc", + "tastejs", + "util", + "utilities" + ], + "license": "MIT", + "main": "base.js", + "name": "todomvc-common", + "repository": { + "type": "git", + "url": "git+https://github.com/tastejs/todomvc-common.git" + }, + "style": "base.css", + "version": "1.0.4" +} diff --git a/examples/todomvc/src/main/web/node_modules/todomvc-common/readme.md b/examples/todomvc/src/main/web/node_modules/todomvc-common/readme.md new file mode 100644 index 00000000..7a5de511 --- /dev/null +++ b/examples/todomvc/src/main/web/node_modules/todomvc-common/readme.md @@ -0,0 +1,15 @@ +# todomvc-common + +> Common TodoMVC utilities used by our apps + + +## Install + +``` +$ npm install --save todomvc-common +``` + + +## License + +MIT © [TasteJS](http://tastejs.com) diff --git a/examples/todomvc/src/main/web/package-lock.json b/examples/todomvc/src/main/web/package-lock.json new file mode 100644 index 00000000..244ffb88 --- /dev/null +++ b/examples/todomvc/src/main/web/package-lock.json @@ -0,0 +1,16 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "todomvc-app-css": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz", + "integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU=" + }, + "todomvc-common": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.4.tgz", + "integrity": "sha512-AA0Z4exovEqubhbZCrzzn9roVT4zvOncS319p2zIc4CsNe5B9TLL7Sei1NIV6d+WrgR5rOi+y0I9Y6GE7xgNOw==" + } + } +} diff --git a/examples/todomvc/src/main/web/package.json b/examples/todomvc/src/main/web/package.json new file mode 100644 index 00000000..e939a50f --- /dev/null +++ b/examples/todomvc/src/main/web/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "dependencies": { + "todomvc-app-css": "^2.0.0", + "todomvc-common": "^1.0.0" + } +} diff --git a/examples/todomvc/src/test/kotlin/test/com/example/TestUtil.kt b/examples/todomvc/src/test/kotlin/test/com/example/TestUtil.kt new file mode 100644 index 00000000..c5ec014f --- /dev/null +++ b/examples/todomvc/src/test/kotlin/test/com/example/TestUtil.kt @@ -0,0 +1,32 @@ +package test.com.example + +import pl.treksoft.jquery.jQuery +import kotlin.browser.document + +interface TestSpec { + fun beforeTest() + + fun afterTest() + + fun run(code: () -> Unit) { + beforeTest() + code() + afterTest() + } +} + +interface DomSpec : TestSpec { + + override fun beforeTest() { + val fixture = "<div style=\"display: none\" id=\"pretest\">" + + "<div id=\"helloworld\"></div></div>" + document.body?.insertAdjacentHTML("afterbegin", fixture) + } + + override fun afterTest() { + val div = document.getElementById("pretest") + div?.remove() + jQuery(`object` = ".modal-backdrop").remove() + } + +} diff --git a/examples/todomvc/webpack.config.d/bootstrap.js b/examples/todomvc/webpack.config.d/bootstrap.js new file mode 100644 index 00000000..32a7c4d0 --- /dev/null +++ b/examples/todomvc/webpack.config.d/bootstrap.js @@ -0,0 +1,4 @@ +config.module.rules.push({test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}); +config.module.rules.push({test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'}); +config.module.rules.push({test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}); +config.module.rules.push({test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'}); diff --git a/examples/todomvc/webpack.config.d/css.js b/examples/todomvc/webpack.config.d/css.js new file mode 100644 index 00000000..5d710d35 --- /dev/null +++ b/examples/todomvc/webpack.config.d/css.js @@ -0,0 +1,2 @@ +config.module.rules.push({ test: /\.css$/, loader: "style-loader!css-loader" }); + diff --git a/examples/todomvc/webpack.config.d/dce.js b/examples/todomvc/webpack.config.d/dce.js new file mode 100644 index 00000000..b536a6bf --- /dev/null +++ b/examples/todomvc/webpack.config.d/dce.js @@ -0,0 +1,2 @@ +var path = require("path"); +config.resolve.modules.unshift(path.resolve("./js/min")); diff --git a/examples/todomvc/webpack.config.d/file.js b/examples/todomvc/webpack.config.d/file.js new file mode 100644 index 00000000..8b853e7e --- /dev/null +++ b/examples/todomvc/webpack.config.d/file.js @@ -0,0 +1,6 @@ +config.module.rules.push( + { + test: /\.(jpe?g|png|gif|svg)$/i, + loader: 'file-loader' + } +);
\ No newline at end of file diff --git a/examples/todomvc/webpack.config.d/jquery.js b/examples/todomvc/webpack.config.d/jquery.js new file mode 100644 index 00000000..40522595 --- /dev/null +++ b/examples/todomvc/webpack.config.d/jquery.js @@ -0,0 +1,4 @@ +config.plugins.push(new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery" +})); diff --git a/examples/todomvc/webpack.config.d/minify.js b/examples/todomvc/webpack.config.d/minify.js new file mode 100644 index 00000000..34e706c9 --- /dev/null +++ b/examples/todomvc/webpack.config.d/minify.js @@ -0,0 +1,4 @@ +if (defined.PRODUCTION) { + config.plugins.push(new webpack.optimize.UglifyJsPlugin({ + })); +} |