include compose
This commit is contained in:
client
features
content
common
src
commonMain
kotlin
dev
inmo
postssystem
features
content
common
server
src
jvmMain
kotlin
dev
inmo
postssystem
features
content
files
common
src
jvmMain
kotlin
dev
inmo
postssystem
features
files
common
gradle
settings.gradletargets/telegram/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/targets/telegram/publication/server
@ -2,6 +2,7 @@ plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
@ -29,6 +30,8 @@ kotlin {
|
||||
api "dev.inmo:micro_utils.fsm.common:$microutils_version"
|
||||
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
||||
api "dev.inmo:micro_utils.crypto:$microutils_version"
|
||||
|
||||
implementation compose.runtime
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +43,8 @@ kotlin {
|
||||
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-html:$kotlinx_html_version"
|
||||
implementation compose.web.core
|
||||
implementation libs.jsuikit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,28 @@
|
||||
package dev.inmo.postssystem.client.fsm.ui
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.inmo.jsuikit.elements.*
|
||||
import dev.inmo.jsuikit.modifiers.UIKitButton
|
||||
import dev.inmo.jsuikit.modifiers.UIKitMargin
|
||||
import dev.inmo.postssystem.client.ui.fsm.*
|
||||
import dev.inmo.postssystem.client.utils.HTMLViewContainer
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.form
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.dom.Form
|
||||
import org.w3c.dom.*
|
||||
|
||||
class AuthView(
|
||||
private val viewModel: AuthUIViewModel,
|
||||
private val uiScope: CoroutineScope
|
||||
) : JSView<AuthUIFSMState>() {
|
||||
private val usernameInput
|
||||
get() = document.getElementById("authUsername") as? HTMLInputElement
|
||||
private val passwordInput
|
||||
get() = document.getElementById("authPassword") as? HTMLInputElement
|
||||
private val authButton
|
||||
get() = document.getElementById("authButton")
|
||||
private val errorBadge
|
||||
get() = document.getElementById("errorBadge") as? HTMLElement
|
||||
private val progressBarDiv
|
||||
get() = document.getElementById("progressBar") as? HTMLDivElement
|
||||
|
||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||
htmlElement: HTMLElement,
|
||||
@ -38,42 +32,36 @@ class AuthView(
|
||||
val completion = CompletableDeferred<UIFSMState?>()
|
||||
htmlElement.clear()
|
||||
|
||||
htmlElement.append {
|
||||
form(classes = "vertical_container") {
|
||||
div(classes = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label") {
|
||||
input(type = InputType.text, classes = "mdl-textfield__input") {
|
||||
id = "authUsername"
|
||||
}
|
||||
label(classes = "mdl-textfield__label") {
|
||||
+"Имя пользователя"
|
||||
}
|
||||
val usernameState = mutableStateOf("")
|
||||
val passwordState = mutableStateOf("")
|
||||
val usernameDisabled = mutableStateOf(true)
|
||||
val passwordDisabled = mutableStateOf(true)
|
||||
val authBtnDisabled = remember {
|
||||
usernameState.value.isNotBlank() && passwordState.value.isNotBlank()
|
||||
}
|
||||
val errorText = mutableStateOf<String?>(null)
|
||||
|
||||
val composition = renderComposableAndLinkToContext(htmlElement) {
|
||||
Form {
|
||||
TextField(
|
||||
InputType.Text,
|
||||
usernameState,
|
||||
disabledState = usernameDisabled
|
||||
)
|
||||
TextField(
|
||||
InputType.Password,
|
||||
passwordState,
|
||||
disabledState = passwordDisabled
|
||||
)
|
||||
|
||||
if (errorText.value != null) {
|
||||
Label.Error.draw(errorText.value.toString(), UIKitMargin.Small.Bottom)
|
||||
}
|
||||
div(classes = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label") {
|
||||
input(type = InputType.password, classes = "mdl-textfield__input") {
|
||||
id = "authPassword"
|
||||
}
|
||||
label(classes = "mdl-textfield__label") {
|
||||
+"Пароль"
|
||||
}
|
||||
}
|
||||
div(classes = "mdl-progress mdl-js-progress") {
|
||||
id = "progressBar"
|
||||
}
|
||||
span(classes = "material-icons mdl-badge mdl-badge--overlap gone") {
|
||||
id = "errorBadge"
|
||||
attributes["data-badge"] = "!"
|
||||
}
|
||||
button(classes = "mdl-button mdl-js-button mdl-button--raised") {
|
||||
+"Авторизоваться"
|
||||
id = "authButton"
|
||||
onClickFunction = {
|
||||
it.preventDefault()
|
||||
val serverUrl = document.location ?.run { "$hostname:$port" }
|
||||
val username = usernameInput ?.value
|
||||
val password = passwordInput ?.value
|
||||
if (serverUrl != null && username != null && password != null) {
|
||||
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, username, password) }
|
||||
}
|
||||
|
||||
DefaultButton("Authorise", UIKitButton.Type.Primary, disabled = authBtnDisabled) {
|
||||
val serverUrl = document.location ?.run { "$hostname:$port" }
|
||||
if (serverUrl != null) {
|
||||
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, usernameState.value, passwordState.value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,38 +70,27 @@ class AuthView(
|
||||
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
||||
when (it) {
|
||||
is InitAuthUIState -> {
|
||||
usernameInput ?.removeAttribute("disabled")
|
||||
passwordInput ?.removeAttribute("disabled")
|
||||
authButton ?.removeAttribute("disabled")
|
||||
errorBadge ?.apply {
|
||||
when (it.showError) {
|
||||
ServerUnavailableAuthUIError -> {
|
||||
classList.remove("gone")
|
||||
innerText = "Сервер недоступен"
|
||||
}
|
||||
AuthIncorrectAuthUIError -> {
|
||||
classList.remove("gone")
|
||||
innerText = "Данные некорректны"
|
||||
}
|
||||
null -> classList.add("gone")
|
||||
}
|
||||
usernameDisabled.value = false
|
||||
passwordDisabled.value = false
|
||||
|
||||
errorText.value = when (it.showError) {
|
||||
ServerUnavailableAuthUIError -> "Сервер недоступен"
|
||||
AuthIncorrectAuthUIError -> "Данные некорректны"
|
||||
null -> null
|
||||
}
|
||||
progressBarDiv ?.classList ?.add("gone")
|
||||
}
|
||||
LoadingAuthUIState -> {
|
||||
usernameInput ?.setAttribute("disabled", "")
|
||||
passwordInput ?.setAttribute("disabled", "")
|
||||
authButton ?.setAttribute("disabled", "")
|
||||
errorBadge ?.classList ?.add("gone")
|
||||
progressBarDiv ?.classList ?.remove("gone")
|
||||
usernameDisabled.value = true
|
||||
passwordDisabled.value = true
|
||||
errorText.value = null
|
||||
}
|
||||
AuthorizedAuthUIState -> {
|
||||
htmlElement.clear()
|
||||
completion.complete(state.from)
|
||||
}
|
||||
}
|
||||
}
|
||||
return completion.await().also {
|
||||
composition.dispose()
|
||||
viewJob.cancel()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package dev.inmo.postssystem.client.utils
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.compose.web.dom.DOMScope
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun Composition.linkWithJob(job: Job) {
|
||||
job.invokeOnCompletion {
|
||||
this@linkWithJob.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)
|
||||
|
||||
suspend fun <TElement : Element> renderComposableAndLinkToContext(
|
||||
root: TElement,
|
||||
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
|
||||
content: @Composable DOMScope<TElement>.() -> Unit
|
||||
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
|
||||
linkWithContext(
|
||||
currentCoroutineContext()
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user