diff --git a/.idea/runConfigurations/browser.xml b/.idea/runConfigurations/browserJs.xml similarity index 85% rename from .idea/runConfigurations/browser.xml rename to .idea/runConfigurations/browserJs.xml index 159625a..905d0c0 100644 --- a/.idea/runConfigurations/browser.xml +++ b/.idea/runConfigurations/browserJs.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations/browserWasmJs.xml b/.idea/runConfigurations/browserWasmJs.xml new file mode 100644 index 0000000..2cdacd1 --- /dev/null +++ b/.idea/runConfigurations/browserWasmJs.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7267b47..7bc736c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Deb import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl plugins { kotlin("multiplatform") @@ -58,6 +59,12 @@ kotlin { binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + binaries.executable() + } + sourceSets { val commonMain by getting { dependencies { diff --git a/app/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/app/Application.kt b/app/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/app/Application.kt new file mode 100644 index 0000000..6247308 --- /dev/null +++ b/app/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/app/Application.kt @@ -0,0 +1,25 @@ +package io.github.xxfast.decompose.router.app + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.CanvasBasedWindow +import io.github.xxfast.decompose.router.LocalRouterContext +import io.github.xxfast.decompose.router.RouterContext +import io.github.xxfast.decompose.router.screens.HomeScreen +import io.github.xxfast.decompose.router.defaultRouterContext + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + val rootRouterContext: RouterContext = defaultRouterContext() + + CanvasBasedWindow(canvasElementId = "ComposeTarget") { + CompositionLocalProvider( + LocalRouterContext provides rootRouterContext, + ) { + MaterialTheme { + HomeScreen() + } + } + } +} diff --git a/app/src/wasmJsMain/resources/index.html b/app/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000..058e262 --- /dev/null +++ b/app/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + App + + + + +
+ + +
+ + diff --git a/app/src/wasmJsMain/resources/styles.css b/app/src/wasmJsMain/resources/styles.css new file mode 100644 index 0000000..46c48b1 --- /dev/null +++ b/app/src/wasmJsMain/resources/styles.css @@ -0,0 +1,14 @@ +html, body { + height: 100%; + margin: 0px; + padding: 0px; +} + +canvas { + width: 100vw; + height: 100vh; + display: block; + position: fixed; + top: 0; + left: 0; +} diff --git a/decompose-router/build.gradle.kts b/decompose-router/build.gradle.kts index 93e37b5..89e8f75 100644 --- a/decompose-router/build.gradle.kts +++ b/decompose-router/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl plugins { kotlin("multiplatform") @@ -28,6 +29,11 @@ kotlin { browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + } + sourceSets { val commonMain by getting { dependencies { @@ -58,8 +64,6 @@ kotlin { } } - val jsMain by getting - val androidMain by getting { dependencies { implementation(compose.material3) @@ -83,6 +87,12 @@ kotlin { implementation(libs.compose.ui.test.manifest) } } + + val jsMain by getting { + dependencies { + implementation("org.jetbrains.kotlin-wrappers:kotlin-browser:1.0.0-pre.752") + } + } } } diff --git a/decompose-router/src/jsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt b/decompose-router/src/jsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt index 91f39af..d6ba47c 100644 --- a/decompose-router/src/jsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt +++ b/decompose-router/src/jsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt @@ -5,7 +5,8 @@ import com.arkivanov.essenty.lifecycle.LifecycleRegistry import com.arkivanov.essenty.lifecycle.resume import com.arkivanov.essenty.lifecycle.stop import kotlinx.browser.document -import org.w3c.dom.Document +import web.dom.DocumentVisibilityState +import web.dom.document as webDocument fun defaultRouterContext(): RouterContext { val backDispatcher = BackDispatcher() @@ -16,11 +17,12 @@ fun defaultRouterContext(): RouterContext { // Attaches the LifecycleRegistry to the document private fun LifecycleRegistry.attachToDocument() { - fun onVisibilityChanged() = if (document.visibilityState == "visible") resume() else stop() + fun onVisibilityChanged() = + if (webDocument.visibilityState == DocumentVisibilityState.visible) resume() + else stop() + onVisibilityChanged() - document.addEventListener(type = "visibilitychange", callback = { onVisibilityChanged() }) -} -private val Document.visibilityState: String - get() = asDynamic().visibilityState.unsafeCast() + document.addEventListener("visibilitychange", callback = { onVisibilityChanged() }) +} diff --git a/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt b/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt new file mode 100644 index 0000000..17f5a2b --- /dev/null +++ b/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/DefaultRouterContext.kt @@ -0,0 +1,27 @@ +package io.github.xxfast.decompose.router + +import com.arkivanov.essenty.backhandler.BackDispatcher +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import com.arkivanov.essenty.lifecycle.resume +import com.arkivanov.essenty.lifecycle.stop +import kotlinx.browser.document +import org.w3c.dom.Document + +fun defaultRouterContext(): RouterContext { + val backDispatcher = BackDispatcher() + val lifecycle = LifecycleRegistry() + lifecycle.attachToDocument() + return RouterContext(lifecycle = lifecycle, backHandler = backDispatcher) +} + +// Attaches the LifecycleRegistry to the document +private fun LifecycleRegistry.attachToDocument() { + fun onVisibilityChanged() = if (visibilityState(document) == "visible") resume() else stop() + onVisibilityChanged() + document.addEventListener(type = "visibilitychange", callback = { onVisibilityChanged() }) +} + +// Workaround for Document#visibilityState not available in Wasm +// From https://github.com/arkivanov/Minesweeper/blob/8270ffb0c75bf032b6d4da673c0bb2b01c9496ec/composeApp/src/wasmJsMain/kotlin/Main.kt#L47 +@JsFun("(document) => document.visibilityState") +private external fun visibilityState(document: Document): String diff --git a/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/Key.kt b/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/Key.kt new file mode 100644 index 0000000..2ec0c5a --- /dev/null +++ b/decompose-router/src/wasmJsMain/kotlin/io/github/xxfast/decompose/router/Key.kt @@ -0,0 +1,7 @@ +package io.github.xxfast.decompose.router + +import kotlin.reflect.KClass + +// TODO: Given that we don't have tree-shaking on js - yet, should be safe to use simpleName here +actual val KClass<*>.key: String get() = + requireNotNull(simpleName) { "Unable to use name of $this as the default key"} diff --git a/gradle.properties b/gradle.properties index 4eac971..9224226 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,9 +8,11 @@ android.nonTransitiveRClass=true #MPP kotlin.mpp.enableCInteropCommonization=true kotlin.android.buildTypeAttribute.keep=true + # Compose-multiplatform org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true # Optin to new Android source set layout # https://kotlinlang.org/docs/whatsnew18.html#kotlin-multiplatform-a-new-android-source-set-layout