Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wasm with compose final #38

Open
wants to merge 6 commits into
base: final
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack

val kotlinVersion = "1.9.10"
val serializationVersion = "1.6.0"
val ktorVersion = "2.3.3"
val serializationVersion = "1.6.2"
val ktorVersion = "3.0.0-wasm2"
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"
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 {
@@ -29,11 +31,19 @@ kotlin {
binaries.executable()
}
}
wasmJs {
browser {
binaries.executable()
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
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")
}
}

@@ -61,33 +71,48 @@ kotlin {
val jsMain by getting {
dependencies {
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")
}
}

val wasmJsMain by getting {
dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material3)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2")
}
}
}
}

application {
mainClass.set("ServerKt")
}

compose.experimental {
web.application {}
}

// include JS artifacts in any JAR we generate
tasks.named<Jar>("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")
) {
"jsBrowserProductionWebpack"
"${taskPrefix}BrowserProductionWebpack"
} else {
"jsBrowserDevelopmentWebpack"
"${taskPrefix}BrowserDevelopmentWebpack"
}
val webpackTask = tasks.named<KotlinWebpack>(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 +141,4 @@ tasks.register("stage") {

tasks.named<JavaExec>("run").configure {
classpath(tasks.named<Jar>("jvmJar")) // so that the JS artifacts generated by `jvmJar` can be found and served
}
}
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -3,4 +3,7 @@ kotlin.js.compiler=ir
kotlin.incremental=true
kotlin.incremental.js=true
kotlin.incremental.js.ir=true
kotlin.incremental.js.klib=true
kotlin.incremental.js.klib=true
org.jetbrains.compose.experimental.wasm.enabled=true
kotlin.daemon.jvmargs=-Xmx4G
app.run.wasm=false
99 changes: 62 additions & 37 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
rootProject.name = "shoppinglist"
2 changes: 0 additions & 2 deletions src/jsMain/kotlin/Api.kt → src/commonMain/kotlin/Api.kt
Original file line number Diff line number Diff line change
@@ -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()
4 changes: 3 additions & 1 deletion src/commonMain/resources/static/index.html
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@
</head>
<body>
<div id="root"></div>
<script src="shoppinglist.js"></script>
<canvas id="ComposeTarget"></canvas>
<script type="application/javascript" src="skiko.js"></script>
<script type="application/javascript" src="shoppinglist.js"></script>
</body>
</html>
113 changes: 113 additions & 0 deletions src/wasmJsMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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.*
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::pushShoppingListItem)
LazyColumn {
items(shoppingList.size) { index ->
ShoppingItem(
shoppingListItem = shoppingList[index],
onDeleteClick = viewModel::removeShoppingListItem
)
}
}
}
}
}
}

@Composable
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.text.trim()
if (text.isBlank()) return
onCreateItem(ShoppingListItem(text.replace("!", ""), text.count { it == '!' }))
input = input.copy(text = "")
}

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()
}
}

@Composable
fun ShoppingItem(shoppingListItem: ShoppingListItem, onDeleteClick: (ShoppingListItem) -> Unit) = Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.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
)
10 changes: 10 additions & 0 deletions src/wasmJsMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
32 changes: 32 additions & 0 deletions src/wasmJsMain/kotlin/ViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 {
var shoppingList by mutableStateOf(listOf<ShoppingListItem>())
private set

fun fetchShoppingList() {
GlobalScope.promise {
shoppingList = getShoppingList()
}
}

fun pushShoppingListItem(shoppingListItem: ShoppingListItem) {
GlobalScope.promise {
addShoppingListItem(shoppingListItem)
shoppingList += shoppingListItem
}
}

fun removeShoppingListItem(shoppingListItem: ShoppingListItem) {
GlobalScope.promise {
deleteShoppingListItem(shoppingListItem)
shoppingList -= shoppingListItem
}
}
}