Skip to content

Commit

Permalink
start migration to compose in webapp
Browse files Browse the repository at this point in the history
  • Loading branch information
InsanusMokrassar committed Dec 7, 2024
1 parent 1c43769 commit 76f1515
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 99 deletions.
6 changes: 6 additions & 0 deletions WebApp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ buildscript {
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"

id "org.jetbrains.kotlin.plugin.compose" version "$kotlin_version"
id "org.jetbrains.compose" version "$compose_version"
}

apply plugin: 'application'
Expand All @@ -27,12 +30,14 @@ kotlin {
dependencies {
implementation kotlin('stdlib')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
implementation compose.runtime
}
}

jsMain {
dependencies {
implementation "dev.inmo:tgbotapi.webapps:$telegram_bot_api_version"
implementation compose.web.core
}
}

Expand All @@ -41,6 +46,7 @@ kotlin {
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
implementation "dev.inmo:micro_utils.ktor.server:$micro_utils_version"
implementation "io.ktor:ktor-server-cio:$ktor_version"
implementation compose.desktop.currentOs
}
}
}
Expand Down
231 changes: 132 additions & 99 deletions WebApp/src/jsMain/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.*
Expand All @@ -18,6 +19,10 @@ import kotlinx.dom.appendElement
import kotlinx.dom.appendText
import kotlinx.dom.clear
import kotlinx.serialization.json.Json
import org.jetbrains.compose.web.dom.Button
import org.jetbrains.compose.web.dom.P
import org.jetbrains.compose.web.dom.Text
import org.jetbrains.compose.web.renderComposable
import org.w3c.dom.*
import kotlin.random.Random
import kotlin.random.nextUBytes
Expand All @@ -33,117 +38,145 @@ fun main() {
val client = HttpClient()
val baseUrl = window.location.origin.removeSuffix("/")

window.onload = {
val scope = CoroutineScope(Dispatchers.Default)
runCatching {

scope.launchSafelyWithoutExceptions {
val response = client.post("$baseUrl/check") {
setBody(
Json.encodeToString(
WebAppDataWrapper.serializer(),
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
)
renderComposable("root") {
val scope = rememberCoroutineScope()
val isSafeState = remember { mutableStateOf<Boolean?>(null) }
val logsState = remember { mutableStateListOf<String>() }
LaunchedEffect(baseUrl) {
val response = client.post("$baseUrl/check") {
setBody(
Json.encodeToString(
WebAppDataWrapper.serializer(),
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
)
}
val dataIsSafe = response.bodyAsText().toBoolean()

document.body ?.log(
if (dataIsSafe) {
"Data is safe"
} else {
"Data is unsafe"
}
)

document.body ?.log(
webApp.initDataUnsafe.chat.toString()
)
}
val dataIsSafe = response.bodyAsText().toBoolean()

document.body ?.appendElement("button") {
addEventListener("click", {
scope.launchSafelyWithoutExceptions {
handleResult({ "Clicked" }) {
client.post("${window.location.origin.removeSuffix("/")}/inline") {
parameter(webAppQueryIdField, it)
setBody(TextContent("Clicked", ContentType.Text.Plain))
document.body ?.log(url.build().toString())
}.coroutineContext.job.join()
}
}
})
appendText("Answer in chat button")
} ?: window.alert("Unable to load body")

document.body ?.appendElement("p", {})
document.body ?.appendText("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")

document.body ?.appendElement("p", {})
document.body ?.appendText("Alerts:")
document.body ?.log(
if (dataIsSafe) {
"Data is safe"
} else {
"Data is unsafe"
}
)

document.body ?.appendElement("button") {
addEventListener("click", {
webApp.showPopup(
PopupParams(
"It is sample title of default button",
"It is sample message of default button",
DefaultPopupButton("default", "Default button"),
OkPopupButton("ok"),
DestructivePopupButton("destructive", "Destructive button")
)
) {
document.body ?.log(
when (it) {
"default" -> "You have clicked default button in popup"
"ok" -> "You have clicked ok button in popup"
"destructive" -> "You have clicked destructive button in popup"
else -> "I can't imagine where you take button with id $it"
}
)
}
})
appendText("Popup")
} ?: window.alert("Unable to load body")
document.body ?.log(
webApp.initDataUnsafe.chat.toString()
)
}

document.body ?.appendElement("button") {
addEventListener("click", {
webApp.showAlert(
"This is alert message"
) {
document.body ?.log(
"You have closed alert"
)
Text(
when (isSafeState.value) {
null -> "Checking safe state..."
true -> "Data is safe"
false -> "Data is unsafe"
}
)
Text(webApp.initDataUnsafe.chat.toString())

Button({
onClick {
scope.launchSafelyWithoutExceptions {
handleResult({ "Clicked" }) {
client.post("${window.location.origin.removeSuffix("/")}/inline") {
parameter(webAppQueryIdField, it)
setBody(TextContent("Clicked", ContentType.Text.Plain))
logsState.add(url.build().toString())
}.coroutineContext.job.join()
}
})
appendText("Alert")
} ?: window.alert("Unable to load body")

document.body ?.appendElement("p", {})

document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess() })
appendText("Request write access without callback")
} ?: window.alert("Unable to load body")
}
}
}) {
Text("Answer in chat button")
}

document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess { document.body ?.log("Write access request result: $it") } })
appendText("Request write access with callback")
} ?: window.alert("Unable to load body")
P()
Text("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")

P()
Text("Alerts:")
Button({
onClick {
webApp.showPopup(
PopupParams(
"It is sample title of default button",
"It is sample message of default button",
DefaultPopupButton("default", "Default button"),
OkPopupButton("ok"),
DestructivePopupButton("destructive", "Destructive button")
)
) {
logsState.add(
when (it) {
"default" -> "You have clicked default button in popup"
"ok" -> "You have clicked ok button in popup"
"destructive" -> "You have clicked destructive button in popup"
else -> "I can't imagine where you take button with id $it"
}
)
}
}
}) {
Text("Popup")
}
Button({
onClick {
webApp.showAlert(
"This is alert message"
) {
logsState.add(
"You have closed alert"
)
}
}
}) {
Text("Alert")
}

document.body ?.appendElement("p", {})
P()
Button({
onClick {
webApp.requestWriteAccess()
}
}) {
Text("Request write access without callback")
}
Button({
onClick {
webApp.requestWriteAccess {
logsState.add("Write access request result: $it")
}
}
}) {
Text("Request write access with callback")
}

document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact() })
appendText("Request contact without callback")
} ?: window.alert("Unable to load body")
P()
Button({
onClick {
webApp.requestContact()
}
}) {
Text("Request contact without callback")
}
Button({
onClick {
webApp.requestContact { logsState.add("Contact request result: $it") }
}
}) {
Text("Request contact with callback")
}
P()

document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact { document.body ?.log("Contact request result: $it") } })
appendText("Request contact with callback")
} ?: window.alert("Unable to load body")
logsState.forEach {
P { Text(it) }
}
}

document.body ?.appendElement("p", {})
window.onload = {
val scope = CoroutineScope(Dispatchers.Default)
runCatching {

document.body ?.appendElement("button") {
addEventListener("click", {
Expand Down
1 change: 1 addition & 0 deletions WebApp/src/jsMain/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<title>Web App Example</title>
</head>
<body>
<div id="root"></div>
<script type="application/javascript" src="https://telegram.org/js/telegram-web-app.js"></script>
<script type="application/javascript" src="WebApp.js"></script>
</body>
Expand Down
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ allprojects {
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
}

// Fix of https://youtrack.jetbrains.com/issue/KTOR-7912/Module-not-found-errors-when-executing-browserProductionWebpack-task-since-3.0.2
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin.class) {
rootProject.kotlinYarn.resolution("ws", "8.18.0")
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ telegram_bot_api_version=22.0.0
micro_utils_version=0.23.2
serialization_version=1.7.3
ktor_version=3.0.2
compose_version=1.7.1

0 comments on commit 76f1515

Please sign in to comment.