From e4c122e4572cbd5f37c45b901424a29f53bb2689 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 12:08:41 +0100 Subject: [PATCH 1/6] Add basic UI for the compose app with Wasm --- build.gradle.kts | 37 +++++-- gradle.properties | 4 +- kotlin-js-store/yarn.lock | 99 ++++++++++------- settings.gradle.kts | 2 + src/commonMain/resources/static/index.html | 4 +- src/jvmMain/kotlin/Server.kt | 20 ++-- src/wasmJsMain/kotlin/App.kt | 117 +++++++++++++++++++++ src/wasmJsMain/kotlin/Main.kt | 10 ++ src/wasmJsMain/kotlin/ViewModel.kt | 16 +++ 9 files changed, 254 insertions(+), 55 deletions(-) create mode 100644 src/wasmJsMain/kotlin/App.kt create mode 100644 src/wasmJsMain/kotlin/Main.kt create mode 100644 src/wasmJsMain/kotlin/ViewModel.kt diff --git a/build.gradle.kts b/build.gradle.kts index e3ca622..64da41c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,16 +1,16 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack -val kotlinVersion = "1.9.10" -val serializationVersion = "1.6.0" +val serializationVersion = "1.6.2" val ktorVersion = "2.3.3" val logbackVersion = "1.2.11" val kotlinWrappersVersion = "1.0.0-pre.621" val kmongoVersion = "4.5.0" plugins { - kotlin("multiplatform") version "1.9.10" + kotlin("multiplatform") version "1.9.21" application //to run JVM part - kotlin("plugin.serialization") version "1.9.10" + kotlin("plugin.serialization") version "1.9.21" + id("org.jetbrains.compose") version "1.6.0-alpha01" } group = "org.example" @@ -18,6 +18,7 @@ version = "1.0-SNAPSHOT" repositories { mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } kotlin { @@ -29,11 +30,15 @@ kotlin { binaries.executable() } } + wasmJs { + browser { + binaries.executable() + } + } sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") - implementation("io.ktor:ktor-client-core:$ktorVersion") } } @@ -46,6 +51,7 @@ kotlin { val jvmMain by getting { dependencies { + implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-serialization:$ktorVersion") implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") @@ -60,6 +66,7 @@ kotlin { val jsMain by getting { dependencies { + implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-js:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") @@ -68,6 +75,17 @@ kotlin { implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom") } } + + val wasmJsMain by getting { + dependencies { + implementation(compose.runtime) + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) + implementation(compose.components.resources) + } + } } } @@ -75,6 +93,10 @@ application { mainClass.set("ServerKt") } +compose.experimental { + web.application {} +} + // include JS artifacts in any JAR we generate tasks.named("jvmJar").configure { val taskName = if (project.hasProperty("isProduction") @@ -86,8 +108,9 @@ tasks.named("jvmJar").configure { } val webpackTask = tasks.named(taskName) dependsOn(webpackTask) - from(webpackTask.map { it.mainOutputFile.get().asFile }) // bring output file along into the JAR + from(webpackTask.map { it.outputDirectory }) // bring output file along into the JAR into("static") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } tasks { @@ -116,4 +139,4 @@ tasks.register("stage") { tasks.named("run").configure { classpath(tasks.named("jvmJar")) // so that the JS artifacts generated by `jvmJar` can be found and served -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bda7801..0d437e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,6 @@ kotlin.js.compiler=ir kotlin.incremental=true kotlin.incremental.js=true kotlin.incremental.js.ir=true -kotlin.incremental.js.klib=true \ No newline at end of file +kotlin.incremental.js.klib=true +org.jetbrains.compose.experimental.wasm.enabled=true +kotlin.daemon.jvmargs=-Xmx4G diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 9f512ca..622d699 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -54,10 +54,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.17": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== +"@jridgewell/trace-mapping@^0.3.20": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -70,6 +70,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -142,9 +147,9 @@ integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.30" @@ -182,6 +187,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@>=10.0.0": version "18.6.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.5.tgz#06caea822caf9e59d5034b695186ee74154d2802" @@ -929,9 +941,9 @@ envinfo@^7.7.3: integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== es-module-lexer@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" - integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== + version "1.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" + integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== escalade@^3.1.1: version "3.1.1" @@ -1180,6 +1192,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1267,6 +1284,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -1406,11 +1430,11 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has "^1.0.3" + hasown "^2.0.0" is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" @@ -1602,12 +1626,12 @@ kind-of@^6.0.2: integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== launch-editor@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7" - integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ== + version "2.6.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" + integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== dependencies: picocolors "^1.0.0" - shell-quote "^1.7.3" + shell-quote "^1.8.1" loader-runner@^4.2.0: version "4.3.0" @@ -2122,9 +2146,9 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: is-core-module "^2.13.0" path-parse "^1.0.7" @@ -2203,10 +2227,11 @@ select-hose@^2.0.0: integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== dependencies: + "@types/node-forge" "^1.3.0" node-forge "^1" send@0.18.0: @@ -2294,7 +2319,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.3: +shell-quote@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== @@ -2482,20 +2507,20 @@ tapable@^2.1.1, tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" -terser@^5.16.8: - version "5.19.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.3.tgz#359baeba615aef13db4b8c4d77a2aa0d8814aa9e" - integrity sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg== +terser@^5.26.0: + version "5.26.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" + integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -2801,9 +2826,9 @@ ws@8.5.0: integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== ws@^8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== ws@~8.2.3: version "8.2.3" diff --git a/settings.gradle.kts b/settings.gradle.kts index 1dbc6de..a9990cb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,9 @@ pluginManagement { repositories { + google() mavenCentral() gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } } rootProject.name = "shoppinglist" diff --git a/src/commonMain/resources/static/index.html b/src/commonMain/resources/static/index.html index 85dca3c..ef6965c 100644 --- a/src/commonMain/resources/static/index.html +++ b/src/commonMain/resources/static/index.html @@ -6,6 +6,8 @@
- + + + \ No newline at end of file diff --git a/src/jvmMain/kotlin/Server.kt b/src/jvmMain/kotlin/Server.kt index 4d164f3..c6fbc2d 100644 --- a/src/jvmMain/kotlin/Server.kt +++ b/src/jvmMain/kotlin/Server.kt @@ -15,13 +15,15 @@ import io.ktor.serialization.kotlinx.json.* import io.ktor.server.plugins.cors.routing.* import org.litote.kmongo.reactivestreams.KMongo -val connectionString: ConnectionString? = System.getenv("MONGODB_URI")?.let { - ConnectionString("$it?retryWrites=false") -} +//val connectionString: ConnectionString? = System.getenv("MONGODB_URI")?.let { +// ConnectionString("$it?retryWrites=false") +//} + +//val client = if (connectionString != null) KMongo.createClient(connectionString).coroutine else KMongo.createClient().coroutine +//val database = client.getDatabase(connectionString?.database ?: "shoppingList") +//val collection = database.getCollection() -val client = if (connectionString != null) KMongo.createClient(connectionString).coroutine else KMongo.createClient().coroutine -val database = client.getDatabase(connectionString?.database ?: "shoppingList") -val collection = database.getCollection() +val collection = mutableListOf(); fun main() { val port = System.getenv("PORT")?.toInt() ?: 9090 @@ -44,15 +46,15 @@ fun main() { staticResources("/", "static") route(ShoppingListItem.path) { get { - call.respond(collection.find().toList()) + call.respond(collection) } post { - collection.insertOne(call.receive()) + collection.add(call.receive()) call.respond(HttpStatusCode.OK) } delete("/{id}") { val id = call.parameters["id"]?.toInt() ?: error("Invalid delete request") - collection.deleteOne(ShoppingListItem::id eq id) + collection.removeIf { it.id == id } call.respond(HttpStatusCode.OK) } } diff --git a/src/wasmJsMain/kotlin/App.kt b/src/wasmJsMain/kotlin/App.kt new file mode 100644 index 0000000..8e0675d --- /dev/null +++ b/src/wasmJsMain/kotlin/App.kt @@ -0,0 +1,117 @@ +// Reworked https://github.com/russhwolf/To-Do android app + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp + +@Composable +fun ShoppingList(viewModel: ShoppingListViewModel) { + val shoppingList = viewModel.shoppingList + + LaunchedEffect(Unit) { + viewModel.fetchShoppingList() + } + + ShoppingListTheme { + Surface { + Column(Modifier.fillMaxSize()) { + Input(onCreateItem = viewModel::addShoppingListItem) + LazyColumn { + items(shoppingList.size) { index -> + ShoppingItem( + shoppingListItem = shoppingList[index], + onDeleteClick = viewModel::deleteShoppingListItem + ) + } + } + } + } + } +} + +@Composable +fun Input(onCreateItem: (ShoppingListItem) -> Unit) = Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val input = remember { mutableStateOf(TextFieldValue()) } + + fun createShoppingItem() { + val text = input.value.text + if (text.isBlank()) return + onCreateItem(ShoppingListItem(text.replace("!", ""), text.count { it == '!' })) + input.value = input.value.copy(text = "") + } + + OutlinedTextField( + value = input.value, + onValueChange = { input.value = it }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { createShoppingItem() }), + modifier = Modifier + .weight(1f) + .padding(8.dp), + ) + OutlinedButton( + onClick = { createShoppingItem() }, + modifier = Modifier.padding(8.dp) + ) { + Text("Create") + } + } + Divider() +} + +@Composable +fun ShoppingItem(shoppingListItem: ShoppingListItem, onDeleteClick: (ShoppingListItem) -> Unit) = Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text("[${shoppingListItem.priority}] ${shoppingListItem.desc}", Modifier.weight(1f)) + IconButton( + onClick = { onDeleteClick(shoppingListItem) } + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + tint = MaterialTheme.colorScheme.error + ) + } + } + Divider() +} + +@Composable +fun ShoppingListTheme(content: @Composable () -> Unit) = MaterialTheme( + colorScheme = lightColorScheme( + primary = Color(0xFF3759DF), + secondary = Color(0xFF375A86) + ), + typography = MaterialTheme.typography, + shapes = MaterialTheme.shapes, + content = content +) \ No newline at end of file diff --git a/src/wasmJsMain/kotlin/Main.kt b/src/wasmJsMain/kotlin/Main.kt new file mode 100644 index 0000000..b7fab32 --- /dev/null +++ b/src/wasmJsMain/kotlin/Main.kt @@ -0,0 +1,10 @@ +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.CanvasBasedWindow + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + CanvasBasedWindow("ShoppingList") { + val vm = ShoppingListViewModel() + ShoppingList(vm) + } +} \ No newline at end of file diff --git a/src/wasmJsMain/kotlin/ViewModel.kt b/src/wasmJsMain/kotlin/ViewModel.kt new file mode 100644 index 0000000..053b34b --- /dev/null +++ b/src/wasmJsMain/kotlin/ViewModel.kt @@ -0,0 +1,16 @@ +import androidx.compose.runtime.mutableStateListOf + +class ShoppingListViewModel { + val shoppingList = mutableStateListOf() + + fun fetchShoppingList() { + } + + fun addShoppingListItem(shoppingListItem: ShoppingListItem) { + shoppingList.add(shoppingListItem) + } + + fun deleteShoppingListItem(shoppingListItem: ShoppingListItem) { + shoppingList.remove(shoppingListItem) + } +} \ No newline at end of file From e63afab3cf987c7ea4c52ceb0e88e11de97d5efe Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 13:01:24 +0100 Subject: [PATCH 2/6] Add API emulation --- build.gradle.kts | 5 ++- src/jsMain/kotlin/Api.kt | 2 - src/wasmJsMain/kotlin/Api.kt | 0 src/wasmJsMain/kotlin/HttpClient.kt | 57 +++++++++++++++++++++++++++++ src/wasmJsMain/kotlin/ViewModel.kt | 26 +++++++++++-- 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/wasmJsMain/kotlin/Api.kt create mode 100644 src/wasmJsMain/kotlin/HttpClient.kt diff --git a/build.gradle.kts b/build.gradle.kts index 64da41c..d9877ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,6 +84,7 @@ kotlin { implementation(compose.material3) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.components.resources) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2") } } } @@ -102,9 +103,9 @@ tasks.named("jvmJar").configure { val taskName = if (project.hasProperty("isProduction") || project.gradle.startParameter.taskNames.contains("installDist") ) { - "jsBrowserProductionWebpack" + "wasmJsBrowserProductionWebpack" } else { - "jsBrowserDevelopmentWebpack" + "wasmJsBrowserDevelopmentWebpack" } val webpackTask = tasks.named(taskName) dependsOn(webpackTask) diff --git a/src/jsMain/kotlin/Api.kt b/src/jsMain/kotlin/Api.kt index b8d51a6..c9f01d2 100644 --- a/src/jsMain/kotlin/Api.kt +++ b/src/jsMain/kotlin/Api.kt @@ -5,8 +5,6 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.serialization.kotlinx.json.* -import kotlinx.browser.window - val jsonClient = HttpClient { install(ContentNegotiation) { json() diff --git a/src/wasmJsMain/kotlin/Api.kt b/src/wasmJsMain/kotlin/Api.kt new file mode 100644 index 0000000..e69de29 diff --git a/src/wasmJsMain/kotlin/HttpClient.kt b/src/wasmJsMain/kotlin/HttpClient.kt new file mode 100644 index 0000000..ba60b38 --- /dev/null +++ b/src/wasmJsMain/kotlin/HttpClient.kt @@ -0,0 +1,57 @@ +// Until Ktor starts work with wasmJs +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +import kotlin.js.Promise + +class HttpClient { + suspend inline fun get(url: String): T = + Json.decodeFromString(fetchAsync(url)) + + suspend inline fun post(url: String, body: T) { + fetchAsync(url, FetchOptions(method = "POST", body = Json.encodeToString(body), headers = mapOf("Content-Type" to "application/json"))) + } + + suspend inline fun delete(url: String) { + fetchAsync(url, FetchOptions(method = "DELETE")) + } +} + +private fun buildFetchOptions(method: JsAny?, body: JsAny?, headers: JsAny?): JsAny = + js("{ method: method, body: body, headers: headers }") + +private fun fetch(url: String, opts: JsAny? = null): Promise = + js("fetch(url, opts).then(x => x.text())") + +private fun createJsArray(): JsArray = js("[]") + +private fun Map.toJsEntries(): JsArray> { + val result = createJsArray>() + for ((key, value) in this) { + val entry = createJsArray().apply { + set(0, key.toJsString()) + set(1, value.toJsString()) + } + result[result.length] = entry + } + return result +} + +suspend fun fetchAsync(url: String, opts: FetchOptions? = null): String { + val promise = fetch(url, buildFetchOptions(opts?.method?.toJsString(), opts?.body?.toJsString(), opts?.headers?.toJsEntries())) + return suspendCoroutine { cont -> + promise.then( + onFulfilled = { cont.resume(it.toString()).toJsReference() }, + onRejected = { cont.resumeWithException(IllegalStateException("Promise error: $it")).toJsReference() } + ) + } +} + +data class FetchOptions( + val method: String? = null, + val body: String? = null, + val headers: Map = emptyMap(), +) + diff --git a/src/wasmJsMain/kotlin/ViewModel.kt b/src/wasmJsMain/kotlin/ViewModel.kt index 053b34b..53b09a0 100644 --- a/src/wasmJsMain/kotlin/ViewModel.kt +++ b/src/wasmJsMain/kotlin/ViewModel.kt @@ -1,16 +1,34 @@ -import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.promise +@OptIn(DelicateCoroutinesApi::class) class ShoppingListViewModel { - val shoppingList = mutableStateListOf() + private val jsonClient = HttpClient() + + var shoppingList by mutableStateOf(listOf()) + private set fun fetchShoppingList() { + GlobalScope.promise { + shoppingList = jsonClient.get(ShoppingListItem.path) + } } fun addShoppingListItem(shoppingListItem: ShoppingListItem) { - shoppingList.add(shoppingListItem) + GlobalScope.promise { + jsonClient.post(ShoppingListItem.path, shoppingListItem) + shoppingList += shoppingListItem + } } fun deleteShoppingListItem(shoppingListItem: ShoppingListItem) { - shoppingList.remove(shoppingListItem) + GlobalScope.promise { + jsonClient.delete(ShoppingListItem.path + "/${shoppingListItem.id}") + shoppingList -= shoppingListItem + } } } \ No newline at end of file From d61ee81a86d039fa4583de20178c044764cf6d80 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 15:25:03 +0100 Subject: [PATCH 3/6] Add KTO --- build.gradle.kts | 12 ++--- src/{jsMain => commonMain}/kotlin/Api.kt | 0 src/wasmJsMain/kotlin/Api.kt | 0 src/wasmJsMain/kotlin/App.kt | 11 +++-- src/wasmJsMain/kotlin/HttpClient.kt | 57 ------------------------ src/wasmJsMain/kotlin/ViewModel.kt | 12 +++-- 6 files changed, 16 insertions(+), 76 deletions(-) rename src/{jsMain => commonMain}/kotlin/Api.kt (100%) delete mode 100644 src/wasmJsMain/kotlin/Api.kt delete mode 100644 src/wasmJsMain/kotlin/HttpClient.kt diff --git a/build.gradle.kts b/build.gradle.kts index d9877ca..2972241 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack val serializationVersion = "1.6.2" -val ktorVersion = "2.3.3" +val ktorVersion = "3.0.0-wasm2" val logbackVersion = "1.2.11" val kotlinWrappersVersion = "1.0.0-pre.621" val kmongoVersion = "4.5.0" @@ -19,6 +19,7 @@ version = "1.0-SNAPSHOT" repositories { mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental") } kotlin { @@ -38,6 +39,10 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + implementation(compose.runtime) + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") } } @@ -51,7 +56,6 @@ kotlin { val jvmMain by getting { dependencies { - implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-serialization:$ktorVersion") implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") @@ -66,10 +70,7 @@ kotlin { val jsMain by getting { dependencies { - implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-js:$ktorVersion") - implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") - implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation(project.dependencies.enforcedPlatform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:$kotlinWrappersVersion")) implementation("org.jetbrains.kotlin-wrappers:kotlin-react") implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom") @@ -78,7 +79,6 @@ kotlin { val wasmJsMain by getting { dependencies { - implementation(compose.runtime) implementation(compose.ui) implementation(compose.foundation) implementation(compose.material3) diff --git a/src/jsMain/kotlin/Api.kt b/src/commonMain/kotlin/Api.kt similarity index 100% rename from src/jsMain/kotlin/Api.kt rename to src/commonMain/kotlin/Api.kt diff --git a/src/wasmJsMain/kotlin/Api.kt b/src/wasmJsMain/kotlin/Api.kt deleted file mode 100644 index e69de29..0000000 diff --git a/src/wasmJsMain/kotlin/App.kt b/src/wasmJsMain/kotlin/App.kt index 8e0675d..179a667 100644 --- a/src/wasmJsMain/kotlin/App.kt +++ b/src/wasmJsMain/kotlin/App.kt @@ -33,12 +33,12 @@ fun ShoppingList(viewModel: ShoppingListViewModel) { ShoppingListTheme { Surface { Column(Modifier.fillMaxSize()) { - Input(onCreateItem = viewModel::addShoppingListItem) + Input(onCreateItem = viewModel::pushShoppingListItem) LazyColumn { items(shoppingList.size) { index -> ShoppingItem( shoppingListItem = shoppingList[index], - onDeleteClick = viewModel::deleteShoppingListItem + onDeleteClick = viewModel::removeShoppingListItem ) } } @@ -66,12 +66,11 @@ fun Input(onCreateItem: (ShoppingListItem) -> Unit) = Column { OutlinedTextField( value = input.value, + singleLine = true, onValueChange = { input.value = it }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { createShoppingItem() }), - modifier = Modifier - .weight(1f) - .padding(8.dp), + modifier = Modifier.weight(1f).padding(8.dp), ) OutlinedButton( onClick = { createShoppingItem() }, @@ -88,7 +87,7 @@ fun ShoppingItem(shoppingListItem: ShoppingListItem, onDeleteClick: (ShoppingLis Row( modifier = Modifier .fillMaxWidth() - .padding(8.dp), + .padding(16.dp), verticalAlignment = Alignment.CenterVertically, ) { Text("[${shoppingListItem.priority}] ${shoppingListItem.desc}", Modifier.weight(1f)) diff --git a/src/wasmJsMain/kotlin/HttpClient.kt b/src/wasmJsMain/kotlin/HttpClient.kt deleted file mode 100644 index ba60b38..0000000 --- a/src/wasmJsMain/kotlin/HttpClient.kt +++ /dev/null @@ -1,57 +0,0 @@ -// Until Ktor starts work with wasmJs -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import kotlin.js.Promise - -class HttpClient { - suspend inline fun get(url: String): T = - Json.decodeFromString(fetchAsync(url)) - - suspend inline fun post(url: String, body: T) { - fetchAsync(url, FetchOptions(method = "POST", body = Json.encodeToString(body), headers = mapOf("Content-Type" to "application/json"))) - } - - suspend inline fun delete(url: String) { - fetchAsync(url, FetchOptions(method = "DELETE")) - } -} - -private fun buildFetchOptions(method: JsAny?, body: JsAny?, headers: JsAny?): JsAny = - js("{ method: method, body: body, headers: headers }") - -private fun fetch(url: String, opts: JsAny? = null): Promise = - js("fetch(url, opts).then(x => x.text())") - -private fun createJsArray(): JsArray = js("[]") - -private fun Map.toJsEntries(): JsArray> { - val result = createJsArray>() - for ((key, value) in this) { - val entry = createJsArray().apply { - set(0, key.toJsString()) - set(1, value.toJsString()) - } - result[result.length] = entry - } - return result -} - -suspend fun fetchAsync(url: String, opts: FetchOptions? = null): String { - val promise = fetch(url, buildFetchOptions(opts?.method?.toJsString(), opts?.body?.toJsString(), opts?.headers?.toJsEntries())) - return suspendCoroutine { cont -> - promise.then( - onFulfilled = { cont.resume(it.toString()).toJsReference() }, - onRejected = { cont.resumeWithException(IllegalStateException("Promise error: $it")).toJsReference() } - ) - } -} - -data class FetchOptions( - val method: String? = null, - val body: String? = null, - val headers: Map = emptyMap(), -) - diff --git a/src/wasmJsMain/kotlin/ViewModel.kt b/src/wasmJsMain/kotlin/ViewModel.kt index 53b09a0..d0b9062 100644 --- a/src/wasmJsMain/kotlin/ViewModel.kt +++ b/src/wasmJsMain/kotlin/ViewModel.kt @@ -7,27 +7,25 @@ import kotlinx.coroutines.promise @OptIn(DelicateCoroutinesApi::class) class ShoppingListViewModel { - private val jsonClient = HttpClient() - var shoppingList by mutableStateOf(listOf()) private set fun fetchShoppingList() { GlobalScope.promise { - shoppingList = jsonClient.get(ShoppingListItem.path) + shoppingList = getShoppingList() } } - fun addShoppingListItem(shoppingListItem: ShoppingListItem) { + fun pushShoppingListItem(shoppingListItem: ShoppingListItem) { GlobalScope.promise { - jsonClient.post(ShoppingListItem.path, shoppingListItem) + addShoppingListItem(shoppingListItem) shoppingList += shoppingListItem } } - fun deleteShoppingListItem(shoppingListItem: ShoppingListItem) { + fun removeShoppingListItem(shoppingListItem: ShoppingListItem) { GlobalScope.promise { - jsonClient.delete(ShoppingListItem.path + "/${shoppingListItem.id}") + deleteShoppingListItem(shoppingListItem) shoppingList -= shoppingListItem } } From 27641bab821bed1c25bb4e68cbaee26548d3ae65 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 15:42:20 +0100 Subject: [PATCH 4/6] Use syntax sugar the state --- src/wasmJsMain/kotlin/App.kt | 61 +++++++++++++++++------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/src/wasmJsMain/kotlin/App.kt b/src/wasmJsMain/kotlin/App.kt index 179a667..19c643b 100644 --- a/src/wasmJsMain/kotlin/App.kt +++ b/src/wasmJsMain/kotlin/App.kt @@ -11,10 +11,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -48,38 +45,38 @@ fun ShoppingList(viewModel: ShoppingListViewModel) { } @Composable -fun Input(onCreateItem: (ShoppingListItem) -> Unit) = Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - val input = remember { mutableStateOf(TextFieldValue()) } +fun Input(onCreateItem: (ShoppingListItem) -> Unit) { + Column { + Row( + modifier = Modifier.fillMaxWidth().padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + var input by remember { mutableStateOf(TextFieldValue()) } - fun createShoppingItem() { - val text = input.value.text - if (text.isBlank()) return - onCreateItem(ShoppingListItem(text.replace("!", ""), text.count { it == '!' })) - input.value = input.value.copy(text = "") - } + fun createShoppingItem() { + val text = input.text.trim() + if (text.isBlank()) return + onCreateItem(ShoppingListItem(text.replace("!", ""), text.count { it == '!' })) + input = input.copy(text = "") + } - OutlinedTextField( - value = input.value, - singleLine = true, - onValueChange = { input.value = it }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { createShoppingItem() }), - modifier = Modifier.weight(1f).padding(8.dp), - ) - OutlinedButton( - onClick = { createShoppingItem() }, - modifier = Modifier.padding(8.dp) - ) { - Text("Create") + OutlinedTextField( + value = input, + singleLine = true, + onValueChange = { input = it }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { createShoppingItem() }), + modifier = Modifier.weight(1f).padding(8.dp), + ) + OutlinedButton( + onClick = { createShoppingItem() }, + modifier = Modifier.padding(8.dp) + ) { + Text("Create") + } } + Divider() } - Divider() } @Composable From ce02939d4164033a1bc38d059abcc851fa81bd9f Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 18:29:51 +0100 Subject: [PATCH 5/6] Add gradle property to switch in between JS and WASM --- build.gradle.kts | 5 +++-- gradle.properties | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2972241..fad423f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,12 +100,13 @@ compose.experimental { // include JS artifacts in any JAR we generate tasks.named("jvmJar").configure { + val taskPrefix = if (project.properties.get("app.run.wasm") == "true") "wasmJs" else "js" val taskName = if (project.hasProperty("isProduction") || project.gradle.startParameter.taskNames.contains("installDist") ) { - "wasmJsBrowserProductionWebpack" + "${taskPrefix}BrowserProductionWebpack" } else { - "wasmJsBrowserDevelopmentWebpack" + "${taskPrefix}BrowserDevelopmentWebpack" } val webpackTask = tasks.named(taskName) dependsOn(webpackTask) diff --git a/gradle.properties b/gradle.properties index 0d437e5..f772412 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,3 +6,4 @@ kotlin.incremental.js.ir=true kotlin.incremental.js.klib=true org.jetbrains.compose.experimental.wasm.enabled=true kotlin.daemon.jvmargs=-Xmx4G +app.run.wasm=false From 5cc51be7fb606422ec05e1e03a50730612ab0959 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Fri, 5 Jan 2024 18:31:59 +0100 Subject: [PATCH 6/6] Uncomment server logic for MongoDB --- src/jvmMain/kotlin/Server.kt | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/jvmMain/kotlin/Server.kt b/src/jvmMain/kotlin/Server.kt index c6fbc2d..4d164f3 100644 --- a/src/jvmMain/kotlin/Server.kt +++ b/src/jvmMain/kotlin/Server.kt @@ -15,15 +15,13 @@ import io.ktor.serialization.kotlinx.json.* import io.ktor.server.plugins.cors.routing.* import org.litote.kmongo.reactivestreams.KMongo -//val connectionString: ConnectionString? = System.getenv("MONGODB_URI")?.let { -// ConnectionString("$it?retryWrites=false") -//} - -//val client = if (connectionString != null) KMongo.createClient(connectionString).coroutine else KMongo.createClient().coroutine -//val database = client.getDatabase(connectionString?.database ?: "shoppingList") -//val collection = database.getCollection() +val connectionString: ConnectionString? = System.getenv("MONGODB_URI")?.let { + ConnectionString("$it?retryWrites=false") +} -val collection = mutableListOf(); +val client = if (connectionString != null) KMongo.createClient(connectionString).coroutine else KMongo.createClient().coroutine +val database = client.getDatabase(connectionString?.database ?: "shoppingList") +val collection = database.getCollection() fun main() { val port = System.getenv("PORT")?.toInt() ?: 9090 @@ -46,15 +44,15 @@ fun main() { staticResources("/", "static") route(ShoppingListItem.path) { get { - call.respond(collection) + call.respond(collection.find().toList()) } post { - collection.add(call.receive()) + collection.insertOne(call.receive()) call.respond(HttpStatusCode.OK) } delete("/{id}") { val id = call.parameters["id"]?.toInt() ?: error("Invalid delete request") - collection.removeIf { it.id == id } + collection.deleteOne(ShoppingListItem::id eq id) call.respond(HttpStatusCode.OK) } }