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()
|
||||
)
|
||||
}
|
@ -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,
|
||||
|
Reference in New Issue
Block a user