Skip to content

Commit f26939d

Browse files
authored
Renovate web sample (#324)
wasm sample renovated
1 parent 4031f35 commit f26939d

File tree

4 files changed

+96
-139
lines changed

4 files changed

+96
-139
lines changed

.github/workflows/wasm_deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ jobs:
2222
java-version: 17
2323

2424
- name: Build web app
25-
run: ./gradlew :example:app:composeApp:wasmJsBrowserDistribution
25+
run: ./gradlew :sample:app-wasm:wasmJsBrowserDistribution
2626

2727
- name: Upload artifact
2828
uses: actions/upload-pages-artifact@v4
2929
with:
30-
path: 'example/app/composeApp/build/dist/wasmJs/productionExecutable'
30+
path: 'sample/app-wasm/build/dist/wasmJs/productionExecutable'
3131

3232
- name: Deploy to GitHub Pages
3333
id: deployment

sample/app-wasm/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
alias(libs.plugins.kotlin.multiplatform)
66
alias(libs.plugins.compose.compiler)
77
alias(libs.plugins.jetbrains.compose)
8+
alias(libs.plugins.buildconfig)
89
}
910

1011
kotlin {
@@ -27,4 +28,11 @@ kotlin {
2728
implementation(projects.sample.shared)
2829
}
2930
}
31+
}
32+
33+
buildConfig {
34+
packageName("com.composegears.tiamat.sample")
35+
buildConfigField<String>("TIAMAT_VERSION", tiamat.versions.tiamat.get())
36+
37+
useKotlinOutput()
3038
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package composegears.tiamat.sample
2+
3+
import com.composegears.tiamat.TiamatExperimentalApi
4+
import com.composegears.tiamat.navigation.NavController
5+
import com.composegears.tiamat.navigation.NavEntry
6+
import com.composegears.tiamat.navigation.Route
7+
import kotlinx.browser.window
8+
import org.w3c.dom.PopStateEvent
9+
10+
private const val TITLE = "Tiamat Wasm"
11+
12+
internal external fun encodeURIComponent(str: String): String
13+
internal external fun decodeURIComponent(encodedURI: String): String
14+
internal external fun addEventListener(type: String, callback: (PopStateEvent) -> Unit)
15+
16+
@OptIn(ExperimentalWasmJsInterop::class, TiamatExperimentalApi::class)
17+
internal object Browser {
18+
19+
fun bind(
20+
navController: NavController,
21+
) {
22+
addEventListener("popstate") { _ ->
23+
flushLocation(navController)
24+
}
25+
navController.setOnNavigationListener { _, _, _ ->
26+
updateHistory(navController)
27+
window.document.title = titleOf(navController.getCurrentNavEntry())
28+
29+
}
30+
flushLocation(navController)
31+
window.document.title = titleOf(navController.getCurrentNavEntry())
32+
updateHistory(navController, forceReplace = true)
33+
}
34+
35+
fun titleOf(entry: NavEntry<*>?): String {
36+
val name = entry?.destination?.name.orEmpty()
37+
return if (name.isEmpty()) TITLE else "$TITLE / $name"
38+
}
39+
40+
fun getCurrentHost(): String = window.location.origin + window.location.pathname.ifBlank { "/" }
41+
fun getCurrentPath(): String = window.location.href.replace(getCurrentHost(), "")
42+
43+
fun flushLocation(
44+
navController: NavController,
45+
) {
46+
val path = getCurrentPath().takeIf { it.isNotBlank() && it != "/" }
47+
path?.let(::path2route)?.let(navController::route)
48+
}
49+
50+
fun updateHistory(
51+
navController: NavController,
52+
forceReplace: Boolean = false,
53+
) {
54+
val title = window.document.title
55+
val path = navController2path(navController)
56+
if (forceReplace) {
57+
window.history.replaceState(data = null, title = title, url = path)
58+
} else {
59+
window.history.pushState(data = null, title = title, url = path)
60+
}
61+
}
62+
63+
fun path2route(path: String): Route {
64+
val segments = path.removePrefix("/").removePrefix("#/").split('/').filter { it.isNotBlank() }
65+
return Route {
66+
segments.forEach {
67+
destination(decodeURIComponent(it))
68+
}
69+
}
70+
}
71+
72+
fun navController2path(navController: NavController): String {
73+
val stack = navController.getNavStack()
74+
if (stack.isEmpty()) return "/"
75+
return buildString {
76+
append(getCurrentHost())
77+
append("#/")
78+
append(stack.joinToString(separator = "/") { encodeURIComponent(it.destination.name) })
79+
}
80+
}
81+
}

sample/app-wasm/src/wasmJsMain/kotlin/composegears/tiamat/sample/main.kt

Lines changed: 5 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,16 @@ import androidx.compose.ui.text.font.FontWeight
1414
import androidx.compose.ui.unit.dp
1515
import androidx.compose.ui.unit.sp
1616
import androidx.compose.ui.window.ComposeViewport
17-
import kotlinx.browser.window
18-
19-
const val TITLE = "Tiamat Wasm"
17+
import com.composegears.tiamat.sample.BuildConfig
2018

2119
external fun onLoadFinished()
2220

2321
@OptIn(ExperimentalComposeUiApi::class)
2422
fun main() {
2523
ComposeViewport(viewportContainerId = "TiamatTarget") {
26-
LaunchedEffect(Unit) {
27-
onLoadFinished()
28-
}
24+
LaunchedEffect(Unit) { onLoadFinished() }
2925
App(
30-
navControllerConfig = {
31-
// open link from browser upon initialization
32-
// loadFromURL(this)
33-
// listen to browser back/forward navigation
34-
// window.addEventListener("popstate") { e ->
35-
// loadFromState(this, (e as? PopStateEvent?)?.state)
36-
// }
37-
// handle navigation events
38-
// setOnNavigationListener { from, to, isForward ->
39-
// if (isForward) {
40-
// appendState(this)
41-
// }
42-
// }
43-
setOnNavigationListener { from, to, isForward ->
44-
window.document.title = TITLE + " / " + (to?.destination?.name ?: "")
45-
}
46-
},
26+
navControllerConfig = { Browser.bind(this) },
4727
overlay = {
4828
Box(modifier = Modifier.fillMaxSize()) {
4929
Text(
@@ -52,122 +32,10 @@ fun main() {
5232
.padding(16.dp),
5333
fontSize = 20.sp,
5434
fontWeight = FontWeight.Bold,
55-
text = "Wasm Beta",
35+
text = "Tiamat WASM ${BuildConfig.TIAMAT_VERSION}",
5636
)
5737
}
5838
}
5939
)
6040
}
61-
}
62-
63-
// todo add browser history & state save/restore logic
64-
65-
// Simple web browser history & url impl
66-
67-
// ---------------- web operations -------------------------------
68-
69-
/*
70-
const val HOME = "Home"
71-
val REDIRECT_DELAY = 100.milliseconds
72-
73-
var redirectTimeout = now()
74-
75-
fun now() = TimeSource.Monotonic.markNow()
76-
77-
fun setTitle(title: String) {
78-
window.document.title = title
79-
}
80-
81-
fun getCurrentPath() = window.location.href
82-
.replace(window.location.origin, "")
83-
.replace("/#", "")
84-
85-
fun loadFromURL(navController: NavController) {
86-
redirectTimeout = now()
87-
val browserPath = getCurrentPath()
88-
val browserRoute = path2route(browserPath)
89-
if (browserPath.isBlank() || browserPath == "/") {
90-
window.history.replaceState("$HOME/".toJsString(), "", "./#$HOME")
91-
setTitle(HOME)
92-
} else {
93-
window.history.replaceState(browserRoute.toJsString(), "", "./#$HOME")
94-
navController.setRoute(browserRoute)
95-
}
96-
}
97-
98-
fun loadFromState(navController: NavController, state: Any?) {
99-
redirectTimeout = now()
100-
(state as? JsString?)?.toString()?.let { navController.setRoute(it) } ?: loadFromURL(navController)
101-
}
102-
103-
fun appendState(navController: NavController) {
104-
val browserPath = getCurrentPath()
105-
val navControllerRoute = navController.getRoute()
106-
val navControllerPath = route2path(navControllerRoute)
107-
if (browserPath != navControllerPath) {
108-
if (redirectTimeout.elapsedNow() < REDIRECT_DELAY) {
109-
window.history.replaceState(navControllerRoute.toJsString(), "", "./#$navControllerPath")
110-
} else {
111-
redirectTimeout = now()
112-
window.history.pushState(navControllerRoute.toJsString(), "", "./#$navControllerPath")
113-
}
114-
}
115-
setTitle(navController.getCurrentNavEntry()?.destination?.name ?: TITLE)
116-
}
117-
118-
// --------------- helpers --------------------------------------
119-
120-
fun path2route(path: String): String {
121-
// here we parse path and make nav-route from it
122-
// append HOME dest as first one for any path (if needed)
123-
return when {
124-
path == "/" -> HOME
125-
path.startsWith(HOME) -> path
126-
else -> "$HOME/$path"
127-
}
128-
}
129-
130-
fun route2path(route: String): String {
131-
// here we convert route to web path (we can clip or make path shorter)
132-
// clip HOME dest if there is any other screen is opened as HOME is always the 1st by default
133-
return when {
134-
route.startsWith("$HOME/") -> route.substringAfter("$HOME/")
135-
else -> route
136-
}
137-
}
138-
139-
fun <Args> NavDestination<Args>.parseEntry(argStr: String?): NavEntry<Args> {
140-
val ext = ext<ScreenInfo<Args>>()
141-
val args = ext?.stringToArgs?.invoke(argStr)
142-
return toNavEntry(navArgs = args)
143-
}
144-
145-
@OptIn(TiamatExperimentalApi::class)
146-
fun NavController.setRoute(path: String) {
147-
route {
148-
path.split("/").forEach { segment ->
149-
val targetName = segment.substringBefore("?")
150-
val argsStr = segment.substringAfter("?", "").takeIf { it.isNotBlank() }
151-
if (targetName == "nc") navController(argsStr)
152-
else destination(targetName)
153-
}
154-
}
155-
}
156-
157-
private fun <Args> NavEntry<Args>.toPath(): String? {
158-
val ext = destination.ext<ScreenInfo<Args>>() ?: return null
159-
val name = ext.name ?: destination.name
160-
val args = navArgs?.let(ext.argsToString)
161-
return if (args == null) name else "$name?$args"
162-
}
163-
164-
fun NavController.getRoute(): String {
165-
val segments = mutableListOf<String>()
166-
var nc: NavController? = this
167-
while (nc != null) {
168-
nc.getCurrentNavEntry()?.toPath()?.let { segments.add(0, it) }
169-
segments.add(0, "nc?${nc.key}")
170-
nc = nc.parent
171-
}
172-
return segments.joinToString("/")
173-
}*/
41+
}

0 commit comments

Comments
 (0)