include compose

This commit is contained in:
2022-01-22 20:19:50 +06:00
parent 4f96d2ce76
commit 0716035f0b
9 changed files with 119 additions and 106 deletions
client
build.gradle
src
jsMain
kotlin
dev
inmo
postssystem
features
content
common
src
commonMain
kotlin
dev
inmo
postssystem
features
content
server
src
jvmMain
kotlin
dev
inmo
postssystem
features
files
common
src
jvmMain
kotlin
dev
inmo
postssystem
features
gradle
settings.gradle
targets/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()
)
}

@ -17,12 +17,7 @@ value class ContentId(val string: String)
* @see ContentSerializersModuleConfigurator.Element
* @see ContentSerializersModuleConfigurator
*/
sealed interface Content
/**
* This type of content represents simple content which is easy to serialize/deserialize and to use
*/
interface SimpleContent : Content
interface Content
/**
* This type represents some binary data which can be sent with multipart and deserialized from it

@ -1,22 +0,0 @@
package dev.inmo.postssystem.features.content.server
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.common.FileName
import io.ktor.http.content.PartData
import java.io.File
suspend fun PartData.loadContent() {
when (this) {
is PartData.FormItem -> TODO()
is PartData.FileItem -> {
val fileName = FileName(originalFileName ?: return null)
val downloadTo = File.createTempFile(
uuid4().toString(),
fileName.extension
)
headers["data"]
return
}
is PartData.BinaryItem -> return null/* Currently impossible state */
}
}

@ -3,6 +3,8 @@ package dev.inmo.postssystem.features.files.common
import com.benasher44.uuid.uuid4
import dev.inmo.postssystem.features.files.common.storage.WriteFilesStorage
import dev.inmo.micro_utils.repos.*
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asOutput
import kotlinx.coroutines.flow.*
import java.io.File
@ -36,7 +38,7 @@ class WriteDistFilesStorage(
file = newId.file
} while (file.exists())
metasKeyValueRepo.set(newId, it.toMetaFileInfo())
file.writeBytes(it.inputProvider())
it.inputProvider().copyTo(file.outputStream().asOutput())
FullFileInfoStorageWrapper(newId, it)
}
@ -52,13 +54,15 @@ class WriteDistFilesStorage(
override suspend fun update(
id: FileId,
value: FullFileInfo
): FullFileInfoStorageWrapper? = id.file.takeIf { it.exists() } ?.writeBytes(value.inputProvider()) ?.let {
): FullFileInfoStorageWrapper? {
val file = id.file.takeIf { it.exists() } ?: return null
value.inputProvider().copyTo(file.outputStream().asOutput())
val result = FullFileInfoStorageWrapper(id, value.copy())
metasKeyValueRepo.set(id, value.toMetaFileInfo())
_updatedObjectsFlow.emit(result)
result
return result
}
override suspend fun update(

26
gradle/libs.versions.toml Normal file

@ -0,0 +1,26 @@
[versions]
jsuikit = "0.0.15"
compose = "1.0.1"
microutils = "0.9.4"
[libraries]
jsuikit = { module = "dev.inmo:kjsuikit", version.ref = "jsuikit" }
microutils-common = { module = "dev.inmo:micro_utils.common", version.ref = "microutils" }
microutils-pagination-common = { module = "dev.inmo:micro_utils.pagination.common", version.ref = "microutils" }
microutils-fsm-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" }
microutils-fsm-repos-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" }
microutils-crypto = { module = "dev.inmo:micro_utils.crypto", version.ref = "microutils" }
microutils-repos-common = { module = "dev.inmo:micro_utils.repos.common", version.ref = "microutils" }
microutils-repos-ktor-client = { module = "dev.inmo:micro_utils.repos.ktor.client", version.ref = "microutils" }
microutils-repos-ktor-server = { module = "dev.inmo:micro_utils.repos.ktor.server", version.ref = "microutils" }
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
microutils-mimetypes = { module = "dev.inmo:micro_utils.mime_types", version.ref = "microutils" }
microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" }
microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" }
[plugins]
compose = { id = "org.jetbrains.compose", version.ref = "compose" }

@ -70,3 +70,5 @@ includes.each { originalName ->
project.projectDir = new File(projectDirectory)
println(project)
}
enableFeaturePreview("VERSION_CATALOGS")

@ -1,7 +1,7 @@
package dev.inmo.postssystem.targets.telegram.publication.server
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
import dev.inmo.postssystem.features.content.binary.common.DefaultBinaryContent
import dev.inmo.postssystem.features.content.common.BinaryContent
import dev.inmo.postssystem.features.content.text.common.TextContent
import dev.inmo.postssystem.features.publication.server.PublicationPost
import dev.inmo.postssystem.features.publication.server.PublicationTarget
@ -12,6 +12,8 @@ import dev.inmo.tgbotapi.requests.send.SendTextMessage
import dev.inmo.tgbotapi.requests.send.media.*
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.utils.StorageFile
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.delay
class PublicationTargetTelegram(
@ -22,9 +24,9 @@ class PublicationTargetTelegram(
post.content.mapNotNull {
val content = it.content
when (content) {
is DefaultBinaryContent -> {
is BinaryContent -> {
val storageFile by lazy {
StorageFile(content.filename.name, content.bytesAllocator()).asMultipartFile()
StorageFile(content.filename.name, content.inputProvider().readBytes()).asMultipartFile()
}
when (content.mimeType) {
is KnownMimeTypes.Image.Jpeg,