include compose
This commit is contained in:
parent
4f96d2ce76
commit
0716035f0b
@ -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
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,
|
||||
|
Loading…
Reference in New Issue
Block a user